Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
885 lines
45 KiB
Markdown
885 lines
45 KiB
Markdown
# Row 137 (#1631): agents land approved merges into the business working repository
|
|
|
|
Brief: `docs/plans/2026-10-15_q167-rows-r3.md`, section "Agents land approved
|
|
merges into the business working repository". I designed against blob
|
|
`f5ef29a2` (commit `c07c87fc2`, rev 1532). The row is now pinned to blob
|
|
`6805904555dc` (rev 1564). The section differs from `f5ef29a2` in one
|
|
paragraph, the read.mjs rule from sage-0505 and sage-0511, which this
|
|
candidate already follows (see "Arbiter path and `gated` refusal"
|
|
below). Owner Darkwing; reviewers Dewey and Filbert. Base `7a2987cf5` for
|
|
rounds 1 and 2; round 3 is rebased onto `63c82f1c4`, row 139's landing.
|
|
|
|
The design comes first, then "As built", the docs, the test map, and the
|
|
checks with the candidate.
|
|
|
|
## What lands where
|
|
|
|
A landing moves `refs/heads/main` in the business working repository,
|
|
`<dataRoot>/working/<business>/<project>.git`. Nothing writes the project
|
|
root, and canonical `refactor` is never a target. `scripts/land-work.sh`
|
|
keeps working as it does today.
|
|
|
|
## The seed (working.mjs)
|
|
|
|
Today `seed()` fetches the project root's HEAD as `+HEAD:refs/heads/main`,
|
|
forced, on every push and review. A landed main would be thrown away on the
|
|
next push. The new seed:
|
|
|
|
1. Fetch the root's HEAD as `+HEAD:refs/heads/root`, forced. A failure
|
|
refuses `root-unreadable`, as now.
|
|
2. If main doesn't exist, create it at root's tip (`update-ref main <tip> ""`,
|
|
which fails if another process created it first).
|
|
3. If main exists, isn't root's tip, and is an ancestor of root, fast-forward
|
|
it with `update-ref main <tip> <old>` (compare and swap).
|
|
4. Otherwise leave main alone. It is never forced.
|
|
|
|
push, review and land all seed under the repository lock. Working
|
|
repositories that exist today need no migration: the first seed after the
|
|
change creates `refs/heads/root`, and main is already an ancestor of it.
|
|
|
|
### The push and fetch behaviour change
|
|
|
|
The brief asks for this to be named. After the first landing, main and root
|
|
diverge until the owner brings main into the root.
|
|
|
|
- push's unrelated-history check still compares against main. A push whose
|
|
commit shares no history with main refuses `unrelated-history`. If the
|
|
owner rewrites the root's history, main keeps the old one, and pushes
|
|
built on the rewrite refuse until main is reconciled by hand.
|
|
- Coders clone from the project root, so a later task's branch doesn't have
|
|
earlier landings. Its merge base with main is then the root commit it
|
|
started from, and the reviewed diff `MB..C` is the task's own change, as
|
|
before. Landing it on a main that has moved on can refuse `base-conflict`
|
|
where the two touch the same lines.
|
|
- The reviewer's fetch now also has `refs/heads/root` in the repository, so
|
|
its `have` negotiation knows the root's commits. What the reviewer gets is
|
|
unchanged.
|
|
- When the owner merges main into the root, the next seed fast-forwards main
|
|
and the two agree again.
|
|
|
|
## Who may land (broker)
|
|
|
|
The run that holds an approved `git.merge.protected` decision asks for the
|
|
landing. Row 136 lets a later run of the same instance use it.
|
|
|
|
**Target rule.** The raise's `context.target` must equal the task ref named
|
|
by the question's work ref (`vikunja:<project>/<task>` for
|
|
`work/vikunja-<project>-<task>`). Then `#consumeAuthority` writes the record
|
|
with that task ref as its subject, so the landing sits on the task's trail
|
|
and `task-subject-required` can't fire. `refactor` never matches a task ref,
|
|
so canonical refactor can't be a target, and land-work's decisions (target
|
|
`refactor`) and agent landings (target a task ref) can't be used for each
|
|
other.
|
|
|
|
**Shared checks.** `readLanding` moves from `scripts/land-work/read.mjs` to
|
|
`packages/bus/src/landing.mjs` and takes `{ into }`:
|
|
|
|
- `into: 'refactor'` (land-work): the raise targets `refactor`, and read.mjs
|
|
still checks its `--base` is `refactor`.
|
|
- `into: 'main'` (agent landing): the raise targets the task ref.
|
|
- Anything else refuses `target-branch`.
|
|
|
|
It runs over a small query adapter (`get`, `all`), so read.mjs passes its
|
|
read-only `DatabaseSync` and the broker passes its Store. read.mjs keeps its
|
|
CLI and its own schema check.
|
|
|
|
The human check (`res.by === business.human`, `via === 'cli'`, one human
|
|
answer with no role or run) is in the shared `readLanding`. Under b2a2d741c
|
|
(sage-0517) land-work's check never changes: canonical `refactor` stays the
|
|
lead's landing. Only the host land op's call accepts a decision its arbiter
|
|
resolved (REQ-DEC-2). Row 139 landed first, so round 3 adds that with the
|
|
`arbiter` option only the broker passes, and the test that an
|
|
arbiter-resolved merge decision raised by merge-gate lands through the host
|
|
op. See "Round 3".
|
|
|
|
**New broker methods** (trusted, through the launch socket ops):
|
|
|
|
- `checkLand(cap, { task_ref, decision })`: the session's agent and task,
|
|
`#checkAuthority(s, 'git.merge.protected', { decision, target: task_ref })`,
|
|
decision not already consumed, then `readLanding` with `into: 'main'`. The
|
|
project is the task's latest push; no push refuses `ref-moved`. A decision
|
|
that names another project refuses `work-ref`.
|
|
- `recordLand(cap, land)`: reruns the checks inside the transaction (the
|
|
verdict is still the latest and approving, nothing supersedes the
|
|
decision), then `#consumeAuthority` writes the `action.allowed` with the
|
|
landed commit, the old main, the reviewed commit, the verdict and the
|
|
patch-id. The decision is spent.
|
|
- `recordEvent` reserves `action.allowed` for `git.merge.protected`, as it
|
|
already does for `git.push.working`, so no run can write a fake landing.
|
|
- `landHeld({ business, role, thread })`: whether that role and thread hold
|
|
a resolved, unconsumed merge decision with the approval option. The
|
|
launcher asks it at launch.
|
|
|
|
### REQ-LAND-1 and the gated path (sage-0505)
|
|
|
|
PRD 1.5 round 2 (row 135) says only a role whose role file grants
|
|
`git.merge.protected` as a cross-role action may land, and an approval of a
|
|
gated-class merge decision never lands. Row 139 takes `git.merge.protected`
|
|
out of GATED_ONLY.
|
|
|
|
This row takes the current gated path. A test role file can't hold the
|
|
grant: `role.mjs` refuses any GATED_ONLY action under `withinRole` or
|
|
`crossRole`, and the broker's `GATED` set classifies `git.merge.protected` as
|
|
`gated` before it reads the role file. So `checkLand` goes through
|
|
`#checkAuthority` with class `gated`, and the tests raise gated decisions.
|
|
|
|
The refusal of class `gated` belongs in `checkLand` and `recordLand`, right
|
|
after the authority check returns. The brief (c07c87fc2) says whichever of
|
|
rows 137 and 139 lands second carries it with its test, and Sage checks
|
|
that at each landing. Row 139 landed first as `63c82f1c4`, so round 3 adds
|
|
the refusal, the arbiter path and both tests, and `landHeld` asks for a
|
|
cross-role decision so the launcher binds the op only for a role that could
|
|
land. The paragraphs above describe the rounds 1 and 2 base; "Round 3" has
|
|
what changed.
|
|
|
|
## The land op (git socket)
|
|
|
|
The socket takes a third op, `land`, with `{ cap, task_ref, decision }`.
|
|
The launcher binds the git socket for a run whose role and thread hold a
|
|
merge decision (`landHeld`), as well as for the push and verdict roles it
|
|
binds today. The bundle shows a `land_work` tool only then. The broker
|
|
rechecks everything at request time, so binding grants nothing by itself.
|
|
A decision resolved while a run is going can only be landed from the next
|
|
run, because the bundle is fixed at launch.
|
|
|
|
### Asynchronous outcome (a choice, flagged for Sage)
|
|
|
|
A full gate takes longer than every timeout between the agent and the host:
|
|
the tool call's 60 s default (`callTool`), the runner's 900 s turn and the
|
|
git client's 300 s socket timeout. The op can't wait for it.
|
|
|
|
So `land` answers as soon as the gate has started:
|
|
`{ landing: "started", base, reviewed, patch_id }`. Every check that can
|
|
refuse before the gate answers synchronously as a refusal, as push does.
|
|
The host finishes in the background. The outcome is two records:
|
|
|
|
- the `action.allowed` (landed) or `action.refused` (`gate-failed`,
|
|
`base-moved`, `host-closing`, ...) on the task's trail, and
|
|
- a RESULT message from the bus (`_bus`, run `bus`, as `#notice` sends) to
|
|
the role in the decision's thread, saying what landed or why not. RESULT
|
|
makes the runner take a turn and send no automatic reply.
|
|
|
|
That message is a broker change beyond "the authority check for the new op
|
|
only". The alternative is a run that polls the trail, which needs a read op
|
|
agents don't have. I took the message. If Sage wants the narrower scope, the
|
|
fallback is the records alone and the role learns the outcome from its next
|
|
launch.
|
|
|
|
The git socket runs one landing at a time. A second request while one is
|
|
running refuses `land-busy`. Two full gates at once would also fight over
|
|
Docker.
|
|
|
|
If the host closes during a gate, the gate's process group is killed and the
|
|
landing is refused `host-closing`; nothing moves. If the host dies outright,
|
|
nothing is recorded and the decision is still unspent, so the run can ask
|
|
again.
|
|
|
|
### The steps
|
|
|
|
In order, matching land-work's refusal order and codes:
|
|
|
|
1. Broker `checkLand`: `decision-not-found`, `not-a-merge`, `not-resolved`,
|
|
`not-human`, `not-merge-option`, `work-ref`, `target-branch`,
|
|
`superseded`, `no-verdict`, `not-approved`, plus `decision-consumed`.
|
|
2. Under the repository lock: seed; the work ref's tip must be the
|
|
verdict's commit (`ref-moved`); OLD is main; `MB = merge-base(OLD, C)`,
|
|
none refuses `base-conflict`; the reviewed patch is `MB..C` in land-work's
|
|
fixed diff format, empty refuses `base-conflict`; its patch-id is taken.
|
|
3. Root file: an added top-level file, an added top-level directory OLD
|
|
doesn't have, or anything under `src/` or `v1/` refuses `root-file`.
|
|
4. The squash, in a temporary index on OLD's tree: the patch is applied
|
|
with `apply --cached`; failure refuses `base-conflict`; the patch-id of
|
|
`OLD..tree` must equal the reviewed one (`patch-drift`). The commit is
|
|
made with `commit-tree`, no ref, author and committer "Mosaic bus host"
|
|
`<[email protected]>`, and land-work's message with
|
|
`Applied-under: git socket land (bus host)`. The commit is fixed before
|
|
the gate, so nothing the gate does can change what lands.
|
|
5. Gate check: the project root is a mosaic-stack checkout (below) with a
|
|
`node_modules`; otherwise `gate-missing`. This is the last synchronous
|
|
refusal; the op answers "started" here.
|
|
6. The gate, outside the lock, in a detached worktree of the new commit
|
|
with the root's `node_modules` linked. A failing step refuses
|
|
`gate-failed` with the step and its log.
|
|
7. Under the lock: `update-ref main NEW OLD`; failure refuses `base-moved`.
|
|
Then `recordLand`. If recording throws, main goes back with
|
|
`update-ref main OLD NEW`; if that fails too, the refusal is
|
|
`rollback-failed` and names both commits.
|
|
|
|
The worktree, its `node_modules` link and the Docker network
|
|
`<name>_default` are removed on every path. Logs stay under
|
|
`<dataRoot>/bus-host/land/<id>/`.
|
|
|
|
### The exact mosaic-stack check
|
|
|
|
`git -C <root> merge-base --is-ancestor c2365ae5190aee751bfbd0a3d47105a706adb5e7 HEAD`.
|
|
c2365ae5 is the new foundation's root commit ("chore: baseline container POC
|
|
and atomic foundation plan", 2026-09-02, tag `poc-container-hello-v0`). The
|
|
repository has three root commits (c2365ae5, e7f338e3, ecde7443); a project
|
|
without c2365ae5 in its history isn't this one. The root must also have a
|
|
`node_modules` directory, which the gate links.
|
|
|
|
### The gate
|
|
|
|
One definition, in `packages/seat/src/landing.mjs`, used by the host and by
|
|
land-work.sh: every `packages/<p>/tests/*.test.mjs` suite except queue's
|
|
(`scripts/test-queue.sh` runs those), then every `scripts/test-*.sh`, each
|
|
step logged to `<step>.log` and summarised in `gate.log` with land-work's
|
|
line format, ending `GATE_PASSED`. land-work.sh calls it through node and
|
|
keeps its own worktree, refusal messages and Docker cleanup. The root-file
|
|
rule moves to the same module.
|
|
|
|
The gate runs reviewed, agent-authored code on the host, outside a sandbox,
|
|
with the host's environment. land-work.sh does the same today under the
|
|
conductor. Here a human-resolved decision and an approving review are what
|
|
authorize it.
|
|
|
|
## Harness
|
|
|
|
- `land_work` tool, verb `working.land`, in `tools.mjs`, shown when the
|
|
launcher says the session may land.
|
|
- `landWork()` client in `git-tools.mjs`.
|
|
- runner local handler for `working.land` over the session's git socket.
|
|
|
|
## Out of scope
|
|
|
|
Routing and GATED_ONLY (row 139), the merge-gate role (row 149), any push
|
|
to a remote, force-push or history rewrite.
|
|
|
|
## As built: where the candidate differs from the design above
|
|
|
|
- **The squash is made in a worktree, not a temporary index.** prepareLand
|
|
adds a detached worktree of the bare repository at main under
|
|
`bus-host/land/<id>/<id>` and applies the reviewed patch there with
|
|
`git apply --index`. The applied diff (`--cached` against OLD) must have
|
|
the reviewed patch-id. `write-tree`, `commit-tree` with OLD as the one
|
|
parent, then `reset --soft` to the squash, so the gate runs in the tree
|
|
that lands. The worktree goes on every refusal and at the end
|
|
(`dropLand`, which removes the `node_modules` symlink first).
|
|
- **Patches are written by git.** Both diffs go through
|
|
`git diff --output=<file>` with fixed prefixes, no textconv and no
|
|
external diff, and patch-id reads the file. A file that isn't UTF-8
|
|
keeps its patch-id.
|
|
- **Order.** The root-file check runs before the worktree is added, so a
|
|
root-file refusal never touches the object store beyond reading.
|
|
gate-missing comes after prepareLand, so the squash exists when the
|
|
host answers `started`.
|
|
- **The `started` answer carries `commit`**, the squash, besides `base`,
|
|
`reviewed` and `patch_id`. An agent can say what will land.
|
|
- **Tickets.** checkLand returns a ticket the host passes to recordLand
|
|
and landRefused. A ticket outlives the run that asked, since the gate
|
|
may end after the run did. An unknown ticket refuses `land-unknown`.
|
|
recordLand checks authority with `#authorityFor` (the holder check is
|
|
left out for that reason) and reruns the landing checks in the
|
|
transaction; it writes the `action.allowed` itself rather than through
|
|
`#consumeAuthority`, which would ask for the claim again.
|
|
- **`no-subject`.** land-work refuses a landing with no subject and no
|
|
`--subject`. An agent has no `--subject`, so checkLand refuses a merge
|
|
option with no first line `no-subject`.
|
|
- **`decision-consumed`** is checked in checkLand and again in recordLand.
|
|
- **The gate's environment.** The host runs the gate with `gateEnv`: PATH,
|
|
HOME, USER, LOGNAME, SHELL, LANG, LC_ALL, TZ, TERM, TMPDIR,
|
|
XDG_RUNTIME_DIR and DOCKER_HOST only. This replaces "with the host's
|
|
environment" in the gate section. land-work.sh keeps the conductor's.
|
|
- **isMosaicStack is async** and runs git through `runGit` with only the
|
|
environment's PATH, hooks at /dev/null and fsmonitor off.
|
|
- **The gate is injectable.** `createGitSocket({gate})` takes `LAND_GATE`'s
|
|
shape, `{classify, run, dropNetwork}`, so the socket tests run a fake
|
|
gate. A test checks that `LAND_GATE` holds the real `isMosaicStack` and
|
|
`runGate`, and the refusals test runs the real classifier on a fixture
|
|
project for gate-missing.
|
|
- **Harness.** `land_work` has no clone requirement and sits between
|
|
`push_working` and `request_review`. The prompt line (round 3 wording):
|
|
"Landing: once your merge decision is approved, land_work it with the task. The
|
|
reply only says the landing started; the gate's outcome comes later as a
|
|
RESULT from the bus. Don't land again while one is running." The
|
|
launcher asks `landHeld` with the run's role instance and thread and
|
|
binds the git socket when it answers true.
|
|
- **The runner's `working.land` route has no test of its own.** Neither do
|
|
`working.push` and `working.fetch`. `landWork` is tested in
|
|
git-tools.test.mjs against a fake host socket, and the launcher test
|
|
checks the bundle and `session.json`.
|
|
- **Arbiter path and `gated` refusal** (sage-0511, sage-0517, sage-0565):
|
|
rounds 1 and 2 left both out, since row 139 hadn't landed, and both
|
|
callers of `readLanding` required a human/cli resolution. Row 139 landed
|
|
first, so round 3 adds them (see "Round 3"). land-work's check in
|
|
`scripts/land-work/read.mjs` is unchanged in effect in every round.
|
|
- **The raise is found by its body, not its subject.** read.mjs looked
|
|
for the decision.raise `action.allowed` with `subject` equal to the
|
|
decision id. The broker files a raise under its task ref when it has
|
|
one (`a.task_ref ?? target-if-task-ref ?? id`), so every decision raised
|
|
for a task, which is every one the host op lands, came back
|
|
decision-not-found. The first gate run caught it (six land.test
|
|
failures). `readLanding` now matches the business, the kind, the
|
|
operation and `body.decision`, as the broker's own raise lookups do.
|
|
For land-work this changes one thing: a refactor decision raised with a
|
|
`task_ref` used to refuse decision-not-found and now reaches the checks
|
|
after it, where a `task_ref` that disagrees with the question refuses
|
|
work-ref. That branch existed in read.mjs but no real raise could reach
|
|
it; the fixture in test-land-work.sh files every raise under the
|
|
decision id, so its cases are unchanged. The human-only check is
|
|
untouched. `recordEvent` refuses an `action.allowed` whose action is
|
|
`git.merge.protected`, so an adapter can't add a second raise that
|
|
passes.
|
|
- **landing.mjs is a runner file.** The broker imports it, so it joins
|
|
`RUNNER_FILES` in packages/seat/src/sandbox.mjs, and the bus import
|
|
closure test (project-runtime.test.mjs, row 138) lists the new import
|
|
and checks that landing.mjs imports nothing.
|
|
- **thread-launch.test.mjs's host answers landHeld.** Its in-process host
|
|
lists the launch ops it serves, and every launch now asks `landHeld`, so
|
|
five of its tests failed with "invalid-host-request" until the op was
|
|
added beside `threadIdle`.
|
|
|
|
## Docs
|
|
|
|
Only the landing paragraphs the brief names:
|
|
|
|
- `packages/seat/README.md`: push step 2 and the review paragraph now
|
|
describe the root seed; "No other ref moves" names the seed's refs; a new
|
|
"Landing on main (#1631)" subsection covers prepareLand, finishLand,
|
|
dropLand and `src/landing.mjs`; the sandbox bind list adds `landHeld`.
|
|
- `packages/cli/README.md`, "Git socket": the bind rule, a `land` row in
|
|
the op table, `checkLand` in the check order, a landing paragraph (steps,
|
|
land-busy, landRefused, the RESULT outcome, logs, `LAND_GATE`), and
|
|
close() stopping a gate.
|
|
- `packages/bus/README.md`: the four row 137 ops and the ticket.
|
|
- `packages/harness/README.md`: the `land_work` paragraph, runner step 3,
|
|
the git socket bind rule.
|
|
- `docs/TOOLS.md`: one paragraph after land-work's on the shared modules.
|
|
|
|
Each passes `tools/unslop-hook/unslop-check.js`.
|
|
|
|
## Tests, against the brief's list
|
|
|
|
| Brief | Test |
|
|
|---|---|
|
|
| A reseed moves root and leaves a landed main | working.test "a reseed moves root but leaves a landed main, and main forwards once root contains it" |
|
|
| main fast-forwards to root when it's an ancestor, stays put when it isn't | the same test, and "after a landing, a push built on the root still lands and main stays where the landing put it" |
|
|
| push's unrelated-history check against main | working.test "a branch with no history in common with main refuses unrelated-history", where main and root are the same commit, and (round 2) "once the root shares no history with a landed main, a push is checked against main, not the root", where they share nothing: a push built on main lands and one built on the root refuses |
|
|
| A mosaic-stack checkout runs the gate | landing.test "isMosaicStack: ... node_modules; nothing else" (true on a child of the root commit) and the runGate tests; git-socket.test "land: the squash runs the gate in its own worktree, then main moves and the broker records it" with a classifier that says yes |
|
|
| Another project refuses gate-missing | git-socket.test "land: refusals before the gate ..." with the real `isMosaicStack` on the fixture project |
|
|
| Each land-work refusal code | On the shared module: `scripts/test-land-work.sh` runs every `readLanding` code through land-work (`into: 'refactor'`), unchanged from row 116. On the host side: land.test "checkLand refuses on the decision and the review" (the broker's decision-not-found, decision-not-approved, decision-mismatch, no-verdict, ref-moved), "checkLand refuses with land-work's codes past the authority check..." (work-ref twice, target-branch, superseded, not-approved, and a `refactor` decision refused decision-mismatch), "no-subject", and decision-consumed in the first test; working.test ref-moved, base-conflict, root-file, base-moved, rollback-failed, and (round 2) patch-drift, with a git first on the PATH that adds a line to what `apply --index` staged, as test-land-work.sh does; git-socket.test gate-missing, gate-failed, land-busy, host-closing, unknown-project and a broker refusal at record |
|
|
|
|
On the host side, `#checkAuthority` runs first, so `not-a-merge`,
|
|
`not-resolved` and `not-merge-option` reach the role as the broker's
|
|
`decision-mismatch` or `decision-not-approved`. In rounds 1 and 2,
|
|
`not-human` couldn't happen there, since only the human resolved a gated
|
|
decision. In round 3 it still can't reach checkLand: a gated decision
|
|
refuses `not-authorized` first, and the broker resolves a cross-role one
|
|
only through its route, the arbiter or the human. The round 3 test checks
|
|
`not-human` on `readLanding` directly instead (see "Round 3").
|
|
|
|
Also new: landing.test (rootHits, gateEnv, gateSteps, runGate pass, fail
|
|
and abort, the CLI), land.test (ticket, landRefused, landHeld, the
|
|
reserved event), the launcher test for `landHeld` binding, and the
|
|
harness tests for the tool, the prompt line and `landWork`.
|
|
`scripts/test-land-work.sh` is unchanged and runs land-work through the
|
|
shared gate and root-file CLI.
|
|
|
|
## Candidate
|
|
|
|
`r1/build.patch` against BASE `7a2987cf5`
|
|
(sha256 `49d5048f6bb53e1b546a6b2dbdec14ef4a9bfe5087f20e218018a62481266611`).
|
|
`r1/candidate-manifest.sha256` lists the 31 files: 27 changed and 4 new.
|
|
The new ones are packages/bus/src/landing.mjs, packages/bus/tests/land.test.mjs,
|
|
packages/seat/src/landing.mjs and packages/seat/tests/landing.test.mjs.
|
|
I applied the patch to a fresh worktree at BASE, `sha256sum -c` passed on
|
|
every line, and I removed that worktree. Canonical HEAD was `b71268b8e` at
|
|
18:56Z. `git diff 7a2987cf5 b71268b8e` on the 27 changed paths is empty, and
|
|
none of the 4 new paths exists at HEAD.
|
|
|
|
## Checks
|
|
|
|
The slot was mine from 18:37Z (sage-0519) until 18:55:30Z. `r1/gate.sh`
|
|
runs `node --test tests/*.test.mjs` in every package with a tests/
|
|
directory except queue (test-queue.sh runs that suite), then every
|
|
`scripts/test-*.sh`. The suites run one after another, each teed to its own
|
|
log, and the script stops at the first failure. TMPDIR was
|
|
`~/darkwing-scratch/r137/tmp`. cli's launcher.test ran on its own under the
|
|
session's inherited TMPDIR, because the bwrap runner fails "did not start"
|
|
under an override. DOCKER_HOST was unset, Docker 29.7.2 was up, and
|
|
COMPOSE_PROJECT_NAME was `darkwing-r137`. An EXIT trap removes
|
|
`darkwing-r137_default` only when it has 0 containers.
|
|
|
|
Round 5, the candidate, ran from 18:49:43 to 18:55:25 UTC and ended
|
|
GATE_PASSED (`r1/gate-run.log`, step logs in `r1/gate/`):
|
|
|
|
| Step | Pass | Fail |
|
|
|---|---|---|
|
|
| bus | 302 | 0 |
|
|
| business | 109 | 0 |
|
|
| cli (all but launcher) | 202 | 0 |
|
|
| cli launcher.test, inherited TMPDIR | 19 | 0 |
|
|
| control-board | 124 | 0 |
|
|
| conversation | 182 | 0 |
|
|
| discord | 183 | 0 |
|
|
| harness | 108 | 0 |
|
|
| ledger | 86 | 0 |
|
|
| mosaic | 69 | 0 |
|
|
| plugins | 28 | 0 |
|
|
| project | 30 | 0 |
|
|
| runs | 43 | 0 |
|
|
| seat | 148 | 0 |
|
|
| tasks | 97 | 0 |
|
|
| webui | 113 | 0 |
|
|
| test-auth | 15 | 0 |
|
|
| test-conductor | 17 | 0 |
|
|
| test-config | 24 | 0 |
|
|
| test-discord | 78 | 0 |
|
|
| test-extension-package | 18 | 0 |
|
|
| test-foundation | 44 | 0 |
|
|
| test-land-work | 84 | 0 |
|
|
| test-queue | 156 node, 28 suite | 0 |
|
|
| test-release | 14 | 0 |
|
|
| test-task | 98 | 0 |
|
|
|
|
The trap printed "network darkwing-r137_default removed". Afterwards no
|
|
Docker network created today remained, no container carried the
|
|
darkwing-r137 project label, and no node or docker process of mine had a
|
|
cwd under my r137 scratch.
|
|
|
|
Rounds 1 to 4 each stopped on a fault in the candidate or its tests. None
|
|
was load or timing (sage-0527). Each fix was rerun singly before the next
|
|
round. The round summaries, the failing step logs and the reruns I logged
|
|
are in `r1/earlier/`. The git-socket rerun in round 2 has no saved log;
|
|
round 5 covers it.
|
|
|
|
1. Round 1, bus, 295 pass, 7 fail. Six land.test tests refused
|
|
decision-not-found, the raise lookup described in "As built". The
|
|
project-runtime import closure test didn't list the broker's new import.
|
|
After both fixes, land.test and project-runtime.test passed 11 of 11.
|
|
2. Round 2, cli, 196 pass, 6 fail. git-socket.test expected a newline
|
|
after the Applied-under line, which `w.git` trims, so the test now
|
|
anchors that line at the end of the message. Five thread-launch tests
|
|
failed because the test host lacked `landHeld`. A rerun under the
|
|
inherited TMPDIR failed the same five (`r2-thread-launch-inherited-tmpdir.log`),
|
|
which ruled out TMPDIR. With the op added, the file passed 8 of 8.
|
|
This round also changed gate.sh: its launcher line used `env -u TMPDIR`,
|
|
and it now passes the inherited TMPDIR.
|
|
3. Round 3, cli launcher.test, 18 pass, 1 fail. The `landHeld` test's
|
|
wrapper used `Object.assign` on a host whose `op` is a read-only
|
|
inherited property and threw a TypeError. It now uses `Object.create`.
|
|
19 of 19 passed, 0 skipped.
|
|
4. Round 4, seat, 146 pass, 2 fail. Two runGate tests named their "must
|
|
not run" script test-three.sh, which sorts before test-two.sh, so it
|
|
ran first. It is now test-z.sh, with a comment saying why. landing.test
|
|
passed 8 of 8.
|
|
|
|
Researcher's row 148 gate ran from /mnt/storage/scratch/researcher/q148
|
|
during my slot (sage-0527). I didn't look inside that directory. Each
|
|
failure above traces to a line in the candidate or its tests, so I
|
|
changed code rather than rerunning for a flake.
|
|
|
|
Not run: mutants. Builder rounds may be targeted (decision 136), and the
|
|
reviewers have the gate above.
|
|
|
|
## Round 2 (Filbert's comment 28782, sage-0541)
|
|
|
|
Filbert asked for two tests the brief requires. Both are in
|
|
packages/seat/tests/working.test.mjs, the only file that changed.
|
|
|
|
- B1, "a landing whose applied diff isn't the reviewed one refuses
|
|
patch-drift and leaves no worktree". A `git` first on the PATH runs the
|
|
real git, then adds a line to the README that `apply --index` staged, as
|
|
scripts/test-land-work.sh does for land-work. prepareLand refuses
|
|
`patch-drift`, main stays put and no worktree is left. The same landing
|
|
with the real git then prepares.
|
|
- B2, "once the root shares no history with a landed main, a push is
|
|
checked against main, not the root". After a landing, the owner starts
|
|
the root over on an orphan branch. The seed moves root to it and leaves
|
|
main. A push built on main lands, and a push built on the new root
|
|
refuses `unrelated-history`.
|
|
|
|
Also fixed: the stale "Row 139 isn't built yet" in "Arbiter path and
|
|
`gated` refusal left out". The test table above names both tests.
|
|
|
|
### Candidate, round 2
|
|
|
|
`r2/build.patch` against BASE `7a2987cf5`
|
|
(sha256 `383fc8329ef340f804de68f981146593bf06e597481991fa9889649e29cb8691`).
|
|
`r2/candidate-manifest.sha256` (sha256
|
|
`3311b46a61e525a1897190bbce05de15a850843d00fce4497ef3d4828434c09f`) lists
|
|
the same 31 paths as r1. Only the working.test.mjs line differs. I applied
|
|
the patch to a fresh worktree at BASE: `sha256sum -c` passed on every line
|
|
and `git diff --cached --check` was clean (31 files, +2043 -255). Canonical
|
|
HEAD was `96fd8593e`, and `git diff 7a2987cf5 96fd8593e` on the 31 paths is
|
|
empty.
|
|
|
|
### Checks, round 2
|
|
|
|
The slot was mine from 19:18Z (sage-0547). This round was targeted
|
|
(decision 136). `r2/round.sh` ran each file singly with TMPDIR
|
|
`~/darkwing-scratch/r137/tmp` and no Docker, then probed each check the way
|
|
Filbert did. A probe edits working.mjs in place, runs working.test, and
|
|
restores the file. The script then checks working.mjs's sha256 against the
|
|
one it started with, and it must fail on exactly the new test. Output is in
|
|
`r2/gate-run.log`, step logs in `r2/gate/`.
|
|
|
|
| Step | Pass | Fail |
|
|
|---|---|---|
|
|
| seat working.test, candidate | 26 | 0 |
|
|
| cli git-socket.test, candidate | 23 | 0 |
|
|
| probe B1: line 348 `if (false) refuse("patch-drift", ...)` | 25 | 1 |
|
|
| probe B2: line 224 merge-base against `refs/heads/root` | 25 | 1 |
|
|
| seat, whole suite, restored candidate | 150 | 0 |
|
|
|
|
- Probe B1 failed only "a landing whose applied diff isn't the reviewed one
|
|
refuses patch-drift and leaves no worktree", with "Missing expected
|
|
rejection".
|
|
- Probe B2 failed only "once the root shares no history with a landed main,
|
|
a push is checked against main, not the root". The push built on main
|
|
refused `unrelated-history`.
|
|
|
|
After each probe working.mjs was back at
|
|
`0d332d73596e94d2114751c1924c4f8463c387aba9ffdb0924d61f6228e1c463`, its
|
|
manifest hash. Afterwards `sha256sum -c` on the r2 manifest passed in my
|
|
worktree, no `.orig` file was left, and no node process of mine was
|
|
running.
|
|
|
|
Not run in this round: the other package suites, the scripts/test-*.sh and
|
|
mutants. Round 2 changes one test file. The r1 gate above covers the other
|
|
30 paths, which are unchanged.
|
|
|
|
## Round 3 (sage-0565): rebased onto row 139, with both carry items
|
|
|
|
Row 139 landed first as `63c82f1c4`, so this row carries the two items the
|
|
brief gives the second lander (brief blob `6805904555dc`). The r2 patch
|
|
applied to `63c82f1c4` without a conflict. B1 and B2 are unchanged.
|
|
|
|
### What row 139 changed under this row
|
|
|
|
In broker.mjs, `GATED` is now the hard-wait set, and vocabulary.mjs's
|
|
`GATED_ONLY` matches it. `git.merge.protected` isn't in it. A role file
|
|
may grant a merge cross-role, and a business that grants a hard wait at
|
|
either class refuses `invalid-authority` (REQ-WAIT-2). So a hard wait is
|
|
never a cross-role decision. decision.raise routes:
|
|
|
|
- cross-role to `arbiters[domain]`, or to the human when the arbiter is
|
|
the raiser's own role;
|
|
- gated and cited to the arbiter;
|
|
- gated and uncited, or any hard wait, to the human.
|
|
|
|
An agent's resolution writes `via: 'broker'` with its run as `by`. A
|
|
human-routed decision resolved by an agent refuses `human-required`.
|
|
|
|
### (a) REQ-LAND-1: a gated merge decision never lands
|
|
|
|
`#landFacts` starts with `if (cls === 'gated') fail('not-authorized')`.
|
|
checkLand passes the class `#checkAuthority` returned, and recordLand
|
|
passes the one from `#authorityFor`, so both run the same line. The class
|
|
can't change between the two: `#authorityFor` requires the decision's class
|
|
to be the one the role holds. The refusal comes before
|
|
`decision-consumed` and `readLanding`, so who resolved a gated decision
|
|
doesn't matter.
|
|
|
|
### (b) REQ-DEC-2: the host op accepts an arbiter-resolved merge
|
|
|
|
`readLanding` takes `{ into, arbiter = false }`. It accepts a resolution
|
|
when one of these holds:
|
|
|
|
- the human resolved it through the CLI with one human answer, the check
|
|
rounds 1 and 2 already had; or
|
|
- `arbiter` is true, the decision is `cross-role`, its `route_to` isn't
|
|
`human`, the resolution came `via: 'broker'`, and there is no human
|
|
answer.
|
|
|
|
Only the broker's `#landFacts` passes `arbiter: true`. read.mjs is
|
|
unchanged, so land-work passes nothing and stays human-only (item 4). The
|
|
route check holds because the broker lets only a run of the `route_to` role
|
|
resolve an agent-routed decision.
|
|
|
|
### Item 5: no hard wait through the arbiter path
|
|
|
|
A hard wait can't reach the arbiter path, for three reasons:
|
|
|
|
1. `readLanding` refuses `not-a-merge` for any action but
|
|
`git.merge.protected`, which isn't a hard wait.
|
|
2. The arbiter path takes only `cross-role`, and REQ-WAIT-2 keeps a hard
|
|
wait out of that class.
|
|
3. A gated decision refuses `not-authorized` before `readLanding` runs.
|
|
|
|
### landHeld and the prompt
|
|
|
|
`landHeld` now skips any decision that isn't `cross-role`, so the launcher
|
|
binds the land op only for a role that could land. The bundle's prompt
|
|
line no longer says the human approved the merge. It reads "Landing: once
|
|
your merge decision is approved, land_work it with the task." The harness
|
|
bundle test and the cli launcher test assert it.
|
|
|
|
### Tests (packages/bus/tests/land.test.mjs)
|
|
|
|
The world gains a `merge-gate` role granted `git.merge.protected`
|
|
cross-role, with pm as the technical and delivery arbiter. The landing
|
|
tests now use it instead of a coder raising a gated decision.
|
|
|
|
- **(b)**, "REQ-DEC-2: a merge-gate raise its arbiter resolved lands
|
|
through the host op: ...". The raise routes `cross-role` to pm, and pm
|
|
resolves it (`by: 'pm-run'`, `via: 'broker'`). `readLanding` without
|
|
`arbiter` refuses `not-human` on it. checkLand answers the facts with
|
|
`by: 'pm-run'`, and recordLand writes the merge event with class
|
|
`cross-role` and spends the decision once.
|
|
- **(a)**, "REQ-LAND-1: a gated merge decision refuses not-authorized,
|
|
cited and approved by the arbiter or uncited and approved by the human".
|
|
On row 139's spec fixture, the coder pushes, gets an approving review and
|
|
raises a merge citing `spec/north-star.md` at the fixture commit. The
|
|
raise routes `gated` to pm, and pm approves. checkLand refuses
|
|
`not-authorized`, `readLanding` with `arbiter: true` refuses `not-human`,
|
|
and landHeld is false. Then, uncited: the raise routes `gated` to the
|
|
human, the human approves, and checkLand still refuses `not-authorized`.
|
|
Nothing merges.
|
|
- New: "a cross-role merge that routes to the human, because merge-gate is
|
|
its own arbiter, lands on the human's CLI answer". pm's resolve refuses
|
|
`human-required`; the human's lands with `by` set to the human.
|
|
- landHeld's test adds a coder's gated decision approved by the human,
|
|
for which landHeld is false.
|
|
|
|
`not-human` still can't reach checkLand (see the test table above). A
|
|
comment above the land-work codes test says so.
|
|
|
|
### Docs, round 3
|
|
|
|
- `packages/bus/README.md`: land-work accepts only a human CLI resolution;
|
|
the broker's `arbiter: true` also accepts a cross-role decision its
|
|
arbiter resolved; `not-authorized` for gated (REQ-LAND-1); checkLand and
|
|
landHeld name `cross-role`.
|
|
- `packages/cli/README.md`: a role granted the merge cross-role lands once
|
|
its arbiter or the human approved the decision.
|
|
- `packages/cli/tests/git-socket.test.mjs`: one comment. Its broker is a
|
|
stand-in, and no logic changed.
|
|
|
|
### Candidate, round 3
|
|
|
|
`r3/build.patch` against BASE `63c82f1c4`
|
|
(sha256 `52c1f1d6564d9e5e12b5708cbf2f12675dee0582b5fc42ece2fcc203fb5b2a7a`).
|
|
`r3/candidate-manifest.sha256` (sha256
|
|
`0fae2b6fa41f1cbcf2117b637a2f7a89cb2af209bf32647f0afc1612a9a34e16`) lists
|
|
the same 31 paths as r2. Round 3 changed 9 of them: the bus and cli
|
|
READMEs, broker.mjs, landing.mjs, land.test.mjs, git-socket.test.mjs,
|
|
launcher.test.mjs, bundle.mjs and bundle.test.mjs. I applied the patch to a
|
|
fresh worktree at BASE. `sha256sum -c` passed on every line, and
|
|
`git diff --cached --check` was clean (31 files, +2115 -255). I removed
|
|
that worktree. Canonical HEAD was `c492f4aaa`, and
|
|
`git diff 63c82f1c4 c492f4aaa` on the 31 paths is empty.
|
|
|
|
### Checks, round 3
|
|
|
|
The slot was mine from 19:43:51Z to 19:46:02Z (sage-0569). Before the
|
|
start, /proc showed no `node --test` process outside my own cwd. This
|
|
round was targeted (decision 136). `r3/round.sh` ran every test file the
|
|
candidate touches, plus row 139's hard-waits.test, each on its own with
|
|
TMPDIR `~/darkwing-scratch/r137/tmp` and no Docker. cli launcher.test and
|
|
thread-launch.test ran under the inherited TMPDIR. Then came
|
|
scripts/test-land-work.sh and one kill per check. A kill edits one source
|
|
file in place, runs one test file, which must fail on the named test, and
|
|
restores the file. The script checks each restored file against its
|
|
starting sha256. Last came the whole bus and seat suites. Output is in
|
|
`r3/gate-run.log`, step logs in `r3/gate/`. It ran from 19:43:51 to
|
|
19:45:35 UTC and ended ROUND_PASSED.
|
|
|
|
| Step | Pass | Fail |
|
|
|---|---|---|
|
|
| bus land.test | 10 | 0 |
|
|
| bus hard-waits.test (row 139) | 12 | 0 |
|
|
| bus project-runtime.test | 3 | 0 |
|
|
| seat landing.test | 8 | 0 |
|
|
| seat working.test | 26 | 0 |
|
|
| harness bundle.test | 22 | 0 |
|
|
| harness git-tools.test | 5 | 0 |
|
|
| harness tools.test | 19 | 0 |
|
|
| cli git-socket.test | 23 | 0 |
|
|
| cli launcher.test, inherited TMPDIR | 19 | 0 |
|
|
| cli thread-launch.test, inherited TMPDIR | 8 | 0 |
|
|
| scripts/test-land-work.sh | 86 | 0 |
|
|
| bus, whole suite, restored candidate | 316 | 0 |
|
|
| seat, whole suite, restored candidate | 150 | 0 |
|
|
|
|
0 skipped in each.
|
|
|
|
| Kill | Edit | Must fail | Failing |
|
|
|---|---|---|---|
|
|
| B1 | working.mjs:348 `if (false) refuse("patch-drift", ...)` | the patch-drift test, exactly 1 | 1 |
|
|
| B2 | working.mjs:224 merge-base against `refs/heads/root` | the root-history test, exactly 1 | 1 |
|
|
| (a) gated refusal | broker.mjs:815 `if (false) fail('not-authorized')` | REQ-LAND-1, exactly 1 | 1 |
|
|
| (b) arbiter path | landing.mjs:112 `arbitrated = false && ...` | REQ-DEC-2, one or more | 6 |
|
|
| item 5, cross-role only | landing.mjs:112 drops `d.class === 'cross-role'` | REQ-LAND-1, exactly 1 | 1 |
|
|
| land-work stays human-only | landing.mjs:91 `arbiter = true` | REQ-DEC-2, exactly 1 | 1 |
|
|
| landHeld cross-role | broker.mjs:924 drops `d.class !== 'cross-role'` | the landHeld test, one or more | 2 |
|
|
|
|
- Kill (b) fails all 6 land.test tests that land or check an
|
|
arbiter-resolved merge.
|
|
- The item 5 kill fails only REQ-LAND-1. With the gated refusal in place,
|
|
the `readLanding` call with `arbiter: true` on the arbiter-resolved gated
|
|
decision is the only check that sees it.
|
|
- The arbiter-default kill fails REQ-DEC-2 on its `readLanding` call
|
|
without `arbiter`, which must refuse `not-human`.
|
|
- The landHeld kill fails the landHeld test and REQ-LAND-1, since both
|
|
assert landHeld is false for a gated decision.
|
|
|
|
Afterwards no node or chromium process of mine had a cwd under
|
|
`~/darkwing-scratch`. No `.orig` file was left, and every killed file was
|
|
back at its starting sha256. The r137 TMPDIR held 847 entries from round
|
|
1's full gate (newest 18:53Z) and none from this run. I removed them.
|
|
|
|
Not run in this round: the other package suites, the other
|
|
scripts/test-*.sh, Docker and mutants. Some untouched test files also read
|
|
a source round 3 changed, and they didn't run on this base:
|
|
|
|
- broker.mjs: packages/cli/tests/helpers.mjs (and so the cli tests that
|
|
build a broker through it), cli tester.test and harness runner.test;
|
|
- launcher.mjs, which asks landHeld: cli launch-limits.test;
|
|
- bundle.mjs: harness claude-gate, claude-session, mcp-server and
|
|
pi-session tests.
|
|
|
|
All of them passed in the r1 full gate on `7a2987cf5`, before the gated
|
|
refusal, the arbiter path and landHeld's filter. The reviewers' gates and
|
|
Sage's gate at the landing target run them on `63c82f1c4`.
|
|
|
|
## Round 4 (sage-0585): B1, the class refusal and the landing.mjs comment
|
|
|
|
Dewey asked for changes on round 3 (#1631 comment 28801, rev 1595), and
|
|
Filbert approved it (comment 28804, manifest `0fae2b6f`). sage-0585 fixed
|
|
round 4 as three items. Everything else in round 3 is unchanged. Base is
|
|
still `63c82f1c4`.
|
|
|
|
### Item 1, Dewey B1: the squash names the real channel
|
|
|
|
Round 3's `landMessage` wrote "Resolved <choice> by <by> via the CLI" for
|
|
every landing. For an arbiter's landing that put "by pm-run via the CLI"
|
|
into main's history, which is false. Now:
|
|
|
|
- `readLanding` returns `via: res.via`, and `#landFacts` passes it into
|
|
checkLand's facts. The broker's ticket answer gains `via`.
|
|
- git-socket.mjs maps it: `cli` writes "via the CLI", and `broker` writes
|
|
"as its arbiter, via the broker". readLanding accepts no other value. If
|
|
one ever got through, the line would print `via <value>`. It never falls
|
|
back to the CLI.
|
|
- land-work's read.mjs prints every key, so it now prints a `via` line too.
|
|
land-work.sh reads keys by name and still writes "via the CLI" itself,
|
|
which holds, since it accepts only a human CLI resolution.
|
|
|
|
### Item 2, Filbert note 1 and Dewey N1: every class but cross-role refuses
|
|
|
|
`#landFacts` now starts with `if (cls !== 'cross-role') fail('not-authorized')`,
|
|
ahead of the spent check and the human check in readLanding. Round 3
|
|
refused only `gated`. A role granted `git.merge.protected` within-role
|
|
resolves its own raise (`via: 'broker'`), so its landing got past that
|
|
line and refused `not-human` in readLanding instead. It now refuses
|
|
`not-authorized`, like a gated one. role.mjs is unchanged: whether it
|
|
should refuse a within-role merge grant at load is a follow-up for row 149
|
|
(sage-0585).
|
|
|
|
The comment above land.test's land-work codes test now matches the code.
|
|
checkLand never gives `not-human`: #landFacts refuses every class but
|
|
cross-role first, and the broker resolves a cross-role decision only
|
|
through the human's CLI answer or its arbiter's decision.resolve.
|
|
|
|
### Item 3, Dewey N2: the comment at landing.mjs:112
|
|
|
|
The arbiter path's line now has a comment above it. A cross-role decision
|
|
routed to an agent closes only through decision.resolve, which takes it
|
|
from the routed role alone (`s.role === route_to`, else `not-resolver`), so
|
|
`res.by` there is the arbiter's run.
|
|
|
|
### Tests, round 4
|
|
|
|
- land.test, REQ-DEC-2: checkLand's facts gain `via: 'broker'`.
|
|
- land.test, the human-routed cross-role landing: checkLand answers
|
|
`by: 'jason'`, `via: 'cli'`.
|
|
- land.test, new: "REQ-LAND-1: a within-role merge grant refuses
|
|
not-authorized at the land". The world's merge-gate holds the grant
|
|
within-role, so its raise with `choice: 'yes'` closes in the call
|
|
(`within-role`, routed to itself, `via: 'broker'`). checkLand refuses
|
|
`not-authorized`, landHeld is false, and nothing merges or refuses on
|
|
the trail. `setup` takes a `roles` override for it.
|
|
- git-socket.test, the first land test: the stand-in's facts gain
|
|
`via: "cli"`, and the squash's message must hold "Resolved yes by jason
|
|
via the CLI at 2026-10-16T00:00:00.000Z."
|
|
- git-socket.test, new: "land: an arbiter-resolved landing's squash says
|
|
the arbiter resolved it through the broker". The stand-in answers
|
|
`by: "pm-run", via: "broker"`. The message must hold "Resolved yes by
|
|
pm-run as its arbiter, via the broker at ..." and no "via the CLI".
|
|
|
|
### Docs, round 4
|
|
|
|
- `packages/bus/README.md`: not-authorized for every class but
|
|
`cross-role`, gated and within-role named; checkLand's answer carries
|
|
the resolution's `via`.
|
|
- `packages/cli/README.md`: the squash's Resolved line names the channel
|
|
from the ticket's `via`.
|
|
|
|
### Interdiff, round 3 to round 4
|
|
|
|
`r4/interdiff.patch` (sha256
|
|
`dcc4672cf6f6ad01cf068725969bfb724d52460528df1adeb405c75a13856af3`), 7
|
|
files, +65 -20:
|
|
|
|
| File | Change |
|
|
|---|---|
|
|
| packages/bus/src/broker.mjs | the class refusal and its comment; `via` in the facts |
|
|
| packages/bus/src/landing.mjs | `via` in readLanding's return; the N2 comment |
|
|
| packages/cli/src/git-socket.mjs | `RESOLVED_VIA` and the Resolved line |
|
|
| packages/bus/tests/land.test.mjs | `via` in two tests, the within-role test, the comment, `setup`'s `roles` |
|
|
| packages/cli/tests/git-socket.test.mjs | `via: "cli"` in the stand-in, the CLI assertion, the arbiter test |
|
|
| packages/bus/README.md | the refusal's classes, `via` in the answer |
|
|
| packages/cli/README.md | the Resolved line |
|
|
|
|
### Candidate, round 4
|
|
|
|
`r4/build.patch` against BASE `63c82f1c4`
|
|
(sha256 `0cabe175b095dd632e6ee6e166efad19089a1c87ffba6198a32c6849a1a80965`).
|
|
`r4/candidate-manifest.sha256` (sha256
|
|
`548ee477baca2a172d2585dd3074979f299b83db4484d1ec89674bb3142a9928`) lists
|
|
the same 31 paths as r3, and differs from it on the 7 above. I applied the
|
|
patch to a fresh worktree at BASE. `sha256sum -c` passed on every line,
|
|
and `git diff --cached --check` was clean (31 files, +2160 -255). I
|
|
removed that worktree. Canonical HEAD was `4ebef2284`, and
|
|
`git diff 63c82f1c4 4ebef2284` on the 31 paths is empty.
|
|
|
|
### Checks, round 4
|
|
|
|
`r4/round.sh` is round 3's script with three kills added and kill (a)
|
|
moved to the new line:
|
|
|
|
| Kill | Edit | Must fail |
|
|
|---|---|---|
|
|
| (a) class refusal | broker.mjs:815 `if (false) fail('not-authorized')` | the within-role test, one or more |
|
|
| item 2, gated only | broker.mjs:815 back to round 3's `cls === 'gated'` | the within-role test, exactly 1 |
|
|
| B1, CLI text | git-socket.mjs:259 hard-codes "via the CLI" | the arbiter squash test, exactly 1 |
|
|
|
|
The other six kills are round 3's.
|
|
|
|
I ran it on Sage's go (sage-0592) from 2026-10-16T20:38:29Z to
|
|
20:40:26Z, in `~/darkwing-scratch/r137/wt4` at BASE with the patch
|
|
applied. Log: `r4/gate-run.log`, per-step logs under `r4/gate/`.
|
|
ROUND_PASSED.
|
|
|
|
| Step | Result |
|
|
|---|---|
|
|
| bus land, hard-waits, project-runtime | 11, 12, 3 pass, 0 fail |
|
|
| seat landing, working | 8, 26 pass, 0 fail |
|
|
| harness bundle, git-tools, tools | 22, 5, 19 pass, 0 fail |
|
|
| cli git-socket | 24 pass, 0 fail |
|
|
| cli launcher, thread-launch (inherited TMPDIR) | 19, 8 pass, 0 fail |
|
|
| scripts/test-land-work.sh | 86 passed, 0 failed |
|
|
| whole bus suite | 317 pass, 0 fail |
|
|
| whole seat suite | 150 pass, 0 fail |
|
|
|
|
All nine kills killed:
|
|
|
|
| Kill | Failing |
|
|
|---|---|
|
|
| b1-patch-drift | 1 |
|
|
| b2-against-root | 1 |
|
|
| (a) class refusal | 2 |
|
|
| item 2, gated only | 1 |
|
|
| B1, CLI text | 1 |
|
|
| b-arbiter-path | 6 |
|
|
| 5-cross-role-only | 1 |
|
|
| arbiter-default | 1 |
|
|
| held-cross-role | 3 |
|
|
|
|
Each named test was among the failures. After the run the sweep found
|
|
none of my processes left, no `.orig` file remained, and `sha256sum -c`
|
|
on the manifest still passed. This is a targeted builder round
|
|
(decisions 136/137). The whole cli and harness suites and the other
|
|
`scripts/test-*.sh` are the landing gate's.
|