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

This commit is contained in:
jason.woltje committed 2026-10-19 17:22:54 -05:00
1 parent d195d171c6
commit 1c90c68c53
1 file changed
+158 -61
+158 -61
View File
@@ -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 Gitea #1673. Owner Dewey, reviewer Filbert, gate owner Sage. Line numbers are at origin/refactor d195d171c.
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.
## 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` This row no longer waits on row 169 to start. Sage lands it after 169, and the merged window runs once 169, this row
(`packages/cli/src/cli.mjs:184-191`) calls `refuseInsideAgent` unless `--tester` is set, and that exits 3. Only and row 176 have landed.
`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.
mos-0868: those reads run under `--tester`, so Jason's part of a window is the plug-in sets, which are writes and stay ## The rules every change here keeps
his, and the browser check. Read only. No write verb gains `--tester`.
## 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 | | 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 project list` | `project.mjs:38`, through `channelCallFor` | `project.list` |
| `mosaic business instances` (the read) | `instances.mjs:93-101` | `instances` | | `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 - `threads` and `project list` go through `channelCallFor` (`cli.mjs:196`), whose caller reads `plugins` first
tester (row 110). `name`, `limits set`, `plugins set`, `business instances add|set`, `project create`, (`packages/plugins/src/channel.mjs:16`). So the `plugins` read has to work for a tester for those two to work.
`thread create|open|read` and every other write refuse `--tester` with the usual usage error (exit 4). - `TESTER_VERBS` (`broker.mjs:95`) gains `usage`, `names`, `limits`, `plugins`, `instances`, `project.list` and
- `threads` and `project list` go through `channelCallFor` (:196), whose channel caller reads `plugins` first `thread.list`.
(`packages/plugins/src/channel.mjs:16`). So the tester needs the `plugins` read for those two to work at all. - `limits`, `plugins` and `instances` take the organization branch (`broker.mjs:1224`), which refuses
- Leave `packages/cli/src/transport.mjs` alone. Row 164 freezes its bytes `human-required` for anyone but the human (:1233) and never reaches the `TESTER_VERBS` check at :1279. Let a tester
(`packages/bus/tests/channel-proof.test.mjs:13`). The same test pins `packages/bus/src/human.mjs`, through there for those three reads. The `routes` and `organization.revision` reads still refuse a tester with
`human-cli.mjs` and `packages/webui/src/bus.mjs`. Don't touch those either. `tester-refused`.
- 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.
- `usage` (:2087) already lets a reader view through, and a tester's non-message verb runs as a reader (:1281). Check - `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. 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. - `names` (:2249) sends anyone who isn't the human to `#agent`. Let a tester read it.
- `project.list` (:2276) and `thread.list` (:2306) call `#human`, which refuses a reader `read-only`. Let a tester - `project.list` (:2277) and `thread.list` call `#human`, which refuses a reader `read-only`. Let a tester read both.
session read both. `thread.list` filters by `owner=s.human`, and a tester has no human, so for a tester it lists `thread.list` filters by `owner=s.human`, and a tester has no human, so for a tester it lists the business human's
the business human's threads (`this.#business(s.business).human`). That is the same thread list the human sees. threads (`this.#business(s.business).human`), the same list the human sees.
- `project.create`, `thread.create`, `thread.open`, `thread.messages` and `name.set` don't change, so they still
refuse a tester. ## Writes (N2, Q173)
- 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 | # | Write | CLI form gaining `--tester` | Where it is recorded, actor `tester` |
a managed run. |---|---|---|---|
| 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 `<dataRoot>/projects/<business id>/`. 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 ## 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 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). same business (deep equal).
2. The CLI runs each of the seven with `--tester` and an agent marker in the environment (`AGENT_MARKERS`, 2. The CLI runs each read 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 `transport.mjs:28`), and prints the human form's output. Without `--tester` the same command exits 3 inside an
exits 3 inside an agent. agent.
3. A tester session refuses `tester-refused` for each of `limits.set`, `plugins.set`, `instances.set`, `routes.set`, 3. W1-W3 as the tester: each write lands, and its organization receipt names actor `tester`. The same write by the
`organization.set`, `routes`, `organization.revision`, `name.set`, `project.create`, `thread.create`, human still names the human.
`thread.open` and `thread.messages`. The organization file's revision and the store don't change. 4. W2 as the tester refuses a credential-declaring definition, a protected definition, the instance names `human` and
4. The CLI write forms refuse `--tester` with exit 4 before any bus call. `tester`, and an instance var `bus.tester`. Revision and store unchanged after each.
5. With `bus.tester` unset or not exactly true, the tester binding refuses `tester-off`, and a read never runs. 5. W4 as the tester writes an `instance_names` row with `by = 'tester'`, and still refuses `name-taken`,
Under a managed run marker the CLI's tester transport refuses `tester-required`. `name-is-instance` and `name-is-role`.
6. `TESTER_VERBS` is exactly the old ten plus the seven, pinned as a list. 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 `<dataRoot>/projects/<business>/`
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): Mutants, each killed by some gate test (mos-0862):
- drop each of the seven from `TESTER_VERBS` - drop any one of the added verbs from `TESTER_VERBS`
- let `limits.set` or `plugins.set` through the organization branch for a tester - 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 - `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 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 ## Gate
Filbert approves the candidate manifest. Bus, cli and plugins suites and every `scripts/test-*.sh` green on Sage's Filbert approves the candidate manifest. Bus, cli, plugins, project, business and webui suites and every
gate. The window-plan docs drop the Jason-only reads in the first window plan whose pinned tree includes this row. `scripts/test-*.sh` green on Sage's gate. The merged window plan (after rows 169, 175 and 176 land) uses these forms
Until then Window B keeps the split in `agents/sage/work/window-plan/repin-ca15d73e9.md`. in place of Jason's reads and plug-in sets.