From c2c4ac6793523d22c48969815ac044a8195cde27 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 22 Oct 2026 13:46:58 -0500 Subject: [PATCH] Row 286 C2-03b: fold dewey-0860 (Q1 sh fd/inode check, Q2 chief by roles definition, N1-N3) --- agents/ludwig/work/queue-286/BUILD.md | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/agents/ludwig/work/queue-286/BUILD.md b/agents/ludwig/work/queue-286/BUILD.md index b49b12088..f57f2dda2 100644 --- a/agents/ludwig/work/queue-286/BUILD.md +++ b/agents/ludwig/work/queue-286/BUILD.md @@ -113,3 +113,47 @@ argv, the mirror config, the run log and the test output. Mutant: a `+` added to Dewey approves the manifest. The library and bus suites, plus every `scripts/test-*.sh`, pass on Sage's gate. The mutant dies. A token search of the run's output finds nothing. + +## Early look (dewey-0860), folded + +Q1, askpass: sh, with no gap between the check and the read. The helper keeps `test -f` and then: +- opens the slot with `exec 3<"$slot"`; +- stats the open file with `stat -L -c '%d:%i %u %a %s %F' /dev/fd/3`, and the path itself (an lstat) + with `stat -c '%d:%i' -- "$slot"`; +- refuses unless the two dev:inode values match, the type is regular, the uid is `id -u`, the mode is + 600 and the size is at most 16384; +- reads the token from fd 3. +A symlink or a swap at either moment changes the inode, and the token comes from the file that was +checked. This matches `readSecretFile` (`gitea-grant.mjs:167-181`). +Residual, the same on both paths: a FIFO swapped in after `test -f` blocks the open. Neither side uses +O_NONBLOCK. +Swap test T5b: the direct helper test puts a `stat` shim on its own PATH. On its first call the shim +renames another file over the slot, then runs the real stat. The helper must exit 1 and print nothing. +Mutant M2 drops the inode compare and must fail T5b. + +Q2, the chief: found through `roles` by definition, not through `launch.instances`. A fire only delists +the name (`packages/business/src/instance-settings.mjs:355`), and `roles[name]` stays. Only a delete +removes it (`deletedDocument`, :70). +- Gitea grant evidence for any `roles` entry whose definition is `chief-of-staff` ends the fallback. + That includes a fired chief beside a new listed one. +- A live credential comes only from the listed chief's live, unheld `gitea_grants` row. +- With no `roles` entry of that definition (deleted, or never a chief), any gitea grant event in the + business refuses `library-fetch-no-grant`. + - Cost, for Sage: row 236 lets seat gitea grants exist before C2-21. So a business with no chief + entry and one seat grant loses the slot fallback. That fails safe, but it narrows "an install that + runs before C2-21 still syncs". +- T7's fired case also asserts that `roles[chief]` still exists after `instance.fire`. + +N1: the `credential` key is optional in `mirror.mjs`'s exact `keys(...)` check (:221), so a pre-286 +state file still reads. The old fixtures have no `credential`: `library-sync.test.mjs:18`, +`library.test.mjs:106` and `mirror.test.mjs:13`. New test T12 reads a pre-286 state file. Row 224 +(Della, 23816cf28) also changes `packages/bus/src/runtime.mjs`. Whichever lands second rebases and reruns +bus whole. + +N2: `[remote rejected]`, a hook refusal, is `library-push-failed`, not a race. Only `[rejected]` with +`(fetch first)` or `(non-fast-forward)` counts as a race. The evidence query depends on +`body.method` and `body.agent`. gitea `grant.applied` carries both at `broker.mjs:2479` and +`grant.revoked` at :1862 and :2797. The spend event (`operation: 'grant.gitea'`, :1093) carries +neither, so it doesn't match. + +N3: `fetchCredential` reads the store, so it is the bus's choice function, not a pure one.