From 1c90c68c534226f7c906eae8ec12cdb34f9d7b2a Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Mon, 19 Oct 2026 17:22:54 -0500 Subject: [PATCH] sage: row 175 brief widened to tester reads and writes (#1673, Q173, mos-0870 N2, mos-0872 Q1-Q3): seven writes recorded as tester, nine human-only items, prefix lifted, project init --tester inside the data root's project area only --- docs/plans/2026-10-19_tester-read-checks.md | 219 ++++++++++++++------ 1 file changed, 158 insertions(+), 61 deletions(-) diff --git a/docs/plans/2026-10-19_tester-read-checks.md b/docs/plans/2026-10-19_tester-read-checks.md index de76c7f75..897349924 100644 --- a/docs/plans/2026-10-19_tester-read-checks.md +++ b/docs/plans/2026-10-19_tester-read-checks.md @@ -1,25 +1,32 @@ -# Brief: read-only window checks a tester can run +# Brief: tester reads and writes for the test system -Gitea #1673. This brief follows Mos's ruling mos-0868, made on Sage's finding F2 from Window A (mos-0866). Owner -Dewey, reviewer Filbert, gate owner Sage. Queued after row 169, so it rides the first window past c216f47db. Line -numbers are at origin/refactor 4b738e2da. +Gitea #1673. Owner Dewey, reviewer Filbert, gate owner Sage. Line numbers are at origin/refactor d195d171c. -## The problem +History. Mos ruled mos-0868 on Sage's Window A finding F2: the window reads run under `--tester`. Jason then made +DECISIONS Q173 (mos-0870): until he says the Mosaic Stack is in use, the mosaic-stack business on dragon-lin is a test +system, and Mos, Sage and Darkwing test it end to end without him. Mos's reading is that agents act as the tester, +never as Jason. So this row widens from reads only (brief blob de76c7f752d4) to reads plus the writes a window and an +end-to-end test need (N2). Mos accepted the split below in mos-0872: seven tester writes, nine human-only items, +nothing moves between the lists. -Every bus read the CLI makes without `--tester` refuses inside an agent run. `callFor` -(`packages/cli/src/cli.mjs:184-191`) calls `refuseInsideAgent` unless `--tester` is set, and that exits 3. Only -`inbox`, `decide`, `tasks`, `agents` and `trail` (`human()`, :570) and the message verbs take `--tester` today. So -every window read of usage, names, limits, plugins, threads, projects or instances needs Jason at a human shell. In -Window A that was two of his four handoffs. +This row no longer waits on row 169 to start. Sage lands it after 169, and the merged window runs once 169, this row +and row 176 have landed. -mos-0868: those reads run under `--tester`, so Jason's part of a window is the plug-in sets, which are writes and stay -his, and the browser check. Read only. No write verb gains `--tester`. +## The rules every change here keeps -## The change +- A tester write is recorded where the human's own write of the same kind is recorded, with `tester` as the actor. + Never the human's id, and never empty. +- The binding doesn't change. `bindTester` (`packages/bus/src/broker.mjs:521-526`) still refuses `tester-off` unless + the business var `bus.tester` is exactly true. `verifyTester` (`packages/bus/src/tester.mjs`) still refuses + `tester-required` under a managed run. `bus.tester` stays Jason's switch: no verb in this row can set it. +- Row 164 pins the bytes of `packages/bus/src/human.mjs`, `packages/bus/src/human-cli.mjs`, + `packages/cli/src/transport.mjs` and `packages/webui/src/bus.mjs` (`packages/bus/tests/channel-proof.test.mjs:11-19`). + Don't touch them. If the build finds one has to move, stop and tell Sage, who takes it to Mos. +- Before in-review, grep every test for each changed path and for a 64-hex pin on it, not only channel-proof. -### CLI +## Reads (as filed in mos-0868) -Add `--tester` to these read forms, and to nothing else: +Add `--tester` to these read forms: | Command | Parser | Bus verb | |---|---|---| @@ -31,64 +38,154 @@ Add `--tester` to these read forms, and to nothing else: | `mosaic project list` | `project.mjs:38`, through `channelCallFor` | `project.list` | | `mosaic business instances` (the read) | `instances.mjs:93-101` | `instances` | -- Each parser accepts `--tester` on the read form only and passes it through to `callFor`, which already binds the - tester (row 110). `name`, `limits set`, `plugins set`, `business instances add|set`, `project create`, - `thread create|open|read` and every other write refuse `--tester` with the usual usage error (exit 4). -- `threads` and `project list` go through `channelCallFor` (:196), whose channel caller reads `plugins` first - (`packages/plugins/src/channel.mjs:16`). So the tester needs the `plugins` read for those two to work at all. -- Leave `packages/cli/src/transport.mjs` alone. Row 164 freezes its bytes - (`packages/bus/tests/channel-proof.test.mjs:13`). The same test pins `packages/bus/src/human.mjs`, - `human-cli.mjs` and `packages/webui/src/bus.mjs`. Don't touch those either. -- Update the usage text (`cli.mjs` USAGE and each command's own) and `docs/TOOLS.md` where they list `--tester`. - -### Bus (`packages/bus/src/broker.mjs`) - -- `TESTER_VERBS` (:95) gains exactly `usage`, `names`, `limits`, `plugins`, `instances`, `project.list` and - `thread.list`. Update the comment above it. -- `limits`, `plugins` and `instances` never reach the `TESTER_VERBS` check at :1279. They take the organization branch - at :1224, which refuses `human-required` for anyone but the human (:1233). In that branch, let a tester session - through for those three reads only. Every write in the branch (`limits.set`, `plugins.set`, `instances.set`, - `routes.set`, `organization.set`) and the `routes` and `organization.revision` reads still refuse a tester, with - `tester-refused`. If `#organization` records an actor for a read, the tester's actor is `tester`, never the human's. +- `threads` and `project list` go through `channelCallFor` (`cli.mjs:196`), whose caller reads `plugins` first + (`packages/plugins/src/channel.mjs:16`). So the `plugins` read has to work for a tester for those two to work. +- `TESTER_VERBS` (`broker.mjs:95`) gains `usage`, `names`, `limits`, `plugins`, `instances`, `project.list` and + `thread.list`. +- `limits`, `plugins` and `instances` take the organization branch (`broker.mjs:1224`), which refuses + `human-required` for anyone but the human (:1233) and never reaches the `TESTER_VERBS` check at :1279. Let a tester + through there for those three reads. The `routes` and `organization.revision` reads still refuse a tester with + `tester-refused`. - `usage` (:2087) already lets a reader view through, and a tester's non-message verb runs as a reader (:1281). Check - it, and leave it alone if it works. -- `names` (:2247) sends anyone who isn't the human to `#agent`. Let a tester session read it. -- `project.list` (:2276) and `thread.list` (:2306) call `#human`, which refuses a reader `read-only`. Let a tester - session read both. `thread.list` filters by `owner=s.human`, and a tester has no human, so for a tester it lists - the business human's threads (`this.#business(s.business).human`). That is the same thread list the human sees. -- `project.create`, `thread.create`, `thread.open`, `thread.messages` and `name.set` don't change, so they still - refuse a tester. -- The binding doesn't change. `bindTester` (:521-525) still refuses `tester-off` unless the business var - `bus.tester` is exactly true. `verifyTester` (`packages/bus/src/tester.mjs`) still refuses `tester-required` under - a managed run. + it and leave it alone if it works. +- `names` (:2249) sends anyone who isn't the human to `#agent`. Let a tester read it. +- `project.list` (:2277) and `thread.list` call `#human`, which refuses a reader `read-only`. Let a tester read both. + `thread.list` filters by `owner=s.human`, and a tester has no human, so for a tester it lists the business human's + threads (`this.#business(s.business).human`), the same list the human sees. + +## Writes (N2, Q173) + +| # | Write | CLI form gaining `--tester` | Where it is recorded, actor `tester` | +|---|---|---|---| +| W1 | `plugins.set` | `mosaic plugins set --file PATCH` (`plugins.mjs:30-36`) | the organization writer's receipt (`packages/business/src/organization.mjs:177-203`) | +| W2 | `instances.set` | `mosaic business instance add\|set` (`instances.mjs:47-60`) | the same receipt | +| W3 | `routes.set` | none; the Console's routing table, used by row 176 | the same receipt | +| W4 | `name.set` | `mosaic name INSTANCE NAME` (`cli.mjs:311-319`) | `instance_names.by` | +| W5 | `message.send` without the prefix | `mosaic talk --tester ROLE BODY` (`cli.mjs:353-369`) | `messages.from_role`, already `tester` | +| W6 | `project.create` | `mosaic project create NAME --tester` (`project.mjs:18-36`) | `projects.created_by` | +| W7 | project init | `mosaic project init BUSINESS PROJECT --tester` (`project.mjs:43-60`) | the repository's first commit author | + +`instance_names.by` and `projects.created_by` are `TEXT NOT NULL` with no CHECK +(`packages/bus/src/migrations/3f-3g.sql:5-12`, `3d-3e.sql:2-10`), so no migration. + +### W1-W3, the organization branch + +- In the branch at `broker.mjs:1224`, a tester session passes for `plugins.set`, `instances.set` and `routes.set` + with `actor = "tester"` (:1246 sets `s.human` today). Every check the human's write runs still runs: the revision, + the key lists, `invalid-plugins`, `unknown-plugin`, the route checks, and in `instance-settings.mjs` the refusals of + credential-declaring definitions (`organization-credentials-out-of-scope`), protected definitions + (`organization-definition-refused`) and the instance names `human` and `tester`. +- `limits.set` and `organization.set` refuse a tester with `tester-refused`, and nothing is written. +- `bus.tester` has only the `["business"]` layer (`packages/business/src/vars.mjs:125`), so `instances.set` can't set + it. Pin that with a tester test. + +### W4, `name.set` + +`name.set` (:2210) sends a non-human through `#classification`, then `#agent`, which refuses a tester. Add a tester +branch before that: every name check still runs, no authority is consumed, and the insert writes `by = 'tester'` +(:2228 writes `s.human || s.run` today). + +### W5, the prefix goes + +Mos lifted the `[mos test]` prefix on the test system in mos-0872 Q1. It came from Jason's Q134 C, made when tests +ran in tmux, and Q173 replaces it here. Remove the broker check (`broker.mjs:1812`, `tester-prefix`), the CLI's early +check (`cli.mjs:369`) and both `TESTER_PREFIX` constants (:96, `cli.mjs:325`). Everything else about a tester send +stays: a role target only, the keys `to`, `body`, `class` and `in_reply_to`, no decision, no authority consumed, no +`human.input`, `from_role` `tester`, and `talk --thread` still refuses `--tester`. A role still answers the tester only +in reply to a tester message (`tester-reply-required`). Update the tests, READMEs and `docs/TOOLS.md` lines that +mention the prefix (`grep -rn "tester-prefix\|mos test\]" packages scripts docs`). + +### W6, `project.create` + +`project.create` (:2258) calls `#human`. Let a tester through with every check unchanged and `created_by = 'tester'`. + +### W7, project init + +`projectCommand` (`project.mjs:43-60`) refuses every `--` argument and calls `refuseInsideAgent`, and `initProject` +(`packages/project/src/index.mjs:95-134`) never contacts the bus and commits as `identity(business.human)`. With +`--tester`, in this order, before anything touches the disk: + +1. The project area check (mos-0872 Q2). The repo `projectRepo` resolves + (`packages/project/src/paths.mjs:30-45`) must sit inside `/projects//`. A business entry + whose `repo` points anywhere else, for example `/mnt/storage/src/mosaic-stack`, refuses with a new + `ProjectError('tester-project-area', ...)` (exit 2), and nothing is created. Compare resolved absolute paths with + the same containment test `projectRepo` uses. +2. Bind the tester with one bus read (`agents`) through the CLI's tester transport, so `tester-off` and + `tester-required` refuse (exit 3) before any write. +3. `initProject(context, { author: "tester" })`: the layout commit's author and committer are `tester` + (`identity("tester")`, `packages/project/src/git.mjs:55`). Without the option it commits as the business human, + as today. + +`project check` doesn't change and takes no `--tester`. + +## Human-only, and why (Q3, unchanged by this row) + +These refuse a tester with `tester-refused` (bus) or a usage error, exit 4 (CLI), and the build adds no path around +them. + +1. `limits.set` and the Console's Settings Save. They cap spend, and money is Jason's. +2. `bus.tester` and every business var. Hand edits only; it is Jason's switch. +3. Credentials, holder and tracker bindings. They are external accounts. +4. `decision.resolve`, `seen` and `withdraw` on human-routed decisions. Those are Jason's answers. +5. `launch.revoke` and `launch.restore`. His stop switch. +6. The human's `message.send`, `thread.send` into the human's threads, `human.input` and `bindHuman`. Any of these is + acting as Jason. +7. `project.file.save`. The spec is the human's (`project-spec-human-only`). +8. `assistant.stop`. It acts on the human's threads. A tester form can come with row 177. +9. The Console preview flags and the proxy. They change outside reach. + +`thread.create`, `thread.open`, `thread.send` and `thread.messages` also still refuse a tester here; row 177 gives +the tester its own threads. ## Tests -Bus tests in `packages/bus/tests/`, CLI tests in `packages/cli/tests/`. Each read is one test that runs on its own. +Bus tests in `packages/bus/tests/`, CLI tests in `packages/cli/tests/`, project tests in `packages/project/tests/`. +Each runs on its own. 1. Each of the seven reads, bound as the tester with `bus.tester` true, returns what the human's read returns on the same business (deep equal). -2. The CLI runs each of the seven with `--tester` and an agent marker in the environment (`AGENT_MARKERS`, - `transport.mjs:28`), and prints the same output as the human's form. Without `--tester` the same command still - exits 3 inside an agent. -3. A tester session refuses `tester-refused` for each of `limits.set`, `plugins.set`, `instances.set`, `routes.set`, - `organization.set`, `routes`, `organization.revision`, `name.set`, `project.create`, `thread.create`, - `thread.open` and `thread.messages`. The organization file's revision and the store don't change. -4. The CLI write forms refuse `--tester` with exit 4 before any bus call. -5. With `bus.tester` unset or not exactly true, the tester binding refuses `tester-off`, and a read never runs. - Under a managed run marker the CLI's tester transport refuses `tester-required`. -6. `TESTER_VERBS` is exactly the old ten plus the seven, pinned as a list. +2. The CLI runs each read with `--tester` and an agent marker in the environment (`AGENT_MARKERS`, + `transport.mjs:28`), and prints the human form's output. Without `--tester` the same command exits 3 inside an + agent. +3. W1-W3 as the tester: each write lands, and its organization receipt names actor `tester`. The same write by the + human still names the human. +4. W2 as the tester refuses a credential-declaring definition, a protected definition, the instance names `human` and + `tester`, and an instance var `bus.tester`. Revision and store unchanged after each. +5. W4 as the tester writes an `instance_names` row with `by = 'tester'`, and still refuses `name-taken`, + `name-is-instance` and `name-is-role`. +6. W5: a tester send without the prefix lands with `from_role` `tester`, writes no `human.input`, consumes no + authority, and still refuses a target of `human` or `tester` (`unknown-role`) and a `decision` key. +7. W6 as the tester: the row's `created_by` is `tester`. +8. W7: `project init --tester` on an entry with no `repo` makes the repo under `/projects//` + with a first commit authored and committed by `tester`. On an entry whose `repo` is outside that area it refuses + `tester-project-area` and leaves the target directory and the data root unchanged (the Q2 refusal test). With + `bus.tester` off it refuses `tester-off`, and under a managed run marker `tester-required`, both before any + directory is made. +9. A tester refuses `tester-refused` for `limits.set`, `organization.set`, `routes`, `organization.revision`, + `decision.resolve`, `launch.revoke`, `launch.restore`, `credentials`, `project.file.save`, `assistant.stop`, + `thread.create`, `thread.open`, `thread.send` and `thread.messages`. The organization revision and the store don't + change. +10. The CLI refuses `--tester` with exit 4, before any bus call, on `limits set` and every other write form not in + W1-W7. +11. With `bus.tester` unset or not exactly true, the tester binding refuses `tester-off` and no read or write runs. +12. `TESTER_VERBS` is pinned as a list: the old ten, the seven reads, `name.set` and `project.create`. (W1-W3 pass in + the organization branch, so they aren't in the list; a test pins that branch's tester set as + `limits`, `plugins`, `instances`, `plugins.set`, `instances.set`, `routes.set`.) Mutants, each killed by some gate test (mos-0862): -- drop each of the seven from `TESTER_VERBS` -- let `limits.set` or `plugins.set` through the organization branch for a tester +- drop any one of the added verbs from `TESTER_VERBS` +- let `limits.set` or `organization.set` through the organization branch for a tester +- record a tester's W1-W3 actor as the business human, or W4's `by` as the human - `thread.list` for a tester binds `owner` to anything other than the business human +- restore the prefix check in the broker +- skip W7's project area check +- skip W7's tester bind (step 2), so init runs with `bus.tester` off - the CLI accepts `--tester` on `limits set` -- the CLI drops `--tester` before `callFor` on any of the seven reads +- the CLI drops `--tester` before `callFor` on any read ## Gate -Filbert approves the candidate manifest. Bus, cli and plugins suites and every `scripts/test-*.sh` green on Sage's -gate. The window-plan docs drop the Jason-only reads in the first window plan whose pinned tree includes this row. -Until then Window B keeps the split in `agents/sage/work/window-plan/repin-ca15d73e9.md`. +Filbert approves the candidate manifest. Bus, cli, plugins, project, business and webui suites and every +`scripts/test-*.sh` green on Sage's gate. The merged window plan (after rows 169, 175 and 176 land) uses these forms +in place of Jason's reads and plug-in sets.