Skip to content

Defer apex-root terminal transition until feature -> main promotion (AB#3169) - #376

Merged
PolyphonyRequiem merged 1 commit into
mainfrom
fix/ab-3169-defer-apex-state-until-promotion
May 14, 2026
Merged

Defer apex-root terminal transition until feature -> main promotion (AB#3169)#376
PolyphonyRequiem merged 1 commit into
mainfrom
fix/ab-3169-defer-apex-state-until-promotion

Conversation

@PolyphonyRequiem

Copy link
Copy Markdown
Owner

Symptom (AB#3169)

During the AB#3166 dogfood, the apex Issue's ADO state went Doing → Done
at 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_completer in .conductor/registry/workflows/implement-merge-group.yaml
unconditionally fired:

polyphony validate --event implementation_complete --work-item $taskId ...
twig state Done --id $taskId ...

…for primary_router.output.primary_id. For an indivisible apex (a
plan with one task, where primary_id == apex_id == root_id), this
transitioned the apex Issue at MG → feature merge time, ahead of the
real "the work is shipped" moment (feature → main).

process-config.yaml:

Issue.implementation_complete: Done

…so the validate event triggered the cascade.

Fix

Carve out the apex-root case in primary_completer:

  1. Capture $rootId = [int]'{{ workflow.input.root_id }}'.
  2. Compute $isApexRoot = ($taskId -eq $rootId).
  3. Apex root branch: skip the validate + state transition. Add an
    AB#3169-referencing twig note + sync only. Output deferred_apex_root: true.
  4. Child task branch: unchanged. Validate + state Done as before
    (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 only
after promote_feature_to_main + promote_feature_pr_dispatch from
PR #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 → Feature cascade blast radius, so only the
indivisible-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:

Both are needed for the gap to fully close.

Tests

  • lint-implement-merge-group.Tests.ps1: 38/38 pass (5 new
    structural tests in new Context "primary_completer apex-root carve-out
    (AB#3169)").
  • Full registry suite: 391/393 (2 pre-existing failures in
    lint-plan-level.Tests.ps1 "Policy wiring checks" — unrelated;
    stash-confirmed on main).
  • conductor validate implement-merge-group.yaml: Successful.
  • All other workflow lints (strict-undefined, pwsh-jinja-bareword,
    sync-after-mutation) green.

Verification path (post-merge)

After this PR + PR #375 are both on main:

  1. Dispatch an indivisible apex.
  2. At MG → feature merge: primary_completer emits
    deferred_apex_root: true and adds a note. ADO state stays Doing.
  3. apex-driver routes: outer_loop_evaluator(complete) → apex_completion_gate_policy_router → promote_feature_to_main → promote_feature_pr_dispatch → close_mark_satisfied.
  4. close_mark_satisfied fires item_satisfied. ADO state
    transitions to Done. Parent cascade runs at the correct moment.

Closes AB#3169.

…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
PolyphonyRequiem force-pushed the fix/ab-3169-defer-apex-state-until-promotion branch from cdf820b to d91edd9 Compare May 14, 2026 05:35
@PolyphonyRequiem
PolyphonyRequiem merged commit aca6370 into main May 14, 2026
1 check passed
@PolyphonyRequiem
PolyphonyRequiem deleted the fix/ab-3169-defer-apex-state-until-promotion branch May 14, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant