Skip to content

Pipeline Roles Finalizer

Autumn edited this page Oct 17, 2026 · 11 revisions

Finalizer role: spec (draft)

The merger, reimagined as the finalizer (proposed: finalizer-role). The sections marked "today" describe the merger as built; the proposed sections replace them.

Draft from integration c1797bd (2026-10-16). It describes today's code; adjust it as the split settles. Index: Pipeline roles.

Purpose

The finalizer takes an issue that is reviewed and approved, merges the base into its branch, retests it, and then either sends findings back to the implementor or merges and closes the PR (Axlog, 2026-10-16: "The merger should be reimagined as the "finalizer". It does the merge, retests, and kicks back findings to the implementor or merges and closes the PR").

  • It runs no model session and repairs nothing. A conflict or a failed retest becomes a finding for the implementor.
  • It takes over the implementor's sync stage: bringing the branch up to date with the base now happens here, once, right before the merge. After implementation, this rebase is the only sync. The implementor syncs only at the start of its entries, before any edit (sync-at-edges).
  • The security review runs again on the merged head, as the last check before landing (final-security-review), unless that head's tree is the one the security review last passed (security-skip-unchanged). The finalizer hands off to the reviewer for it and lands only the head it passed. A base that moves after that review sends the issue back to the merge. Deferred (roles-6-10-scope): for now there is no final security review, and the finalizer only syncs, merges and reruns the tests.
  • It publishes the issue's wiki commits, closes the issue and any finished parent, and frees closed issues' local resources, as the merger does today.
  • It is the only role that holds merge credentials. It also pushes the rebased branch. Neither token reaches a model, because it runs no session.

Today (the merger): it lands a change that every check and review has passed, and finishes the issue. It squash-merges the verified head in merge order, publishes the issue's wiki commits, closes the issue and any finished parent, and frees closed issues' local resources.

Scope today

  • Stages: merge (stages/merge.ts, merge-wiki.ts), publish (stages/publish.ts), and the sweep's closing steps for parents (stages/parents.ts) and cleanup (stages/cleanup.ts).
  • Ops:
    • merge (src/ops/merge.ts, merge-order.ts);
    • finish-wiki, close-parent (src/ops/finish.ts);
    • wiki-sync, finish-wiki-sync, publish (src/ops/publish.ts, src/wiki-publish.ts);
    • wiki-check before the merge;
    • cleanup (src/ops/cleanup.ts).
  • Sessions: none of its own. The wiki conflict resolver (agent issue-pipeline-writer) runs inside merge and publish today. It replies with CIFIX_SCHEMA, given in Implementor. Proposed (merge-only-when-ready): it moves to the spec writer, and the merger runs no session.
  • Job role: merger for merge and publish. Its tokenEnv token goes to ops, never to a model. Proposed (finalizer-role): merger for finalize and publish, with push access to agent/* branches added for the rebase, since no session runs.

Lifecycle (proposed: finalizer-role, final-security-review)

Built first (roles-6-10-scope): the finalizer runs today's merge stage, and publish stays as it is. In order, it applies today's merge gate, waits in merge order (step 3 of finalize below), and rebases a branch that is behind (step 4). A clean rebase is pushed and retested in the same job (step 5), and the issue merges once the retest passes, as today's testsOnly path does. A rebase conflict saves the issue at sync, and a failed retest at checks, so the implementor handles them as today. It then handles the wiki as merge does today, merges, and finishes as step 4 of land describes. Deferred: finalize and land as stages, the conflict and retest findings, the final security review, the ready check and the returns for missing verdicts.

The finalizer has three entries: finalize, land and publish. Each is a linear script, not a state machine (role-state-machines), on the shared runner. Wiki access: write, in the job's docs/, at the issue's wiki branch, used only by wiki-sync and publish, which are ops, and by the wiki conflict resolver. Code access: read, in repo/: merge and publish run as merger, so pipeline_commit refuses a repo/ path there (#338). No session commits, and the finalizer runs no session: the final security review is the reviewer's, between finalize and land.

Stage finalize replaces today's sync and merge. The reviewer's first security pass saves the issue at finalize.

The security review runs again as the last check, on the merged head (Axlog, 2026-10-16: "The security review should happen again as the final step after the merge. If a conflict occurs after the security review, the process returns to the merge."). So the order is: merge the base in, retest, final security review, land.

finalize (merge the base in and retest)

  1. Claim the issue, and check out the PR's head and the issue's wiki branch.
  2. Ready check (script, below): the issue is reviewed and approved at the PR's head. A missing verdict posts a note and returns the issue to the stage that gives it (Returns for missing verdicts, below).
  3. Wait: merge order, or a stacked prerequisite that is still open. A queued reply saves and waits.
  4. Merge the base in: rebase the branch onto the base (sync-base), or onto the base once a stacked prerequisite has merged. A branch that already contains the base's head skips the rebase.
    • Clean: push with --force-with-lease to the head the ready check saw.
    • Conflict: abort the rebase, and send a conflict finding to the implementor (below).
  5. Retest the new head: setup, the test command (twice, as verify runs it: as the branch has it, and with the spec writer's tests as at specTestsSha), lint, typecheck and the lockfile check, then CI at the head (ci-status). A head that is already verifiedSha with CI green skips this.
    • Pass: record verifiedSha at the new head.
    • Failure: a retest finding, with the failing output, to the implementor.
    • CI pending: end held, and the next start carries on from the pushed head.
  6. Wiki: with wiki commits, wiki-sync and wiki-check. A conflict or failed check returns the issue to the spec writer, the wiki's writer (see Open questions).
  7. Hand off: save the issue at final-security, with mergedSha (the retested head) and mergedBaseSha (the base head it contains). The reviewer's final security review runs next.

final-security (the reviewer's; see Reviewer): the security reviewer reviews the merged head, unless its tree equals securityTree, the tree the security review last passed (security-skip-unchanged); a skip records finalPassedSha and saves at land with no session. A pass records finalPassedSha, securitySha and securityTree, and saves at land. Findings go to the implementor at fix, as security findings do today, and the issue comes back through checks, accept, security and finalize.

land

  1. Claim, and read the PR's head and the base's head from GitHub. No checkout is needed.
  2. Still current? The PR's head must be finalPassedSha, and the base's head must still be mergedBaseSha (or contained in the branch).
    • Base moved, or the merge reports behind or conflict: the process returns to the merge. Save at finalize, which rebases, retests and sends the issue through the final security review again. At most maxSyncs times per issue, then stop.
    • PR head moved: a missing verdict; return to finalize, whose ready check routes it.
  3. Merge: mark the draft PR ready, then squash-merge with --match-head-commit at finalPassedSha. GitHub closes the PR.
  4. Finish: publish the wiki commits (or save at publish if that failed), close the issue when the base isn't the default branch, close-parent, run commands.teardown, remove the worktree and branches, label agent:merged, and release.

publish

  1. wiki-sync. A conflict returns the issue to the spec writer, which rebases the wiki branch and saves the issue back at publish.
  2. publish, then finish as in step 4 of land.

First and final security review (decided: security-skip-unchanged, Axlog, 2026-10-17): both reviews stay, and each runs only when the code changed since the security review last passed: when the head's tree differs from securityTree. The first review sends findings back before acceptance and the merge-order wait. The final review then runs on the merged head only when merging the base in changed the tree. When the branch already contained the base, the merged head's tree is the one the first review passed, and the final review is skipped.

Merge order and the final review: the merge-order wait comes before the rebase, so only the issue at the head of the queue spends a final security review. A merge into the base by someone else can still move it. That returns the issue to finalize, as above.

Locate (proposed: every-role-resumes)

Every start runs the steps in order. The ready check and the up-to-date check skip what is done: a branch that already contains the base's head isn't rebased again, and a head equal to verifiedSha with CI green isn't retested. Each step that changes GitHub checks first whether it is done (Shared): merge whether the PR is merged, publish whether the wiki's base holds the issue's wiki commits, closing the issue whether it is closed, branch deletion whether the branch exists. Merge order holds across a restart, since --merge-queue is in the job's arguments. An interrupted rebase or wiki-sync starts over from the pushed heads, and an interrupted retest runs again.

No work (proposed: no-work-skips-stages)

  • finalize (today merge): a PR already merged by hand gets the bookkeeping and no merge, as today. A wiki-only change goes through finish-wiki, as today. An issue already closed with no PR left to merge is released, with nothing done.
  • publish: no wiki commits ahead of the wiki's base means there is nothing to publish. The issue is finished without a push.

cleanup of other closed issues stays with whatever runs each sweep (today the sweep's entry; see the orchestrator's open questions).

Inputs

The finalizer reads these, but trusts none of them just because the issue is at finalize: the ready check tests each against the PR's current head.

  • Proposed: from the reviewer, at finalize: a security review with no finding, and passedSha.
  • From the reviewer, at merge: passedSha after a security review with no finding.
  • From the implementor, at merge: a clean rebase of a passed head that re-ran its tests (testsOnly).
  • verifiedSha, verifySummary, flows, stackedOn, and no open blocking finding.
  • From the orchestrator, optionally: its ranked merge queue (--merge-queue).

Outputs

  • Merged: the PR squash-merged into the base at verifiedSha, a signed merger comment, the code branch deleted, the wiki commits published, the issue closed (when the base isn't the default branch) and labelled agent:merged, its claims closed, the state at done.
  • Wiki only: finish-wiki publishes the wiki commits and closes the issue as merged, with no PR merge.
  • Waiting: stays at merge when queued behind another issue or a stacked prerequisite.
  • Back to the implementor: sync when the branch is behind or conflicts, keeping passedSha so a clean rebase re-runs only the tests.
  • Findings (proposed): a conflict or retest finding saved for the implementor, the issue at fix, a signed note on the PR, and finalizeReturns counted.
  • Returns for missing verdicts (proposed): a signed note on the PR and the issue saved at the stage that gives the verdict, with finalizeNote in its state and finalizeReturns counted.
  • At publish: code merged but the wiki commits not yet published.
  • Stops: merge-refused, wiki-check, conflict, budget, a security category or error. Proposed: only merge-refused (what no earlier stage can fix), finalize-returns (new, past the limit), maxSyncs reached, budget and error; the rest become findings or returns.

Requirements

Ready check (proposed: merge-only-when-ready, finalizer-role)

Deferred (roles-6-10-scope).

The finalizer works only on an issue that is reviewed and approved (Axlog, 2026-10-16: "The merge should operate on approved and reviewed."). A script checks, before any other step, reading GitHub, git and the issue's state and never a model. At the PR's current head:

  • there is a PR, open, for agent/issue-N (none needed on the wiki-only path);
  • reviewed: the head's diff was reviewed (reviewedSha, through the fix rounds), or the head's changes have the patch id the review passed (reviewedPatchId, review-keeps-on-clean-rebase), and no blocking finding is open;
  • approved: acceptance passed at the head (acceptedSha) or at changes with the same patch id (acceptedPatchId), and the security review passed at it (passedSha; it keeps its own rule, security-skip-unchanged).

Being up to date with the base, the tests and CI aren't part of the check: the finalizer brings the branch up to date and retests it itself.

Bring up to date and retest (proposed: finalizer-role)

  • The finalizer rebases onto the base (today's sync-base, moved from the implementor). For a stacked issue it waits while the prerequisite is open, and rebases onto the base once it has merged.
  • Proposed (sync-at-edges): this rebase, after the merge-order wait, is the only sync after implementation. No sync runs after a review clears, and none while an implementor entry runs. The implementor's other syncs come at the start of its entries, before any edit. Only returns to finalize count toward maxSyncs. The implementor's start syncs don't.
  • A clean rebase keeps the verdicts. The diff from the base is the reviewed and approved diff, so only the retest runs, as today's testsOnly rule does. Built today (review-keeps-on-clean-rebase, #394): the review and acceptance stand on the changes' patch id, so a restart after a clean rebase keeps the review and an acceptance at changes with the patch id it passed runs no session, while the security review still runs when the tree changed. A rebase that conflicts is aborted; the finalizer never resolves it.
  • The retest is today's verify, minus the fails-before-fix check, plus CI at the new head. A pass records verifiedSha at the head the finalizer will merge.
  • A head that already contains the base's head and equals verifiedSha with CI green skips both.

Findings to the implementor (proposed: finalizer-role)

A conflict or a failed retest is a finding, saved with the issue's findings and sent to the implementor at fix:

  • conflict: the base's head, the conflicting files and commits. The fix entry's start sync does the rebase, and the implementor's conflict resolver resolves it there, before any other edit (sync-at-edges).
  • retest: what failed (a test, lint, typecheck, the lockfile, a CI check), with its output clipped. The implementor's CI fixer handles it.

The finalizer posts a signed note on the PR naming the findings, marked <!-- issue-pipeline:<issue>:finalize-note:<n> -->, and counts the return in finalizeReturns (a checkpoint). Code the implementor changes then goes through its checks, the check-fix review, accept and security again, as a resolved conflict or a check fix does today, and comes back to finalize.

Returns for missing verdicts (proposed: merge-only-when-ready)

Deferred (roles-6-10-scope).

When the ready check finds something missing, the finalizer posts the same kind of note, saves finalizeNote: { missing, detail, sha, stage } (which the receiving role's task includes), counts the return in finalizeReturns, and saves the issue at the stage that supplies it, for the first missing item in this order:

Missing Back to Role
no open PR (none was ever opened) spec, whose open-pr step opens it spec writer
a PR a person closed none: stops with merge-refused, since closing is a person's call
an open blocking finding fix implementor
head's diff not reviewed review reviewer
acceptance not passed at the head accept reviewer
security review not passed at the head security reviewer
wiki commits conflict with the wiki's base, or fail wiki-check spec (a wiki-sync entry, see Spec writer) spec writer
  • Each receiving role's own locate and no-work checks carry the issue forward again, back to finalize.
  • Past maxFinalizeReturns (proposed: 3 per issue, findings and missing verdicts together), the finalizer stops with agent:stopped-finalize-returns, the notes listing every return.
  • A refusal no earlier stage can fix stops with merge-refused and a note on how to fix it: branch protection that requires an approval the pipeline can't give, or a PR a person closed.
  • Waiting in merge order or on a stacked prerequisite isn't a return.
  • A network failure defers to the next sweep, as today.

Merge gate (today)

  • An open blocking finding stops the merge before any launch. An open security finding stops with its category as the code; any other with no code. Proposed: a return to fix.
  • The merge request carries the state and the checks' summary. The summary notes a verdict given before a clean rebase.
  • merge refuses, changing nothing:
    • a closed PR, with its state;
    • a PR whose head moved from verifiedSha, with the new head (proposed: the finalizer retests the head instead);
    • a conflicting PR, as conflict;
    • a PR GitHub calls BEHIND, a refusal "Base branch was modified", or a verified head that lacks the base's latest commit (checked locally), as behind.
  • A PR merged by hand gets the merge bookkeeping and no merge.
  • A draft PR is marked ready before the merge. Proposed (spec-writer-opens-pr): every PR is still a draft here, since no earlier role marks it ready, so this step always runs. mergeAdmin merges with --admin. A refusal for want of an approval says so and how to fix it.
  • No approving review is ever needed: GitHub doesn't count a PR author's approval, and the pipeline's account is the author. Branch protection on the base should require CI but not an approval, or let the account bypass it (mergeAdmin).
  • The behind check runs locally as well as on GitHub, because without branch protection GitHub would merge a stale branch.

Merge order

The finalizer keeps this order. Weighing re-check cost, such as merging the issue with the dearest checks first, is deferred (roles-6-10-scope).

  • With mergeOrder: true and no queue, the older PR merges first: another open agent:working issue at accept, security or merge with an older PR makes merge reply queued. Issues at sync or checks, stopped issues, newer PRs, unreviewed issues and expired leases don't hold the line (#358).
  • The order is total, so two issues never wait on each other. It exists so a slow issue's verify stops losing merge races to quick issues, each loss costing one of its maxSyncs.
  • The order is opt-in (mergeOrder: true), so a sweep running an older copy of the workflow is unaffected.
  • With the orchestrator's --merge-queue (issues ranked by how many waiting issues they unblock), merge waits only for an issue ahead of it in that queue that is open, not stopped and at accept, security or merge; one at sync or checks doesn't hold it (#358).
  • A stacked issue isn't ranked while its prerequisite is open, so it neither heads the queue nor holds another merge.
  • A stacked issue replies queued while its prerequisite is open, naming it in waitingFor and stackedOn, with its PR in waitingForPr (0 when it has none), with or without a queue. Once the prerequisite merged, it replies behind until sync-base unstacks it. The wait uses stack.ts's one merged rule (prereqMerged, through stackedQueued in src/ops/merge-order.ts), and doesn't depend on mergeOrder.
  • A queued merge notes the wait once and stays at merge for the next sweep, costing only ops calls.

Merge outcomes

  • merge squashes with --match-head-commit set to verifiedSha, posts a signed merger comment, and finishes the issue.
  • A rebased head that changed goes through verification and acceptance again, but not the first review. A clean rebase of a head that passed every check, acceptance and the security review (a lost merge race) re-runs only the tests (setup, the test command and the lockfile check, skipping fails-before-fix) and then merges. A resolved conflict or a check fix changes code, so every check runs again.
  • behind sends the issue back to sync in the same sweep, once, keeping its passed head. A second behind in one sweep, and a conflict, wait at sync for the next sweep. All of this counts toward maxSyncs. Proposed (finalizer-role): the finalizer rebases and retests itself, and only a conflict goes to the implementor, as a finding.
  • A network failure defers to the next sweep. Any other failed merge step, or a refused merge, stops the issue.

Wiki at merge and publish

  • A repo's wiki is on when its config has wiki: true or { dir, branch, url }. A repo whose wiki doesn't exist yet is skipped; GitHub creates the wiki repo only when its first page is made in the browser.
  • With a wiki, merge first rebases the issue's wiki commits onto the wiki's latest version (wiki-sync) and checks them (wiki-check). A failed check stops the issue with agent:stopped-wiki-check before the merge. Without a wiki, it merges at once.
  • Proposed (merge-only-when-ready): a wiki conflict or a failed wiki-check returns the issue to spec, and the finalizer runs no session. Today: a failed check stops the issue, and a wiki conflict goes to the conflict resolver in the wiki checkout. A resolved conflict goes on to the check without counting a sync. A rejected resolution stays at merge for the next sweep. A conflict after the last resolution (maxConflictFixes) stops as conflict.
  • A merge whose wiki commits were not published moves to publish. publish rebases them again, pushes, and finishes the issue. A conflict goes to the resolver and publishes again; a second conflict in one sweep waits for the next. A failed publish stops; one on the network defers. Nothing is saved as a patch.
  • A wiki-only change (flows) finishes through finish-wiki: it deletes the code branch, publishes the wiki commits, closes the issue with a comment listing them, and labels it agent:merged.

Finishing

  • PRs merge into the configured baseBranch, which may differ from the default branch. project-buddy's is integration, never main: production runs main, which only scripts/deploy.sh moves. Merging is never a release, and the merger never touches main.
  • GitHub closes an issue from "Closes #N" only when the PR merges into the default branch. So after a merge into another base, ops closes the issue itself with a comment naming the PR. An issue already closed isn't closed again.
  • Not enforced today: the second step of an expand-then-contract migration must not merge into integration until the first step is live in production.
  • close-parent closes a split parent once every sub-issue is closed and at least one merged (closed as completed, or labelled agent:merged), and names the open ones otherwise. It closes the parent first, then relabels it, then comments, so a failed close leaves no comment. The orchestrator runs the same code each tick (#378).
  • After a merge or split, the issue's worktree and branches are deleted, and the config's optional commands.teardown runs in the issue's run dir to free what setup made elsewhere.
  • Each sweep's cleanup removes a closed, unclaimed issue's worktree, branches and teardown resources once nothing local is unsaved, holding <clone>.lock for one issue at a time. It keeps an open or stopped issue, a live claim, uncommitted changes, commits not on GitHub, unpublished wiki commits, and the sweep's own issue. A failed cleanup is reported in the summary, never thrown.

Done looks like

  • Every requirement above has a test at one of the interfaces below, and mise //apps/issue-pipeline:check passes.
  • The role's stages and ops live in modules the other roles don't import, apart from the shared parts the index lists.
  • No model session runs with the merger's token. --print-boundary for merge and publish shows role merger, network api.github.com, and no model session gets GH_TOKEN.
  • A merge can only land verifiedSha, and only with no open blocking finding.
  • Proposed: a merge lands only a reviewed and approved diff, rebased onto the base's head and retested there. A conflict or failed retest always ends as a finding for the implementor, and a missing verdict as a return to its stage, never as repair work by the finalizer.
  • Proposed: finalize and publish launch no model session.
  • Every merged issue ends closed, labelled agent:merged, with its branch deleted and its wiki commits published or waiting at publish.
  • The wiki's merge-by-script, merge-order and wiki-docs sections describe the role as built.

Test interfaces

  • Step machine: next from stages/merge.ts and stages/publish.ts, with workflow-finish-test-kit.mjs (stateWith, contextWith, VERIFIED, replied).
  • Built sweep: runWorkflow with an issue claimed at merge or publish, scripting merge, wiki-sync, wiki-check, publish, finish-wiki, close-parent and cleanup replies, and args.mergeQueue. Assert with opsRequests, requestFor and releasedState.
  • Ops CLI:
    • merge: runOps against ops-test-kit.mjs (world, finishReplies, finishWrites, advanceBase) and the fake gh, asserting the exact gh writes and comments.
    • wiki ops: runOps against wiki-test-kit.mjs repos, checking the wiki remote's branches.
    • cleanup: runOps against ops-test-kit.mjs (world) worktrees.
  • Stage job: runJob with --stage merge and --print-boundary, asserting the merger boundary and that no pi session gets the merger's token.
  • Orchestrator: the merge queue's ranking in src/orchestrator/queue.ts with orchestrator-fixtures.mjs.

Test plan

Requirement Interface Case Today
Ready (new) runner, fake gh, local git an issue reviewed, accepted and security-passed at its head goes on to rebase, retest and merge; nothing else is required of it up front none yet
Up to date and retested (new) runner, local git, fake gh a branch behind the base is rebased, pushed and retested before the merge; one already current with green CI at verifiedSha is merged with no rebase or retest none yet
Conflict to the implementor (new) runner, local git a conflicting rebase is aborted, a conflict finding saved, the issue at fix, one marked note none yet
Retest failure to the implementor (new) runner, fake verify and CI a failing test, lint, typecheck, lockfile or CI check becomes a retest finding with its output, and the issue goes to fix none yet
Hands off to the final security review (new) runner, local git a clean rebase and retest save at final-security with mergedSha and mergedBaseSha none yet
Land when current (new) runner, fake gh at land, a PR at finalPassedSha with the base still at mergedBaseSha merges at that head none yet
Lost merge race returns to the merge (new) runner, fake gh at land, a moved base or a behind or conflict refusal saves at finalize; the rebase, retest and final security review run again; past maxSyncs it stops none yet
Each missing verdict returns (new) runner, fake gh, local git one row per table row: the item missing sends the issue to its stage with one marked note and finalizeNote saved; several missing return for the first in order none yet
Return limit (new) runner the return past maxFinalizeReturns, findings and missing verdicts counted together, stops with finalize-returns none yet
Not fixable (new) runner, fake gh a person-closed PR and an approval requirement stop with merge-refused and a how-to note none yet
No session (new) stage job finalize, land and publish launch no model session, a wiki conflict included none yet
Gate on findings step machine, built sweep an open finding stops before any launch; a security one names its category workflow-stages-merge-next.test.mjs, workflow-stages-finish-codes.test.mjs
Merge request step machine carries state, summary and merge queue workflow-stages-merge-next.test.mjs, workflow-merge-queue.test.mjs
Refusals ops CLI closed, moved head, conflict, behind (three ways), approval hint merge-op.test.mjs
Merged by hand ops CLI bookkeeping only merge-op.test.mjs
Squash and finish ops CLI squash at the verified head, verdict recorded, issue finished merge-op.test.mjs, finish-ops.test.mjs
Merge order ops CLI older PR at accept, security or merge holds; at sync or checks, stopped, newer, unreviewed and expired don't merge-op.test.mjs, merge-order-op.test.mjs
Merge queue ops CLI queue ahead at accept, security or merge holds; at sync or checks, closed, stopped, unready don't merge-queue-op.test.mjs
Stacked wait ops CLI waits for an open prerequisite; behind once it merged merge-stacked-op.test.mjs
Outcomes step machine, built sweep queued stays; behind resyncs once; second behind and conflict wait; network defers workflow-stages-merge-next.test.mjs, workflow-stages-merge-resync.test.mjs
Wiki at merge step machine rebase, check, merge; failed check stops; conflict to resolver; limits workflow-stages-merge-wiki-next.test.mjs, wiki-publish-ops.test.mjs
Publish step machine, built sweep, ops CLI clean publish closes; conflicts; budget; network; no patch workflow-stages-publish-next.test.mjs, workflow-stages-publish.test.mjs, finish-merged.test.mjs
Wiki only step machine, ops CLI finish-wiki publishes, closes and labels workflow-stages-flows-next.test.mjs, finish-wiki-op.test.mjs
Close parent ops CLI, step machine closes when all sub-issues closed; names open ones finish-ops.test.mjs, workflow-stages-finish-next.test.mjs
Cleanup ops CLI removes only what is safe cleanup-ops.test.mjs
Merger boundary stage job merger role, network, no model token job-roles.test.mjs, job-boundary.test.mjs
Role isolation (new) lint or unit the role's modules import nothing from the other roles' modules none yet
Kill anywhere (new) locate, runner, fake gh killed before and after each step and started again, the finalizer ends as an uninterrupted run: rebased and pushed once per base head, merged once, published once, closed once none yet

Open questions

  • Wiki conflict resolver: proposed (merge-only-when-ready): a wiki conflict returns to the spec writer, the wiki's writer, so the finalizer has no model session. Or should it be a finding for the implementor like a code conflict, which would mean giving the implementor wiki write access? Open: whether the spec writer's wiki-sync entry is worth its own state, or the spec stage simply runs again from locate.
  • Final security review scope: the branch's diff against the new base, or that plus the base's changes since the first pass that touch the same files?
  • Rebase or merge commit: "does the merge" is proposed as a rebase onto the base, as sync-base does today, then a squash merge. Merging the base into the branch would avoid force-pushes but adds merge commits that the squash discards anyway.
  • Migration order: should the finalizer refuse to merge the second step of an expand-then-contract migration until the first is live? Today it's only a rule people follow.
  • Whether closing parents and cleanup belong here or in the shared sweep code.
  • "Approved": proposed to mean the pipeline's verdicts (acceptance and security) at the head. Should it also, or instead, mean a person's approving review on GitHub?
  • maxFinalizeReturns: 3 per issue, or per head? And does a retest return count toward the implementor's ciFixRounds too?

Sources

The requirements above take in the wiki decisions this role follows, as of wiki f1b2944 (2026-10-16): merge-by-script, merge-order, stacked-branches, base-branch-may-differ-from-default, wiki-docs, wiki-flows, worktrees-and-branches, integration-branch (Decisions-Accounts). Once the specs are approved, prune-wiki-decisions deletes or trims the wiki sections the specs make redundant. From this split, not on the wiki yet: merge-only-when-ready, finalizer-role, final-security-review. From this split, on the wiki: sync-at-edges.

Clone this wiki locally