docs: merge path brief, a resolved merge decision lands as a gated local commit (#1610)
Filed at Mos's request after the PRD v1.1 merge. The slice is conductor-only: push and any stack-role landing authority stay out of scope. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# Merge path for approved work (2026-10-14)
|
||||
|
||||
Filed by Sage at Mos's request after the PRD v1.1 merge. Gitea #1610.
|
||||
|
||||
## A resolved merge decision lands as a gated local commit
|
||||
|
||||
### Problem
|
||||
|
||||
The Stack can take a task from the PM to a reviewed commit and stop
|
||||
there. On 2026-10-14:
|
||||
|
||||
- the coder pushed `work/vikunja-19-265` at 8ae5a149 with `push_working`.
|
||||
That tool writes only `refs/heads/work/vikunja-<project>-<task>` in the
|
||||
host's working repository (`<dataRoot>/working/<business>/<project>.git`,
|
||||
`packages/seat/src/working.mjs`);
|
||||
- the reviewer approved 8ae5a149 in verdict d3f97f9d;
|
||||
- Jason resolved decision f71c73c9, "squash-merge work/vikunja-19-265 into
|
||||
refactor", in the CLI.
|
||||
|
||||
No seat could act on that decision, so Mos routed it to Sage by hand.
|
||||
Sage made the squash patch, checked that its patch-id matched the
|
||||
reviewed diff, ran the full gate in a worktree, committed f7c8537b3,
|
||||
pushed it and re-pinned. Jason then told the PM from the Console that
|
||||
the work had merged. Every approved change would stall the same way.
|
||||
|
||||
`scripts/conductor-apply.sh` doesn't cover this. It takes a headless pi
|
||||
run record and a path allowlist, not a work ref, a verdict and a
|
||||
decision.
|
||||
|
||||
### Scope decision
|
||||
|
||||
Sage, as lead, scopes this slice to the conductor. The conductor gets
|
||||
one command that turns a resolved merge decision into a gated local
|
||||
commit, and the checks bind that commit to what the reviewer approved
|
||||
and the human decided. Pushing stays an explicit conductor act
|
||||
(invariant 8). Giving a stack role landing or push authority would widen
|
||||
role authority, a policy change that needs Jason. It's out of scope
|
||||
here and would be its own row if Jason wants it.
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: darkwing, after row 115.
|
||||
- Reviewer: filbert (decision 130: not a sandbox row; it reads the
|
||||
working repository and runs git on the host, but it adds nothing a
|
||||
session can reach).
|
||||
|
||||
### Files owned
|
||||
|
||||
- `scripts/land-work.sh` (new), or a `land` verb in an existing
|
||||
host-side CLI if that fits better. Record the choice and the reason.
|
||||
- `scripts/test-land-work.sh` (new), plus fixtures under `tests/` or the
|
||||
owning package's tests.
|
||||
- `docs/TOOLS.md`: one entry.
|
||||
- `docs/plans/CONDUCTOR.md`: a short "landing approved work" section.
|
||||
|
||||
Nothing under `packages/bus/src`, `packages/harness` or `roles/` changes.
|
||||
If the decision or verdict can't be read without a broker change, stop
|
||||
and tell Sage. Don't add a verb.
|
||||
|
||||
### What ships
|
||||
|
||||
1. `land-work <decision-id> [--base refactor] [--dry-run]`. It reads,
|
||||
and never writes, the decision and the verdict through the existing
|
||||
read-only path the Console and `mosaic` use. It never opens
|
||||
bus.sqlite for writing. It refuses with a named reason and exit 2
|
||||
unless all of these hold:
|
||||
- the decision is resolved, by a human, with the option that means
|
||||
merge. The decision names one work ref (`work/vikunja-<p>-<t>`) and
|
||||
one target branch (exactly `refactor`; `next` and `main` always
|
||||
refuse);
|
||||
- the latest `review.verdict` for that task is `approve` and names a
|
||||
commit C;
|
||||
- the work ref in the working repository is at C. A ref that has moved
|
||||
since the verdict refuses `ref-moved`;
|
||||
- no later decision on the same work ref withdraws or overrides it.
|
||||
2. It builds the squash in a detached worktree on the current base. The
|
||||
reviewed diff is `git diff $(git merge-base base C) C`. It refuses
|
||||
`base-conflict` if that diff doesn't apply cleanly to the base. After
|
||||
applying, it refuses `patch-drift` unless `git patch-id --stable` of
|
||||
the worktree's diff against the base equals that of the reviewed
|
||||
diff. If the base has moved since the review, the change must still
|
||||
be byte-for-byte what was reviewed.
|
||||
3. It refuses `root-file` for any added file at the repository root
|
||||
(invariant 1). Today's case: 010d30cbe added
|
||||
`src/parse-requirement-id.mjs` at the root.
|
||||
4. It runs the full gate in that worktree: every package suite and every
|
||||
`scripts/test-*.sh`, with pipefail and the output teed to a log. Any
|
||||
failure refuses `gate-failed` and names the log. Flaky tests
|
||||
#258/#261/#262/#263 get no special case; the conductor reruns.
|
||||
5. On a pass it commits once on the base, locally:
|
||||
- the subject comes from the decision title, or `--subject`;
|
||||
- the body cites the decision id, verdict id, work ref, C, the base
|
||||
and the patch-id, and ends with the conductor's attribution line.
|
||||
6. It prints, and doesn't send, the line for the PM:
|
||||
`<work ref> (<task>) is merged to <base> as <sha>; push pending.`
|
||||
Telling the PM through the bus is a follow-up. Today a human sends it
|
||||
from the Console.
|
||||
7. `--dry-run` runs every check and the gate, then prints the commit it
|
||||
would make. It writes no ref.
|
||||
8. The worktree, its `node_modules` link and any Docker network named
|
||||
for it are removed on every path, including refusals.
|
||||
|
||||
### Not in this row
|
||||
|
||||
- Push, merge to `next` or `main`, re-pin or restart. Those stay
|
||||
explicit acts with their existing authority. The re-pin is still Mos's
|
||||
window (decision 109).
|
||||
- Any stack role (PM, coder, reviewer) calling this.
|
||||
- Telling the PM through the bus.
|
||||
|
||||
### Acceptance
|
||||
|
||||
- Tests use fixture repositories and a fixture decision and verdict
|
||||
source. Each refusal in item 1 and every named reason in items 2-4 is
|
||||
exercised. Each test is shown to fail when the check under it is
|
||||
removed: one mutant per check, listed in the packet.
|
||||
- A replay of the 19/265 case against a scratch copy of the working
|
||||
repository at 8ae5a149 and base 1c9a4910d. The resulting tree matches
|
||||
f7c8537b3's tree, the commit is local only, and `--dry-run` writes no
|
||||
ref.
|
||||
- The 19/229 case (010d30cbe) refuses `root-file`.
|
||||
- Full gate green. Filbert approves on #1610 naming the candidate
|
||||
manifest. Sage reruns the gate at the landing target.
|
||||
Reference in New Issue
Block a user