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