Defer apex-root terminal transition until feature -> main promotion (AB#3169) - #376
Merged
PolyphonyRequiem merged 1 commit intoMay 14, 2026
Merged
Conversation
…B#3169) `primary_completer` in `implement-merge-group.yaml` previously fired `polyphony validate --event implementation_complete` + `twig state Done` unconditionally for `primary_router.output.primary_id`. For an **indivisible apex** (where primary_id == apex_id), that transitioned the apex Issue from `Doing → Done` at the MG → feature merge moment, **before** feature → main was merged. ADO then cascaded the apex's parent items (User Story / Feature) to Done while the work was still sitting on the feature branch. Companion to PR #375 (AB#3168), which inserted `promote_feature_to_main` + `promote_feature_pr_dispatch` into apex-driver.yaml's close path so the feature → main merge happens before `close_mark_satisfied`. With that promotion path in place, the apex's terminal state can safely defer to `close_mark_satisfied` (which fires `item_satisfied`). ## What this PR does - `primary_completer`: capture `$rootId` from `workflow.input.root_id`, compute `$isApexRoot = ($taskId -eq $rootId)`, then split the body: - **Apex root branch**: skip `polyphony validate --event implementation_complete` and `twig state Done`. Only add an explanatory note + sync. Output includes `deferred_apex_root: true` so observability is preserved. - **Child task branch**: unchanged — `validate` then `twig state Done`, matching the pre-existing rubber-duck #7 acknowledgement that child Tasks transition at MG→feature merge time. - Updated `description:` on the agent (with quoting to keep the colon inside the parenthetical out of YAML mapping scope). - 5 new structural Pester tests in `lint-implement-merge-group.Tests.ps1` asserting: rootId capture, isApexRoot discriminator, if/else partition of the apex-root vs child-task code paths, `deferred_apex_root` flag in the JSON output, and AB#3169-referencing note text in the apex-root branch. ## Tests - `lint-implement-merge-group.Tests.ps1`: **38/38 pass** (was 33/33 + 5 new). - Full `.conductor/registry/tests` suite: **391/393 pass**. Two failures in `lint-plan-level.Tests.ps1` "Policy wiring checks" are pre-existing on `main` (stash-confirmed unrelated; out of scope for this PR). - `conductor validate implement-merge-group.yaml`: Successful. - `lint-strict-undefined`, `lint-pwsh-jinja-bareword`, `lint-sync-after-mutation`: green. ## Why apex-root only For child Tasks inside a multi-task MG, the existing rubber-duck #7 acknowledgement at the top of `primary_completer` explicitly accepts the MG→feature timing for the implementation_complete event. That behavior is unchanged. Only the indivisible-apex case (the symptom filed in AB#3169) is carved out, because: 1. Apex Issues have a different ADO state-machine semantics (`item_satisfied` is the conventional terminal trigger, fired by `close_mark_satisfied`). 2. The apex's terminal transition cascades to its parent tree (User Story / Feature in the AB#3169 trace), so a premature transition has blast radius beyond the apex itself. For child Tasks, no such cascade happens — they're leaves. ## How AB#3168 + AB#3169 interact Together they close the gap that AB#3169 surfaced: - AB#3168 (PR #375, merged): apex-driver.yaml now routes through `promote_feature_to_main` → `promote_feature_pr_dispatch` → `close_mark_satisfied`. Feature → main is merged BEFORE `close_mark_satisfied` fires `item_satisfied`. - AB#3169 (this PR): primary_completer no longer prematurely transitions the apex Issue at MG→feature merge. The Issue stays in `Doing` until `close_mark_satisfied` runs `item_satisfied` after promotion. Result: apex Issue (and its parent cascade) only goes Done after the work is actually on `main`. Closes AB#3169. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem
force-pushed
the
fix/ab-3169-defer-apex-state-until-promotion
branch
from
May 14, 2026 05:35
cdf820b to
d91edd9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom (AB#3169)
During the AB#3166 dogfood, the apex Issue's ADO state went
Doing → Doneat the moment the MG was merged into the feature branch (21:26:43Z) —
before feature → main was merged. ADO then cascaded the apex's
parent items (User Story / Feature) to Done while the work was still
sitting on the feature branch and not yet on
main.Root cause
primary_completerin.conductor/registry/workflows/implement-merge-group.yamlunconditionally fired:
…for
primary_router.output.primary_id. For an indivisible apex (aplan with one task, where
primary_id == apex_id == root_id), thistransitioned the apex Issue at MG → feature merge time, ahead of the
real "the work is shipped" moment (feature → main).
process-config.yaml:…so the validate event triggered the cascade.
Fix
Carve out the apex-root case in
primary_completer:$rootId = [int]'{{ workflow.input.root_id }}'.$isApexRoot = ($taskId -eq $rootId).AB#3169-referencing
twig note+ sync only. Outputdeferred_apex_root: true.(matches pre-existing rubber-duck Feature/2582 pg 4 #7 acknowledgement at line 636-641).
The apex's terminal transition is now deferred to
close_mark_satisfied(which fires
polyphony validate --event item_satisfied), reached onlyafter
promote_feature_to_main+promote_feature_pr_dispatchfromPR #375 (AB#3168) merge feature → main.
Why apex-root only
For child Tasks inside a multi-task MG, the existing carve-out comment
explicitly accepts the MG → feature timing (Tasks have no parent
cascade — they're leaves). Only the indivisible-apex case has the
Issue → User Story → Featurecascade blast radius, so only theindivisible-apex case needs deferral.
Sister PR
This PR is a sibling to PR #375 (AB#3168) — they were filed
together as defense-in-depth follow-ups from the AB#3166 dogfood:
apex-driver.yamlto actuallypromote feature → main before close. Without this PR, the apex would
still transition prematurely.
merge time. Without PR Insert feature → main promotion before close_mark_satisfied (AB#3168) #375, deferring would leave the apex stuck in
Doingforever.Both are needed for the gap to fully close.
Tests
lint-implement-merge-group.Tests.ps1: 38/38 pass (5 newstructural tests in new Context "primary_completer apex-root carve-out
(AB#3169)").
lint-plan-level.Tests.ps1"Policy wiring checks" — unrelated;stash-confirmed on
main).conductor validate implement-merge-group.yaml: Successful.sync-after-mutation) green.
Verification path (post-merge)
After this PR + PR #375 are both on
main:primary_completeremitsdeferred_apex_root: trueand adds a note. ADO state staysDoing.outer_loop_evaluator(complete) → apex_completion_gate_policy_router → promote_feature_to_main → promote_feature_pr_dispatch → close_mark_satisfied.close_mark_satisfiedfiresitem_satisfied. ADO statetransitions to
Done. Parent cascade runs at the correct moment.Closes AB#3169.