Compare commits

..
195 changed files with 5722 additions and 20730 deletions

No files matched your search

+3 -39
View File
@@ -5412,44 +5412,8 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
2026-10-23T17:32:37Z | Gyro sage-3544 fenton-0016 N1/N2 delta | Before: W7 Gate/final real-conversion list omit the W8 build-order cross-reference and W8 human-view filtering cites agent settings. After: both locations explicitly put W8 landing before human opening as mos-1470 build order, not acceptance; comment/tracked-deletion links are not current story links and never appear in the human Connections view regardless of agent settings. New commit atop approved 5af060fb5, no amend. Static diff and append-only checks pass. Docs only, no repo Node/tests/live action. Fenton checks only this delta.
2026-10-23 | Gladstone | Gitea #1816 / Vikunja 614 hostcheck item 5 and bundle verification | Before: dpkg's correct 750 form falsely fails item 5, and two run-sheet bundle verifies require a missing repository. Draft after: exact root/mosaic/path with octal 0750 equivalence; stat/output unchanged; both verifies use cleaned-up bare scratch repos. Three additive-only test blocks, two planned mutants. Untested under Sage-3738; Sage-3740 authorizes untested branch push for static review. Queue row pending; Sage owns gate and landing.
2026-10-23 | Fethry | Row 225 C2-10b (#1726), handoff recovery. Before: Morgana's untested candidate 9a131e956 replaces renderItems with renderReply and adds Console citation/notice/status rendering and status/revert routes. Continuing that exact history on fethry/row225; preserving A1-A5 and the accepted file deviations. Remaining: confirm limits contract, review/test candidate under Sage CLEAR, obtain Mos OK through Sage for inherited old-test changes, manifest and Dewey review. No Node/tests run in this session and no green claim.
2026-10-23 | Gladstone | #1816 / Vikunja 614 scope amendment mos-1534, sage-3741 | F1 narrowed to exact 750/0750 and single spaces; F3 allows @ in absolute unit paths with unsafe-character refusals retained; F4 moves copy_tree to / after path resolution. AS_USER audit found git -C and stack-release's existing checkout cd cover the other steps. Added new unit and unreadable-cwd copy tests only. Untested under Sage's no-Node hold; no live action.
2026-10-23 | Fethry | Row 225 T1 lane A (sage-3641/3647). Before: nested replyLimits fallback/problem omitted, wrong-project revert receipt accepted, invalid library mapping hidden. After RED: these three local behaviors corrected; three assertions in the new handoff test files corrected against existing route/mapping/Boolean behavior, each explained in BUILD.md. Whole-file GREEN: renderer 9/0, routes 13/0, browser 9/0; full library 81/0, channel-proof 2/0. Approved old-test numstats remain 80/66 and 3/1. Full webui/build and full integration gate have not run. Isolated mutants next within the authorized 19:38Z hard stop; no completion claim.
2026-10-23 | Gladstone | #1816 / Vikunja 614 F5 draft (mos-1536) | Before: the run sheet omits the explicit notify decision and precheck misses a bus-start refusal. After: section 9a creates a private null-binding decision as mosaic; precheck directly reuses readNotifyConfig and private_dir, and new tests cover accepted and refused decisions. Existing fixture setup additions await Mos approval through Sage; installer unchanged. Untested under no-Node hold.
2026-10-23 | Gladstone | #1816 / Vikunja 614 fixture ruling mos-1537 | Added only three setup lines each to host(t)/world(t) for the explicit null notifier decision. Existing assertions/fakes/lines intact. New install test checks decision bytes/mode preserved and missing decision remains absent. F5 missing-file fault removes the setup file. Untested under Sage's hold; separate follow-up atop 33950cf9b.
2026-10-23 | Gladstone | #1816 / Vikunja 614 F6 and notify parent (mos-1537) | Before: the run sheet's copy check lacks its required CLI wrapper; mkdir -p -m does not guarantee the notify parent mode. After: a separately Mos-gated wrapper --create step follows install and precedes check, as mosaic from cwd /; section 9a explicitly creates both notify directories at 0700. Docs only, no live action; Node/tests still held.
2026-10-23 | Gladstone | Row320 / #1816 first review CHANGES (morgana-0217) | Reviewer run: hostcheck19/19, unit47/47, install24/24, copy17/18; seven of eight mutants killed, M4 survived because both base/mutant failed only cleanup. Fixed this row's F4 test to enter cwd then chmod000, assert no read/search, pass an absolute outside bundle, and restore0700 in finally. Existing tests/setup and production scripts unchanged. Correction untested locally pending Morgana's focused rerun; no acceptance claim.
2026-10-23 | Boyd | Row 298 (#1804, Q228), before implementation completion | Took over Megavolt 7a141269c on boyd/row298 at 45dca69f6 under mos-1496/1502. Approved D1-D6 govern human-only grants.scope.set, empty fallback, in-scope close/evidence and spend recheck. All inherited code UNTESTED; no Node/tests until Sage CLEAR. Plan and static gaps recorded in agents/boyd/work/queue-298/BUILD.md; no live grant or credential access.
2026-10-23 | Boyd | Row 298 (#1804, Q228), targeted verification | On sage-3642 T1 lane B, five whole files serial: business scope 7/0, bus scope 14/0, scope runtime 1/0, CLI scope 4/0, project runtime 3/0 (29/0, no skips). Initial bus fixture typo recorded and corrected. M1-M10 killed by named assertions, source restored and final files green. Dewey dewey-0933 positive control and notes addressed; manifest and evidence in agents/boyd/work/queue-298/BUILD.md. Full package/script gate remains Sage's. No live scope set.
2026-10-23T22:41:07Z | Boyd | Row 298 (#1804), rebase after row 239 | Rebased onto 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7 on boyd/row298-r2 without altering published boyd/row298. Only append-log conflicts; upstream bytes preserved and Boyd suffixes appended. Product merges automatic; row 239 writer-lock revoke and post-revoke error handling preserved, organization.mjs unchanged from new base. Static checks only; UNTESTED on new base pending Sage CLEAR. Details in Boyd BUILD.md.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 before | The library installer runs the clone importer, whose yaml dependency is absent on fresh clones, and tail -n 1 hides uncaught errors behind Node's version banner. Plan: run the same-commit copy importer, include it in COPY_PATHS, keep the first error line with last-line fallback, append isolated regression coverage. Mos-1550 approves setup-only world(t) additions and the one-path copy-file pin addition. No tests run yet; Sage slot required.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 after | Importer now runs from the same-commit dependency-equipped copy; COPY_PATHS includes it. One-line refusal selects an actual Error/ERR_ line, avoiding Node's source excerpt/banner, with last-line fallback. Mos-1550-approved fixture setup and one-file pin changes; two append-only regression tests. Under sage-3808 CLEAR, serialized timeout -k 10 runs in /mnt/storage/scratch/tmp/goldieuQ2664: install26/26, copy18/18, pinned-copy10/10; new tests2/2 first run and restored; N installer expected-red2/2, F8 and F9 mutants killed, installer SHA256 restored each time. No unexpected red. Evidence and exact old-test changes in agents/goldie/work/queue-322/BUILD.md; full gate and Morgana review belong to Sage.
2026-10-23 | Goldie | Row 324 / #1820 before (Q247, mos-1555) | New branch goldie/row324 at row298 approved860afc2b6. Design/readers audit in agents/goldie/work/queue-324/BUILD.md: normalize omitted tracker to null, preserve present validation, guard sync readers/adapter config; runtime/instance-settings/broker checks already optional. Existing business.test.mjs98 explicitly requires tracker, so implementation held for Mos approval of removing that one list entry; no old test or product edit, no tests run.
2026-10-23 | Goldie | Row 324 / #1820 draft built (sage-3836, mos-1556) | Claim rev3759. Omitted tracker normalizes null; present including null/malformed stays validated. Loader, business validate, bootConfig trackerFor and Console sync reader handle absence. Row298's guarded runtime/instance-settings/broker paths preserved. Two new test files (9 planned tests); sole old-test edit drops tracker from required-key list under mos-1556. node --check6/6 and diff check pass. No tests run: untested branch push expressly ordered before CLEAR; Duckworth review and Sage gate pending.
2026-10-23 | Goldie | Row 324 / #1820 rebased verification (sage-3850) | Rebased onto3569bf54f as7e59ba8b3; only append-log conflicts, eight manifest files byte-equal. Serialized timeout-k10 scratch TMPDIR goldieOdgIjr: new business5/5, new CLI4/4 (includes Console absent read), existing business21/21; presence mutant killed, bytes restored, new files rerun5/5 and4/4. No unexpected red. Published rebased branch goldie/row324-r2 preserves original remote draft without force-push. Evidence/mutant diff/reader list in BUILD.md; review/full gate pending.
2026-10-23 | Goldie | Row 323 / #1819 before | Case 1 lacks a committed business file. Brief requires actual .53 values and refuses guessing. New branch goldie/row323 from local refactor da537e7d1; source review shows project root, literal human id, tracker sync identity/reference metadata and launch model values absent from DESIGN, plus nonempty launch.instances requires another role. Asked Sage (goldie-0008); no config or test written, no test run. Details agents/goldie/work/queue-323/BUILD.md.
2026-10-23 | Goldie | Row 323 / #1819 static blocker (sage-3823) | Mos-1554 requires no bot and an absent, never-created tracker token path. Static tracing confirms this refuses install validation (business cli84-97, credentials78-83) and bus boot (runtime131/157, bus credentials225-228), even without tracker.baseUrl; unset env also refuses at bus credentials278. Reported Sage goldie-0009; no JSON/test/credential written, no tests. Accepted source values/role and refusal-test rulings are recorded in BUILD.md; waiting for product-boundary resolution.
2026-10-24 | Goldie | Row 323 / #1819 resumed draft (sage-3859) | Rebasing prior notes onto Q247 landing dd6cb5872 resolved only append logs. Added trackerless .53 factory JSON: jason, approved project root, chief+assistant for required nonempty launch.instances, claude-code/Opus5.5/high, tester on. No credentials or token placeholder. Seven new tests prepare exact-doc loading plus scratch real validate/install/boot and three refusal paths; one restoring tester-switch mutant prepared. No old test edits; no tests before Sage CLEAR. Source choices/rulings in BUILD.md.
2026-10-24 | Goldie | Row 323 / #1819 pre-test commit (sage-3861 CLEAR) | Sage explicitly requests commit before runs; JSON/new7-case test/restoring mutant and field provenance committed untested before slot work. Exact sources distinguish mos-1554 human/no-bot intent, sage-3820 roles, sage-3822 project/model, and Q247 tracker omission. No old test edits. Next runs: new file, business whole, CLI whole, mutant/restored file, serialized scratch TMPDIR timeout-k10.
2026-10-24 | Goldie | Row 323 / #1819 first slot STOP (sage-3861) | On committed571376c30: new file7/7 and business261/261 green; whole CLI hit unchanged revoke-window.test.mjs170 (test579, row306 R4), no RESULT after20.1s at ask167/Promise.all194. Stopped suite on observing red, cleaned only positively identified own scratch fixture. Partial631 pass/1 fail before termination, not full-suite green. No mutant/restored run, no old-test edit/rerun. Manifest unchanged; Sage notified goldie-0016 and slot returned. Evidence local under queue-323, awaits ruling.
2026-10-24 | Goldie | Row 323 / #1819 second red STOP (mos-1560, sage-3864) | Isolated revoke-window whole-file run1 on unchanged571376c30 failed0/1 after20.126s, same no RESULT at ask167/test194. Start00:24:22Z, load1.46/1.68/2.70, busyhelperCLEAR, fresh scratch goldieBObDQ9. No run2/3/CLI retry/mutant/restored run; no source/old-test edit. Sage goldie-0018 has log/load/error; slot returned, awaits base-dd6cb5872 root cause. Remote remains unverified571.
2026-10-24 | Goldie | Row 323 / #1819 accepted evidence (mos-1561, sage-3869) | Sage traced revoke-window reds to37-byte TMPDIR making109-byte tools.sock >107 limit (session.mjs278), not Codex. Fresh31-byte /scratch/tmp/XXXXXX confirmation passes1/1 in0.84s. On unchanged571376c30 serialized timeout-k10: business261/261, new7/7, tester-switch mutant killed/restored SHA85e6b9ac, restored new7/7. Every log starts UTC/load/helperCLEAR; all positive runs0 fail/skip/cancel. CLI whole acceptance comes from Sage gate under mos-1561; no local CLI rerun. Evidence-only notes/log commit follows, no old test/source changes.
2026-10-23 | Fethry | Row 225 T1 expanded evidence complete (sage-3647). Full library81/0 and channel-proof2/0; final targeted renderer9/0, routes13/0, browser9/0. Isolated kit14/14 assertion kills on507901277, every target restored/hash-checked, candidate hashes unchanged. First attempt stopped at a wait-based failure, recorded as not killed; corrected the new test's repaint wait, baseline9/0, and reran the whole kit. No processes left; lane A returned around19:29Z. Source manifest for Dewey prepared; full webui/build and Sage's integration/scripts gate still pending. Raw logs preserved in scratch, normalized display copies and raw SHA256 provenance in agent evidence.
-8
View File
@@ -1,8 +0,0 @@
# Beakley
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 217 (C2-06, handed over from Launchpad).
No launcher in this directory. The seat runs as a T3 thread (thread 88b97f65) and works in its own worktree. It has no
credentials of its own; it pushes only its own `beakley/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-8
View File
@@ -1,8 +0,0 @@
# Boyd
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 298 (the chief-of-staff grant scope, handed over from Megavolt).
No launcher in this directory. The seat runs as a T3 thread (thread 48a3ae38) and works in its own worktree. It has no
credentials of its own; it pushes only its own `boyd/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-272
View File
@@ -1,272 +0,0 @@
# Row 298 (#1804, Q228): chief-of-staff grant scope
Owner Boyd (48a3ae38), reviewer Dewey. Branch `boyd/row298`, base `45dca69f6`.
Assignment: mos-1496/Q245. Handoff: Megavolt megavolt-0203, local `7a141269c`;
cherry-picked as `4de3cec27` (design) and `7cea739de` (UNTESTED code).
Brief: `docs/plans/2026-10-23_mvp-readiness-rows.md`, chief-of-staff grant scope.
Megavolt's original design remains in `agents/megavolt/work/queue-298/BUILD.md`.
## Authority and accepted design
Sage sage-3594, restated in sage-3612: launch.grantScope, lenient read with empty
fallback, strict human-only write. No general organization setter may write it.
Per-method agents/projects or agents/hosts/commands; host-enroll never in scope.
Host command arrays match whole tokens and exact lengths. `grants.scope.set`
from the human CLI is Approve; no Console page or chief-raised scope change.
Mos mos-1496/1502 requires the same human-shell guard as `mosaic access`.
The broker closes an in-scope request via scope and writes grant.scope.used;
missing evidence must refuse execution. Narrowing before spend fails
scope-changed. Empty scope ships; only Jason sets the live value (Q188).
No credential files read, no live grants, no deployment, no queue writes. Push
only boyd/*, additive commits, no amend/force-push or attribution trailers.
Sage owns queue, shared test slot and landing. No Node/tests before CLEAR.
## Implementation plan and ledger
Using writing-plans and executing-plans inline; the approved brief and D1-D6
are the spec. This packet is the requested plan/tracking location.
- [x] Read brief, roster, implementation and handoff; register session.
- [x] Bring Megavolt's two commits onto Boyd's branch without changing history.
- [x] Add independent CLI tests in `packages/cli/tests/grants-scope.test.mjs`:
scope read/set requests; agent-marker rejection before stdin/transport;
--tester rejection; missing revision, malformed/empty/oversize input.
- [x] Strengthen new business/bus grant-scope tests for exact commands,
scope evidence binding, host-enroll exclusion and spent scope-changed.
- [ ] On Sage CLEAR, observe targeted failures before fixes; fix inherited
implementation using the existing human proof and grant executors.
- [ ] Update bus/CLI docs, append BUILD-LOG before/after evidence, manifest.
- [ ] On Sage CLEAR run touched suites; record exact results and mutants.
- [ ] Commit UNTESTED until verified; push boyd/row298 only when slot permits;
report to Sage and Dewey, cc Mos for pushes/blockers. Dewey reviews manifest.
Pre-flight interfaces: business checkGrantScope/grantScopeOf/grantInScope feed
the instance writer and broker. Runtime supplies a fresh scope from the org
file and routes the human setter to the same organization writer. CLI callFor
supplies refuseInsideAgent and humanTransport. The grant executors consume the
broker's immutable close/evidence. Tests use scratch files/fake Gitea only.
Review focus: exact argv length, stale scope before spend, forged/missing scope
evidence, malformed fallback, and CLI agent ancestry. Tests must cover each.
Static findings before any Boyd Node/test run:
- Handoff introduced commandPrefixes beyond the accepted D2 shape, allowing
trailing args. Remove it; only exact command arrays are authorized.
- scope.used carries a digest without validating it, and no org revision.
Bind evidence to the canonical scope snapshot and grant/close sequence;
preserve historical grants when scope is later changed, recheck at spend.
- Gitea recheck throws inside the spend transaction and rolls back spend on
scope-changed. Preserve spend and log failure for that case (D6).
- The optional giteaStart?. assertion always passes when the API is absent;
use the real runGiteaGrant entry point. Fake Gitea tokens() does exist.
## Existing test changes (mos-1272)
Approved by Mos mos-1510 via Sage sage-3624; edited exactly as approved:
| File | Actual numstat | Reason |
| --- | --- | --- |
| packages/bus/tests/project-runtime.test.mjs | +1 / -1 | Vocabulary import pin adds grantScopeOf and grantInScope. |
Other test work is confined to row 298's new files. No pre-existing CLI tests
will change. Record final numstat here before review.
## Human proof and organization ownership
CLI: `packages/cli/src/cli.mjs` callFor runs refuseInsideAgent before constructing
humanTransport, as `accessCommand` does. The setter takes no --tester flag.
`packages/bus/src/human.mjs` verifyHuman checks the nonce, CLI path and every
ancestor's markers/argv; `bindHuman` and #human in broker.mjs require the
business human, via cli, outsideAgent true. Tests will pin this chain's caller
refusal. The scope receipt records by/via/outsideAgent and old/new sha256.
Org file: `packages/business/src/organization.mjs` regular() requires a regular,
non-symlink file owned by the process uid and not writable by group/other;
write() uses revision checks, locked durable receipts and atomic rename.
The OS owner uid can edit it directly; the agent sandbox does not mount the
user's config (`packages/seat/src/sandbox.mjs`). This is the existing cooperative
human/process boundary, not a hostile same-uid isolation claim.
Chief enrollment: broker instances.set calls #human and #mayHuman before
add/change; credential issue/delete use #requestCredential's human gate.
launch.by is an organization-file setting, excluded from general setters;
only that chief may auto-approve within scope. Additional chief instances
remain on the per-grant human approval path.
## Validation
UNTESTED. No Boyd Node/test run yet; waiting for Sage CLEAR.
M1: bypass in-scope check. M2: bypass invalid-value fallback. M3: remove human
setter guard. M4: drop grant.scope.used. All need named failing tests on gate.
## Session correction
The initial SESSIONS entry labels startup 19:13Z; startup was approximately
19:08Z. Message header timestamps in boyd-0001 through 0004 were estimates;
use T3's receipt timestamps as authoritative. Subsequent reports use date -u.
## Departures from Megavolt 7a141269c (Sage sage-3624)
1. Removed commandPrefixes; host scope commands match exact token arrays only.
2. grantScope callback now supplies one organization read `{value,revision}`.
grant.scope.used includes that revision, canonical scope snapshot and digest;
#approvedGrant validates the digest, shape, method, agent and close sequence.
It uses the immutable snapshot so later narrowing doesn't revoke issued grants.
3. Gitea scope-changed is caught inside the spend transaction and logs a terminal
failure without rolling back its spend. Other recheck failures keep their
existing behavior. Host already catches recheck failures after spending.
Additional handoff corrections: replaced optional nonexistent giteaStart with
runGiteaGrant; added successful host executor/revoke and enrollment-exclusion
coverage; added real runtime writer coverage and independent CLI guard tests.
Sage scheduled push at 19:41Z with MOSAIC_GITEA_CREDENTIAL_FILE unset, then T1
lane B around 19:47Z (after explicit CLEAR), four new files whole and serial,
hard stop 20:00Z. Fethry lane A is disjoint. No test was run early. Production
corrections are marked UNTESTED under that slot mandate; baseline regression
checks against the inherited implementation will run only on CLEAR.
## Candidate preparation (before T1)
The three static corrections, CLI/runtime coverage and docs are prepared but
UNTESTED. `git diff --check` is clean. The approved import-pin edit is exactly
1 addition / 1 deletion. M1-M4 are encoded in mutants.py (not run). No Node,
https git, full suite or webui build has run in Boyd's worktree.
Review package: product/test/documentation files in MANIFEST.sha256, excluding
append-only project logs and the preserved predecessor packet. The manifest
identifies exact bytes for Dewey; each later product change regenerates it and
requires a review delta. Existing tests changed: only project-runtime, 1/1.
## Dewey static review dewey-0933
N1: Added a positive control to the malformed-evidence fixture. A valid event
must pass scope authorization and reach decision-mismatch on the deliberately
absent definition binding; invalid events remain not-authorized. No spend.
N2: Additional named mutants cover spend recheck, exact argv length, Gitea spend
rollback, launch.by eligibility and accepting any evidence row (results below).
N3: At spend only grant scope membership is rechecked, not whether the raising
chief is still launch.by. This implements sage-3594 Q5; changing the launcher
later does not by itself invalidate a pending scope-approved request.
N4: The scope shortcut returns the closed decision view to the chief without
sending a separate close message; documented in the bus verb table.
N5: grant.scope is logged after the organization file commit in a separate bus
transaction. A crash between them leaves durable changes.jsonl as the receipt,
as with other organization writers. The setter does not claim cross-store
atomicity.
## T1 lane B results (sage-3642, 2026-10-23 19:23Z–19:29Z)
TMPDIR: `/mnt/storage/scratch/tmp/boyd298-4bzr6fn7`. Each whole file ran serially
as `timeout -k 10 180 node --test --test-concurrency=1 FILE`, TMPDIR set to that
scratch directory. Final restored candidate results:
| Whole file | Pass | Fail | Skip |
| --- | ---: | ---: | ---: |
| business/tests/grant-scope.test.mjs | 7 | 0 | 0 |
| bus/tests/grant-scope.test.mjs | 14 | 0 | 0 |
| bus/tests/grant-scope-runtime.test.mjs | 1 | 0 | 0 |
| cli/tests/grants-scope.test.mjs | 4 | 0 | 0 |
| bus/tests/project-runtime.test.mjs | 3 | 0 | 0 |
29/0/0 total. `final-1.log` through `final-5.log` hold the final output. The
runtime's reload-needs-restart notice is expected: the fixture's boot document
has fewer credential references than its organization document, as in the
existing instances runtime tests. No credential contents are loaded from the
real user config.
Initial run: business 7/0; bus 13/1 due to Boyd's test typo, an out-of-scope
`opts` reference in the missing-scope assertion. Corrected only that new-test
line, then bus 14/0 and the remaining files green. Dewey N1 positive control
was added and the whole bus file passed again; final rerun above followed all
mutants. No production code changed during this test window.
Mutants: all whole files, serial, with timeout -k 10 and the same TMPDIR.
`mutants.py --sage-clear sage-3642 --logs SCRATCH/review-mutants --deadline
2026-10-23T19:38:00+00:00` restores exact original source after each run.
M1/M2/M3/M4 are brief/Sage mutants; M5–M10 cover review focus and dewey-0933.
Each exited 1 with named assertion failures, no loader/syntax kill.
| Mutant | Change | Pass/fail | Named killer |
| --- | --- | --- | --- |
| M1 | skip raise scope check | 11/3 | bus out-of-scope Gitea waits; empty/bad scope waits; host argv exclusion |
| M2 | skip invalid-value fallback | 5/2 | business missing/bad scope fallback; bad file scope fallback |
| M3 | remove #human from scope verb | 13/1 | bus Q3 caller refusals |
| M4 | drop grant.scope.used write | 9/5 | bus Q4 protected evidence assertion; four executor waits also fail |
| M5 | skip scope digest check | 13/1 | bus Q4 bad evidence: decision-mismatch instead of not-authorized |
| M6 | rethrow narrowing, rolling spend back | 13/1 | bus Q5 Gitea spent assertion |
| M7 | same without length check | 6/1 | business Q2 shorter command prefix exclusion |
| M8 | no-op #scopeRecheck | 12/2 | bus Q5 Gitea and host scope-changed outcomes |
| M9 | remove launch.by check | 13/1 | bus Q6 second chief stays open |
| M10 | accept any matched scope evidence row | 13/1 | bus Q4 bad evidence, with positive control |
Mutation log sha256 (under scratch/review-mutants):
```text
55e9973af5fca0088963e2ca11fd81b2fb46dfead813a577c0e0aad2d6269b68 M1.log
abd1b784eff5ae1333a7b47085d3e199cad6d27a00e2584edf0b62417ad4c888 M2.log
1a56085e548d1e244f0f88fe12a45f77c162375880794f9ef88b241ed3a1704d M3.log
9c120f3f7128488edaf8bfd6d41bae712e69e76da4195a0d4dc0e8336b02038f M4.log
1bba88a3752823454acdaa51abde6aad66fdd5488801b4aceda02a3b42c8123d M5.log
a4ce3cacca9f7ea56b8cdfd503c76173a8e4ff1d8a7ec07032c93f0b51aa60d8 M6.log
acecd5afc93d68d96d713f8a9a2dc08351831a0b7360a13b4e30a572ad4167bf M7.log
fce7fa505ccf4f2d4190bd03c209fd19f1cf2770d3ae1246a3bd03697594019e M8.log
a830a7964890dd04a2dcc7a932a1502f1dea06419a1eae6b91a85f7878dc5681 M9.log
38ce3e7f69f6c6ffe10133397465d52538a930a830f76256a8b9280f8df78e4b M10.log
```
Full touched-package suites and every scripts/test-*.sh remain for Sage's full gate; this CLEAR authorized only the five whole files. Candidate remains
marked Untested for the full gate. No queue row has been changed by Boyd.
## Dewey final delta review (dewey-0935, 2026-10-23 19:31:55Z)
Dewey reviewed 574ece13c..bcd93dc08 with local git only, no Node: CLEAN,
nothing blocks. Verified all 13 manifest entries, exact package-file coverage,
project-runtime 1/1, M5-M10 mutation sites, all ten mutation log hashes and
counts, named assertion kills without loader/syntax errors, final five logs
29/0/0, and N1-N5 coverage. No production code delta in this review range.
Formal Gitea APPROVE on #1804 and queue record wait on the row 291 hold release
and Sage's go. Sage's touched-package and every scripts/test-*.sh gate remains
required. Boyd has released lane B and has no running test processes.
## Rebase after row 239 (sage-3794)
Current branch: `boyd/row298-r2`, rebased from published row 298 tip
`2fc27bce6bfac2debe9703782bf308132c26b393` onto
`833ec1cc8d7c11842efb82e57c1a0b256c8d22d7`, which includes row 239 through
`e0cb1db2c`. Original `boyd/row298` remains intact locally and remotely. A new
Boyd branch carries the rebased candidate so no published history is amended
or force-pushed. This section supersedes the original base/branch header for
current candidate work.
Conflicts: only the first Boyd log append in `BUILD-LOG.md` and
`docs/SESSIONS.md` conflicted while replaying 574ece13c. For each, verified the
old commit's log started with the merge base bytes, then retained the new
base's complete log bytes and appended only Boyd's suffix (434 and 281 bytes
respectively). Later Boyd appends replayed automatically. No prior entries
were removed or rewritten. Five commits replayed; the third/fourth range-diff
entries differ only in append-log context; package-only range-diff is all `=`.
The overlapping product files merged automatically: broker.mjs, runtime.mjs
and instance-settings.mjs had no textual conflicts. Inspected their diff
against the new base: only the existing row 298 scope additions remain.
Preserved row 239's injected writer, fire/delete beforeWrite callback, and
commit() passing beforeWrite into writer.write. There is no pre-lock revoke
call in commit(). `packages/business/src/organization.mjs` is byte-identical
to the new base: beforeWrite still runs inside withLock, after revision and
validation checks; post-revoke errors still carry revoked true. Broker's
row-239 error handling and runtime's lock/revoke wiring remain intact.
Static checks only: package patch range-diff all equal, organization writer
unchanged, diff whitespace clean, refreshed 13-file package manifest verifies.
The only pre-existing row-298 test change remains project-runtime.test.mjs
exactly 1/1 under mos-1510. No test files were changed for this rebase.
UNTESTED on the new base: no Node, tests, mutants, builds or queue writes run.
Earlier 29/0 and M1-M10 results apply to the old base only. Await Sage CLEAR
for whole touched-file reruns; Dewey reviews the new candidate.
Row 321 (#1817) review assignment acknowledged; waiting for Sage's request
and Gladstone's pushed docs-only F7/F10 run-sheet candidate.
@@ -1,13 +0,0 @@
612488b705853e75e6f8952e41552526f4d07dd297d7619d608d434ed75121d6 packages/bus/README.md
63fdf6c048f0994af3551510119eeb294c71f8116702772844214930b827cc56 packages/bus/src/broker.mjs
65425ead353017c99e04f1e0bcc3f85c4ba60054da293badaa0360e681e418dc packages/bus/src/runtime.mjs
e7065db949cacfd4679a1031eebf5cde09ed196ce85612e8400ae563f02a0f18 packages/bus/tests/grant-scope-runtime.test.mjs
4030cf584481a1db12d9e6f744015c48fdebd468335d065ead3e90180858d366 packages/bus/tests/grant-scope.test.mjs
bc432d8d430433b9d84c00883584dc53d4b27afb5e93f739fe3ca28e80f01efa packages/bus/tests/project-runtime.test.mjs
84d6dd15d5ca2926d8f7a34548c70c3e7a3c58e692f0b009414f1af35cb096cc packages/business/src/business.mjs
e3b425f9ad2a0d4e6e0b55bd48827e144015662d95ace4163fe6474e5f98da81 packages/business/src/instance-settings.mjs
fed4177325f65a2b9823a40a9c35c01d60c0520e3ff40b4574c3cf2c29cc096b packages/business/src/vocabulary.mjs
b7f96e45b9ffca2a7ebc18dc446f0adcaac72d3900f098c60157cf6cbf1bfc13 packages/business/tests/grant-scope.test.mjs
60fc267b7f8f9e83eeb0d0207c8abdef68d554f32b08d508e98dbf59b442a366 packages/cli/README.md
b586907ca8f351f60dc19ec2683df75d2d70ddb12955d09fb14560c9d3bed3dc packages/cli/src/grants.mjs
fb7c30b31facd887b03ff02dd6734d48a815978481b33bb21ec48604a51c5ff5 packages/cli/tests/grants-scope.test.mjs
-105
View File
@@ -1,105 +0,0 @@
#!/usr/bin/env python3
"""Row 298 mutants. Run only with Sage's explicit CLEAR in Boyd's slot.
Each mutation is applied to this worktree, runs one whole new test file,
then restores the exact original bytes, including after an interruption.
Logs go to a caller-supplied scratch directory, never the repository root.
"""
import argparse
import datetime
import pathlib
import subprocess
ROOT = pathlib.Path(__file__).resolve().parents[4]
BROKER = ROOT / 'packages/bus/src/broker.mjs'
VOCABULARY = ROOT / 'packages/business/src/vocabulary.mjs'
BUS_TEST = 'packages/bus/tests/grant-scope.test.mjs'
BUSINESS_TEST = 'packages/business/tests/grant-scope.test.mjs'
def replace_once(source, old, new):
if source.count(old) != 1:
raise RuntimeError(f'expected exactly one mutant site, found {source.count(old)}')
return source.replace(old, new, 1)
def mutate(name, source):
if name == 'M1':
return replace_once(source,
'if (!raise?.grant || !grantInScope(scope, raise.grant)) return view;',
'if (!raise?.grant) return view;')
if name == 'M2':
return replace_once(source,
'return scope === null ? { scope: GRANT_SCOPE_EMPTY, fallback: true } : { scope, fallback: false };',
'return { scope: scope ?? value, fallback: false };')
if name == 'M3':
return replace_once(source,
' #grantScopeVerb(s, verb, a) {\n this.#human(s);',
' #grantScopeVerb(s, verb, a) {')
if name == 'M4':
old = """ this.#grantEvent(s.business, {
action: 'grant.scope.used', grantDecision: d.id, decisionSeq: seq, method: raise.grant.method,
agent: raise.grant.agent, scope, revision, scopeSha256: sha256(JSON.stringify(scope)),
}, d.id);"""
return replace_once(source, old, ' // MUTANT: scope evidence omitted.')
if name == 'M5':
return replace_once(source,
"evidence.agent === grant.agent && evidence.scopeSha256 === sha256(JSON.stringify(checked.scope)) &&",
"evidence.agent === grant.agent &&")
if name == 'M6':
start = source.index(" checked = this.#store.transaction(() => {\n const consumed = this.#consumeGrant")
end = source.index('\n } catch (e) {', start)
return source[:start] + " checked = this.#store.transaction(() => this.#giteaRecheck(business, decision, this.#consumeGrant(business, decision, 'gitea')));" + source[end:]
if name == 'M7':
return replace_once(source,
'const same = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);',
'const same = (a, b) => a.every((x, i) => x === b[i]);')
if name == 'M8':
return replace_once(source,
" if (this.#closed(decision)?.via === 'scope' && !grantInScope(this.#scopeNow(business).scope, g)) fail('scope-changed');",
" // MUTANT: no spend-time scope recheck.")
if name == 'M9':
return replace_once(source,
' if (s.role !== this.#business(s.business).launch?.by) return view;',
' // MUTANT: any chief instance can use scope.')
if name == 'M10':
start = source.index(' const evidence = JSON.parse(row.body);', source.index(' #scopeUsed('))
end = source.index('\n }', start)
return source[:start] + ' return true;' + source[end:]
raise ValueError(name)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--sage-clear', required=True, help='CLEAR message key; required before any Node run')
parser.add_argument('--logs', type=pathlib.Path, required=True)
parser.add_argument('--mutant', choices=['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'all'], default='all')
parser.add_argument('--deadline', default='2026-10-23T19:38:00+00:00')
args = parser.parse_args()
deadline = datetime.datetime.fromisoformat(args.deadline)
args.logs.mkdir(parents=True, exist_ok=True)
names = ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10'] if args.mutant == 'all' else [args.mutant]
for name in names:
remaining = (deadline - datetime.datetime.now(datetime.timezone.utc)).total_seconds()
if remaining <= 15:
raise RuntimeError('Sage slot deadline reached; no new test run')
path, test = (VOCABULARY, BUSINESS_TEST) if name in ['M2', 'M7'] else (BROKER, BUS_TEST)
original = path.read_bytes()
mutated = mutate(name, original.decode())
log = args.logs / f'{name}.log'
try:
path.write_text(mutated)
with log.open('w') as output:
result = subprocess.run(['timeout', '-k', '10', str(int(min(180, remaining - 13))), 'node', '--test', '--test-concurrency=1', test],
cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
timeout=min(195, remaining - 1), check=False)
print(f'{name}: exit={result.returncode} log={log}', flush=True)
finally:
path.write_bytes(original)
# Exit 1 needs human inspection of the failing test names; errors are not kills.
if result.returncode == 0:
raise RuntimeError(f'{name} survived')
if __name__ == '__main__':
main()
-8
View File
@@ -1,8 +0,0 @@
# Bubba
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 288 (agent write notices and library revert, handed over from Honker).
No launcher in this directory. The seat runs as a T3 thread (thread 3d6672b5) and works in its own worktree. It has no
credentials of its own; it pushes only its own `bubba/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
@@ -1,30 +0,0 @@
# Row 239 round 1 verdict: honker/row239-r4 e0cb1db2c (duckworth-0295)
Request: honker-0206 (in-review round 1, rev 3719). Candidate: e0cb1db2c on refactor 546366f4a.
Method: git reads of local objects only. No node, no tests, no fetch, no queue reads.
## Verdict: APPROVE
- **The replay is exact.** `git range-diff 93694c518..5b8492b0f 546366f4a..0cae2dad5` gives four `=`
lines. None of the 16 manifest paths changed between 93694c518 and 546366f4a, and
`git diff 5b8492b0f 0cae2dad5` over those 16 paths is empty. My r3 read (REBASE-r3-5b8492b0f.md,
duckworth-0292) applies unchanged, and so do the 0165 design approval and the 0168 code approval with
C1 and C2 met.
- **e0cb1db2c is doc only.** It appends 26 lines to agents/honker/work/queue-239/BUILD.md. No test reads
that file.
- **Pins at e0cb1db2c.** All four 64-hex pins in channel-proof.test.mjs match the files' sha256:
human.mjs, human-cli.mjs, transport.mjs and webui bus.mjs (e3f52172…). Upstream changed library
src/index.mjs and business src/index.mjs, and no test or script pins them. The only hit in my grep
is a basename collision (`cli.mjs` matched `human-cli.mjs`).
- **The run, as Honker reported it (sage-3773 CLEAR, on 0cae2dad5. I didn't rerun it):**
- The six touched test files ran whole: 65/65, 0 skipped, no first-run red.
- Mutants M1-M3g: 9/9 killed as assertion failures in their named tests.
- M3f killed T6, which matches my static trace in 0292.
- Every restore matched by sha256, and the tree was clean afterwards.
Running on the replay rather than r3's base was right, because bus runtime.mjs imports library
src/index.mjs and the cli imports business src/index.mjs, and both changed upstream.
- **What remains is Sage's.** The full gate runs on the replay at landing (sage-3730). Sage's call.
## Notes
- None new. 0168 N1 (endsWith access-revoked) stands as recorded.
@@ -1,56 +0,0 @@
# Row 324 round 1 verdict: goldie/row324-r2 dbdfcfd52 (duckworth-0298)
Request: sage-3853 (#1820, comment 31469, round 1, rev 3768/3769). Candidate: dbdfcfd52 on refactor
3569bf54f, code commit 7e59ba8b3. Brief: docs/plans/2026-10-23_optional-tracker-brief.md.
Method: static. Git reads of local objects only. No node, no tests, no fetch, no queue reads.
## Verdict: APPROVE (static)
- **The manifest matches.** agents/goldie/work/queue-324/MANIFEST.sha256 hashes to 52874356…2b473d,
the digest Sage gave. All 8 listed blobs match the files at dbdfcfd52.
- **The loader change is narrow.**
- business.mjs:239 drops "tracker" from the required-key loop.
- business.mjs:296 is `Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null`.
Omission gives `tracker: null`. A present key always goes through checkTracker.
- business.mjs:298 guards the role tracker comparison on `tracker &&`. An absent business tracker
doesn't waive the role pm tracker check, and the new test covers that.
- **A present null still refuses.** checkTracker (business.mjs:85) calls requireObject first, so a
present null, false, [] or a non-object refuses. {} and {sync:{}} refuse further in. The new
refusal test covers each of them.
- **Every business.tracker reader handles absence.** I read each hit in Goldie's reader-audit.txt and
the two outside it in scripts:
- bus runtime.mjs:131 registers `<business>/@sync` only under `b.tracker?.sync?.credentials`. An
absent tracker registers nothing.
- bus runtime.mjs:247 hashes `b.tracker?.sync ?? null`.
- broker.mjs:6242 reads `b.tracker?.sync?.bot`. Broker 3969 and 4497 read the role-level tracker
only.
- cli.mjs:85 and webui reads.mjs:166 use `tracker?.sync?.credentials ?? {}`. The
`b.tracker.sync.bot` inside the reads.mjs loop runs only when there are entries.
- cli config.mjs:58 trackerFor returns null on an absent tracker before the vars check. bootConfig
(config.mjs:138-139) sets config.trackers only for a non-null result.
- tasks/adapter.mjs loads only for configured trackers (process.mjs:136), and its settings refuse
with tracker-config at :25 before the :35 and :81 reads.
- instance-settings.mjs:127 reads `old.tracker?.sync`.
- stack-release.sh:2074 and :2120 use `b.tracker?.sync?.credentials`.
- launch-settings, resolve, instances.mjs and public/bus.js:785 read role-level trackers only.
conversation/controller exec.tracker is a different object.
- **Writers don't serialize the loader's null.** The setters build `next` from the raw file bytes
(organization.mjs:88-93), not from the loaded object, and validate before writing
(organization.mjs:201, :208). A business file without a tracker stays without one.
- **The brief's tests are there.**
- business/tests/optional-tracker.test.mjs has 5 tests: omission loads null, CLI validate with the
sync file gone, a present tracker preserved and frozen, the refusal set, and the role pm check.
- cli/tests/optional-tracker.test.mjs has 4 tests: no trackers from bootConfig with tracker vars
set, startBroker with no @sync credential, businessView with no credentials, and a present
tracker with a missing token refusing credential-file.
- The existing-tracker case runs through business.test.mjs unchanged apart from :98.
- **The one old-test edit is in bounds.** business.test.mjs:98 drops "tracker" from the
required-key loop, as mos-1556 allows.
- **The run, as Goldie reported it (sage-3850 CLEAR, I didn't rerun it):** 5/5, 4/4 and
business.test 21/21. The presence mutant at business.mjs:296 is killed, and the restore matched
sha256 e76db421.
## Notes
- N1 (doc). The 'undefined' refusal case only exists in memory. A JSON file can't carry undefined,
so on disk it is the same as omission. The test is still correct.
-8
View File
@@ -1,8 +0,0 @@
# Fethry
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 225 (C2-10b, handed over from Morgana).
No launcher in this directory. The seat runs as a T3 thread (thread 14f80e3c) and works in its own worktree. It has no
credentials of its own; it pushes only its own `fethry/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
+376
View File
@@ -0,0 +1,376 @@
# Row 225 (C2-10b) Console chat: replies, citations, write notices, status line
Owner: Morgana, from sage-3582 (18:53Z 10-23). Base: origin/refactor 45dca69f6. Branch: morgana/row225.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-10b. Design: agents/gosalyn/work/case2/DESIGN.md 7.4,
5.2 (the reply schema and the check result) and the write notice and `library.revert` (about line 286).
## Ruling on the dependency gap (sage-3591)
225 builds against the design contracts. `renderReply` stays pure, and the caller supplies the answers
it can't know. The revert route proxies to the bus verb and shows the refusal code until the verb
exists. Landing order: 225 lands after 217 (C2-06), not after 288 (C2-02). Sage adds `after 217` to
row 225. Before 288 lands, the Revert button gets the bus's refusal and shows its code, as the brief
allows.
## A1. The check result I assume (to 217, Launchpad)
The broker stores one check result with each agent-to-human message the guard passes. The Console reads
it on the message row that `/api/chat/threads/<id>/messages` returns, under the key `check`:
```json
{
"check": {
"citations": [
{ "scope": "canon", "agent": null },
{ "scope": "supporting", "agent": "acme/scribe" },
{ "scope": "web", "agent": null }
]
}
}
```
- `check` is `null` or absent on a message the guard didn't check: a human's message, a free-text reply,
a notice, or a message from before 217.
- `check.citations` has one entry per entry of the body's `citations`, in the same order.
- `scope` is one of `canon`, `playground`, `supporting` and `web`. A url cite is `web`.
- `agent` is the `Mosaic-Agent` trailer value, `<business>/<instance>`, from
`git log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>`, or `null` when the
file's last commit at `commit` carries none. A url cite has `agent: null`. The Console shows the
agent-written label when `agent` is a nonempty string, and names the agent.
The Console trusts nothing in the body for scope or label. A cite with no matching check entry renders
with no badge and no label. It doesn't fall back to a scope it worked out itself.
If 217 uses other names, I change mine to theirs before either of us pushes (sage-3591 item 1).
## A2. The revert verb I assume (to 288, Honker)
- Verb: `library.revert`. A human verb (design M4): from an agent session it refuses `human-required`,
like `library.status` (`broker.mjs:5133`).
- Args: `{ "project": <business project name>, "commit": <40 lowercase hex> }`. The design writes
`library.revert {commit}`. `project` is added to match the other library verbs, and like theirs it is the
key of `business.projects` (`mirror.mjs:31`), which is the chat project's `repo` link, not its UUID. Both are required, and no other key
is allowed (`invalid-request`), as the other library verbs check with `keys(args, ...)`.
- Result: `{ "project", "path", "reverted": <the commit given>, "commit": <the new commit> }`.
- Refusal codes the Console shows by name:
- `library-revert-not-agent`: `commit` has no `Mosaic-Agent` trailer for this business, or isn't
reachable from the served commit.
- `stale-base`: a later commit changed that path.
- `human-required`, `invalid-request`, `project-not-configured`.
- Before 288 lands, the bus refuses the verb (expected `unknown-verb`, `broker.mjs:6578`), and the Console
shows that code.
- HTTP status in the Console (`packages/webui/src/bus.mjs` `busStatus`): `stale-base` and
`library-revert-not-agent` map to 409. Every other code keeps the existing mapping.
## A3. The status line
`GET /api/library/status?project=<id>` proxies `library.status {project}` through the existing
`libraryStatus` option. `cli.mjs:110` already passes that option, but `startServer` doesn't read it.
The status line shows:
- the served commit (7 characters) and its age;
- `library-diverged` when the sync state names it;
- the fetch error code;
- the reply limits in force, and the keys that fell back;
- the count of R-problems, linking to the list.
It reads the fields `readSyncState` returns at the base (`packages/library/src/mirror.mjs:232`). It shows
any field 217 adds (the limits) only when present.
## A4. "Changed since" (ruled sage-3596)
Sage ruled that 217 supplies `last`. On a `thread.messages` row that carries `check`, each path cite's
entry gets `last`, the file's last commit at the served tip, read when the message is read. When `last`
is absent the Console marks nothing changed, and a test pins that.
One more field is needed, raised in morgana-0200. The cited `commit` is a library commit, not the
file's own last commit, so a cite made at a commit that didn't touch the file would differ from `last`
while the file is unchanged. I asked 217 to also store `file`, the file's last commit at the cited
commit. That's `%H` from the same `git log -1 <commit> -- <path>` call that reads the trailer, so it
costs no extra git call. The Console marks a cite changed when both `file` and `last` are 40-hex and
differ. The cited commit is reachable from the served one (5.2), so `file` is an ancestor of `last`,
and differing means newer. A missing or malformed `file` or `last` marks nothing.
The entry the Console reads, in full: `{ scope, agent, file?, last? }`.
## A5. `no-cited-answer` retires
`renderItems` and its `NOTICES` (`no-cited-answer`, `no-answer`, `turn-failed`) go. 217 sends the
runner's fixed texts as plain bodies (design 5.2, "Runner texts"), and renderReply shows those as free
text. Design 7.4 names no other notice than `library-write`, so nothing is lost (sage-3596 item 3).
## Files
Both deviations below were accepted in sage-3596 and are named in the manifest for Dewey. The brief owns these:
- `packages/library/src/render.mjs`: `renderReply` replaces `renderItems`.
- `packages/library/src/index.mjs`: the export.
- `packages/library/README.md`: the section.
- `packages/library/tests/render.test.mjs`.
It also owns `bus.js`, its stylesheet, `serve.mjs` (only the two routes) and new webui tests. Two
deviations, both recorded here:
- The chat view's message list is `packages/webui/src/public/chat.js:85` (`window.MosaicChat`), not
`bus.js`. The reply, notice and Revert rendering goes in `chat.js` and `chat.css`. The status line
goes in the chat thread view, so it is also in `chat.js`.
- `packages/webui/src/console-bus.mjs` and `cli.mjs` each get one `libraryRevert` line, beside the
existing `libraryStatus` wiring.
## Overlap with row 220 (C2-10a, Researcher, 3ef6da90d)
220 changes one line each in the webui tests discord.test.mjs, fixture.mjs, history-fixture.mjs,
relaunch.test.mjs and return-flow.test.mjs. Its other paths are in control-board, conversation, discord
and business. 225 doesn't touch those five files or those packages. Its webui tests are new files.
## Handoff to Fethry (mos-1501, Q245)
The row moved to Fethry (Codex, fethry/row225). Everything below is **untested**: no suite or single file
has run on it, since a run needs Sage's CLEAR. Each changed file passes `node --check`.
Done in the handoff commit:
- `render.mjs` `renderReply(body, {check, changed, name})`; index export; README section;
`render.test.mjs` rewritten. `renderItems` is gone from every package.
- `serve.mjs`: `/render.mjs` static entry (owner and user file maps), `GET /api/library/status`
(one `project` query, answer must name that project), `POST /api/library/revert` (save's boundary,
strict `{project, commit}`, refusal shows the code, lost or unusable answer is `outcome-unknown`,
never retried). Both are nulled for a granted user, and `userRoute` keeps them owner-only. The tester
listener refuses `libraryRevert`.
- `bus.mjs`: `library-revert-not-agent: 409`. That re-pins `packages/webui/src/bus.mjs` in
`packages/bus/tests/channel-proof.test.mjs`, with a comment line, as row 254 did. It's a third deviation,
in another package's test.
- `console-bus.mjs` and `cli.mjs`: `libraryRevert`, beside `libraryStatus`.
- `chat.js`: dynamic `import('/render.mjs')` (CSP is `script-src 'self'`); body text until it loads.
Cites with a scope badge, the agent-written label, "changed since" from `file`/`last`, links only for
https with `rel="noopener noreferrer"`. A notice gets a Revert button behind `confirm()`. The library
status line goes under the thread heading.
- `chat.css`: cite, badge, notice and status styles.
- New tests: `packages/webui/tests/library-routes.test.mjs` and
`packages/webui/tests/chat-replies-browser.test.mjs`. 220's five files are untouched.
Decisions made in the build:
- The library name is the chat project's `repo`. A project with no repo reads no status and its
Revert does nothing.
- These codes hide the status line: `project-not-declared`, `library-mapping-invalid`,
`project-not-configured`, `not-configured` and `owner-only`.
- Otherwise every code-repo thread, and every granted user, would show "unavailable".
- Cost: a broken library mapping also hides the line. `library.status` from the CLI still shows it.
- "Age" is the time since `lastFetch` ("fetched 5 min ago"), not the served commit's age, which the
status doesn't carry.
- The limits fields are assumed to be `replyLimits {requireCitation, citationsPerReply}` and
`replyLimitsFallback [keys]`, and are shown only when present. **Not agreed with 217.** Confirm with
Beakley and rename.
- A notice body's shape is all the renderer checks. An agent in a business without a library could
send a body that looks like a notice. The page would show it, but `library.revert` still refuses it.
Left:
1. Get CLEAR and run the webui and library suites, `channel-proof.test.mjs`, and the two new files whole.
The browser test's selectors and waits have never run.
2. Agree the limits field names with 217 (Beakley).
3. Granted-user R6 G5 (`granted-user.test.mjs:252`) lists owner verbs by hand. Add `library.revert`
once 288 lands.
4. BUILD.md: the mutant list and the mos-1275 timeout plan.
5. The manifest names the three deviations: chat.js and chat.css in place of bus.js, the console-bus and
cli lines, and the channel-proof re-pin.
## Fethry continuation (2026-10-23, Q245)
Owner: Fethry, thread 14f80e3c-ace7-4793-a577-e1275ca09b0b. Branch fethry/row225.
This file carries Morgana's complete BUILD.md above unchanged, recovered from 9a131e956
(base 45dca69f6). Morgana stopped editing 225 at that handoff. Issue #1726; Dewey reviews.
Sage owns queue moves, test slots, gate and landing. No Node/tests until Sage CLEAR
(sage-3611); commits/pushes before the gate are explicitly Untested. No credential files read.
### Confirmed interfaces and rulings
- Beakley beakley-0004 confirms ordered check.citations {scope, agent, file, last}; a missing
or malformed file/last means no changed marker. Fetched age accurately describes lastFetch.
- Beakley's concrete library.status shape is replyLimits:
{requireCitation, citationsPerReply, fallback: [keys], problem: 'reply-limits-unreadable' | null}.
The inherited top-level replyLimitsFallback assumption is superseded. Show nested fallback and
problem, with a browser regression test for each. No broker or policy change in this row.
- Bubba bubba-0002 confirms A2 and the exact broker notice shape. fethry-0005 forwards the
project-key correction: project is chat project.repo, not the chat UUID.
- Keep the inherited ordinary-code-repo/granted-user status hiding behavior for now, but a
library-mapping-invalid error should stay visible: hiding an invalid mapping hides diagnosis.
Confirm this local UI choice through regression coverage and tell Sage/Dewey.
### Continuation plan and acceptance evidence
Spec: docs/plans/2026-10-20_case2-rows.md C2-10b; design 5.2 and 7.4;
standing rulings sage-3591, sage-3596 and sage-3611. Implement inline in this existing isolated
T3 worktree; use the existing candidate and tests, never restart the implementation.
1. Static audit the handoff against the contracts and inherited tests. Add failing coverage for
nested limits fallback/problem, wrong-project revert receipts, missing file/last and invalid
mapping visibility. Existing tests require Mos approval through Sage before further edits.
2. On Sage CLEAR, run the new files whole and library renderer tests with per-test deadlines;
record RED before changing uncovered behavior. Fix only demonstrated local defects. Run
targeted GREEN, then the full relevant suites only within the authorized slot.
3. Record exact commands, timings and output paths. Prepare mutant kill list below; run only on
CLEAR, using isolated copies so the candidate is never temporarily changed for mutation.
4. Freeze a manifest containing all source/test/doc changes and all three handoff deviations.
Push only fethry/*, as additional commits. Send exact head/results to Dewey and Sage; cc Mos
on pushes/blockers. Sage performs queue moves, gate and landing. No merge/release/live changes.
### Existing test changes requiring Mos OK through Sage (mos-1272)
Diff against 45dca69f6, inherited from Morgana 9a131e956:
| File | Added | Removed | Reason |
| --- | ---: | ---: | --- |
| packages/library/tests/render.test.mjs | 80 | 66 | Brief requires retiring renderItems and replacing its tests with renderReply coverage |
| packages/bus/tests/channel-proof.test.mjs | 3 | 1 | bus.mjs gains library-revert-not-agent:409; refresh that source hash and document why |
No further edits to these files pending approval. No edits to row220's five tests.
The new webui test files are additions against the row base.
### Timeout plan (mos-1275)
Every Node test run uses --test-concurrency=1 and --test-timeout=30000, under an outer timeout.
New library/route files: outer 120s each; new browser file: outer 240s. Full library and channel-proof:
outer 180s; full webui: outer 900s, MOSAIC_REQUIRE_EDITOR_BUILD=1. Full webui requires its own
packages/webui npm ci per agents/README.md and Sage CLEAR. Output redirects to an evidence log;
inspect tail, failure names and summary, never claim green from an exit alone. A timeout is a
failed run: stop, report the exact command/phase/last output, and diagnose before retrying.
Sage's gate owns every scripts/test-*.sh; no implicit slot or test authority from this plan.
### Mutant plan (pending CLEAR)
- M1: drop a path badge or the supporting label; cite browser assertions must fail.
- M2: allow http/javascript links; renderer and browser safe-link assertions must fail.
- M3: compare cited library commit to last instead of file to last, or treat a missing file/last as
changed; changed-since assertions must fail, including same-file/different-library-commit.
- M4: ignore nested fallback/problem; status browser tests must fail.
- M5: remove a revert fence (send, identity, exact Origin or Fetch Metadata), strict key/commit
validation or wrong-project receipt validation; route tests must fail with zero unexpected calls.
- M6: retry a refused/unknown revert; single-call route/browser assertions must fail.
- M7: remove granted-user library-route fence; owner-only route coverage must fail.
Progress: handoff and interfaces recovered; candidate untested. No completion claim.
Static evidence correction: the first pushed patch artifact used ordinary context lines, whose
single-space blank context lines make git diff --check report whitespace in the artifact. Regenerated
inherited-old-tests.patch with --unified=0; it is the same exact two-file change, applicable with
git apply --unidiff-zero. No source or existing test changed for this correction.
Mutation kit authored at mutants.py (11 concrete mutations across M1-M7), Python AST parse only.
It archives the committed packages plus their tracked fixture dependencies into disposable snapshots,
links the existing dependency install read-only in practice (tests do not install), enforces exactly
one mutation anchor and requires an assertion failure for a kill. It records source hashes before
and after. It has not run; anchors for the planned fixes intentionally require those fixes first.
No Node/tests/build/browser work has run in Fethry's session pending Sage CLEAR.
### Old-test approval and preservation condition (mos-1510, sage-3623)
Mos approves render.test.mjs +80/-66 and channel-proof.test.mjs +3/-1 exactly. No additional edits
are permitted to either file without a new Mos OK through Sage. The condition on the renderer:
- The old malformed-body fixed-line behavior no longer applies. Design 5.2 now says any body
outside the cited-object schema is free text whose text is the whole body. Rejecting it into a
fixed line would hide human/free-text/older messages and the four fixed runner texts. The new
render.test.mjs tests “free text renders as itself with no citations” and “a body without the
reply or notice shape is free text: the whole body, no citations” explicitly cover this new rule.
- The old no-options path-badge behavior still applies. The new render.test.mjs test “without a
check result a path cite has no badge, no label and is never changed” covers it, and the browser
test checks an unchecked path cite has no badge/label. No path inference fallback was added.
Dewey checks both against the frozen old-test delta. The two numstats remain 80/66 and 3/1.
### Test slot and shared protocol (sage-3623, beakley-0006)
Targeted lane A slot is planned around 19:47Z, after Gosalyn's 226 run and Sage's queue gap.
Wait for explicit CLEAR; the scheduled time alone grants nothing. Hard stop 20:00Z for Sage's
226 gate. Boyd's lane B bus/cli/business work is disjoint and does not count as a busy lane A.
The three requested files are the library renderer test and the two new webui files, whole/serial.
Beakley confirms reply-protocol.mjs exports only GUARD_CODES (the seven guard refusal codes) and
FIXED_TEXTS {empty, failed, uncited, tooLong}. It has no scope/notice constants or validator.
renderReply defines none of those codes or runner texts: it already renders the fixed bodies as free
text. It therefore needs no protocol import and no extra browser module route (beakley-0006).
Do not add unused imports or redefine those shared codes/texts later.
2026-10-23T19:21:07Z dependency update (bubba-0006): Bubba's first static row288 push is
origin/bubba/row288 c867cada9, explicitly Untested with no Node run. A2 library.revert uses the
business.projects key; broker notice retains the exact agreed body. Bubba adds librarySave only
in console-bus.mjs/cli.mjs and leaves libraryRevert wiring owned by 225. Preserve both callback
changes when Sage integrates. No dependency merge or test run performed here; 225 still lands
after 217 and may show unknown-verb until 288 lands.
## T1 targeted RED to GREEN (sage-3641, 2026-10-23T19:23Z)
All three files ran whole and serial, with TMPDIR=/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2,
--test-concurrency=1 --test-timeout=30000 and outer timeout -k 10 (120s renderer, 120s routes,
240s browser). Initial renderer 9/0 (0.105s), routes 9/3 (0.226s), browser 5/3 (5.630s).
Final renderer 9/0 (0.105s), routes 12/0 (0.212s), browser 8/0 (5.284s). Logs in evidence/.
Production fixes after RED:
- Reject a library.revert receipt with the wrong project as outcome-unknown; no retry.
- Read replyLimits.fallback and display replyLimits.problem per beakley-0004/0006.
- Show library-mapping-invalid in the status line; its hidden-status branch is covered by the
browser refusal matrix once the nested-limits assertion passes. This diagnosis was made statically;
the first RED exits that matrix at the preceding fallback assertion, so visibility itself has only
a GREEN run, unlike the fallback/problem and wrong-project fixes.
Three assertion corrections in NEW handoff files (not pre-existing tests, per sage-3647):
1. library-routes.test.mjs: POST /api/library/status in preview mode expects 403, because the existing
global preview write fence runs before the GET-only route and returns read-only. The route remains
GET-only and no callback fires on that POST. Do not weaken or bypass the fence to get 405.
2. library-routes.test.mjs: project-not-declared expects 400, which busStatus already maps at the
row base. The inherited 502 expectation contradicted that existing map. No production map change.
3. chat-replies-browser.test.mjs: coerce the whole “img element OR window.injected” expression to a
Boolean. With no injected node/code the old expression returns undefined, not false. Assertions
still require neither DOM injection nor executed handler. All preceding cite/link assertions pass.
Frozen pre-existing test deltas still render.test.mjs 80/66 and channel-proof.test.mjs 3/1.
No Node/Chromium process with this slot TMPDIR remained after targeted tests (checked /proc).
Expanded CLEAR sage-3647: full library, then channel-proof whole, then isolated mutants that fit;
still hard stop 19:38Z. No full webui suite, npm ci or editor build authorized in this window.
Expanded-slot evidence: full library 81/0 (9.179s), channel-proof 2/0 (0.573s), whole/serial,
--test-timeout=30000, outer timeout -k 10 180, same TMPDIR. Logs copied to evidence/.
To exercise invalid-mapping visibility independently, added its own browser case and temporarily
restored only the hidden-mapping branch. Whole browser RED: 7/2 (8.810s); the direct assertion sees
null instead of the diagnostic, and the refusal-matrix wait also fails. Finally restored the exact
corrected source before the next run. This supersedes the earlier visibility “GREEN only” note.
Final targeted coverage before mutation: browser 9/0 (6014.970ms runner duration), routes 13/0.
Added an independent send-off test with a configured libraryRevert callback: absence of --send
still refuses with zero callback calls. This makes the send fence independently mutation-testable.
The kit now has 14 concrete mutations, including agent-label, unreadable-limits problem and send.
Each isolated target is restored to its candidate SHA256, and worktree hashes are checked before
continuing to the next mutant (sage-3647). Runner uses TAP so assertion failures can be distinguished
from timeouts/import/process failures. Full webui/build and the integration gate remain pending.
Dependency update beakley-0008: row217 first push origin/beakley/row217 c25f5b8da is UNTESTED,
continued from Launchpad e25ef0373. No dependency merge here; its protocol/module mount belongs to
217 and renderReply needs none of those codes/texts. Integration acceptance remains Sage's gate.
First isolated-mutant attempt stopped honestly at M3-wrong-commit: M1 badge, M1 agent label and M2
https-only link mutants were killed by assertions; M3 failed via the browser's expected-state wait,
so the strict runner did NOT count it as a kill. Every target restored/hash-checked and candidate
hashes unchanged. Logs retained in evidence/mutants-attempt1/. Corrected the new browser test to wait
for a completed repaint (independent of desired output), then assert changed-marker count. Whole
browser baseline remains 9/0. No production change. Re-run the whole isolated kit against this test.
## T1 complete: isolated mutants and source-review handoff
sage-3647 final kit on source candidate 507901277: 14/14 assertion kills, no timeout/import/process
failure counted. Each mutant matched one anchor, restored the target SHA256 and checked the three
candidate source hashes before the next. evidence/mutants-final/summary.json records every result;
final worktreeUnchanged=true. Logs name the failing tests, including M3's direct assertion after the
refresh wait correction. /proc inspection found no remaining Node/Chromium process with the slot
TMPDIR. Returned lane A to Sage as fethry-0016 at about19:29Z, before the19:38Z hard stop.
Checked-in TAP display copies remove trailing whitespace so the diff is clean. Their raw SHA256
values are recorded in evidence/raw-log-provenance.json; original byte-for-byte logs remain under
/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2. This changes evidence presentation only, never tests.
MANIFEST.md and manifest.r1.sha256 freeze the13 changed package files for Dewey. All three handoff
deviations and Mos1510's two preservation conditions are explicit. Old-test numstats remain80/66 and3/1.
No full webui/npm ci/build was run; Sage explicitly reserved it for a later window. Full package/script
and dependency integration gate remains required before landing. No queue move, merge or live action.
+67
View File
@@ -0,0 +1,67 @@
# Row 225 review packet — C2-10b (#1726)
Owner Fethry (14f80e3c), reviewer Dewey (84fb346c). Base 45dca69f6.
Source candidate 507901277 continues Morgana 66f7becf2 and 9a131e956; no rewritten history.
Status: targeted tests and full library green; full webui/build and Sage's full gate still pending.
This packet requests source/manifest review, not landing or a full-green claim.
## Behavior and contracts
The Console shows reply text and citations with path/line/quote, scope and agent-written labels,
marks a changed file only with valid differing check.file/check.last, links only https URLs, shows
write/delete notices and explicit Revert results, and displays library status, fetch age, divergence,
fetch errors, nested replyLimits metadata and R-problems. Invalid library mappings remain visible.
Revert validates {project,commit}, preserves refusal codes and reports uncertain/wrong-project
receipts as outcome-unknown without retry. The project is chat project.repo (business.projects key).
A1-A5 are in BUILD.md. Dependency contracts: row217 ordered check.citations and nested replyLimits;
row288 A2 revert result/notice. No dependency merge in this candidate. Landing order is 217 then225;
unknown-verb is expected until288 lands. Beakley's GUARD_CODES/FIXED_TEXTS need no renderer import:
renderReply defines neither and renders those fixed bodies as free text.
## Frozen implementation and tests
manifest.r1.sha256 freezes the 13 changed package paths, including the two approved existing-test
changes. Check from repository root with `sha256sum -c agents/fethry/work/queue-225/manifest.r1.sha256`.
All other changed files are BUILD/session records, handoff/approval evidence and this review packet.
No role, policy, schema, secret, live instance, deployment or queue change is included.
Three departures from the brief, carried from Morgana's handoff:
1. Chat rendering/status live in public/chat.js and chat.css, where the message list is, instead of
bus.js and its stylesheet (sage-3596). serve.mjs serves the same pure render.mjs module to owner
and granted-user pages; no duplicated browser renderer and no generated asset.
2. console-bus.mjs and cli.mjs wire libraryRevert beside libraryStatus (sage-3596). --send still
independently gates Revert. Preserve row288's separate librarySave wiring at integration.
3. webui/src/bus.mjs maps library-revert-not-agent to409; bus/tests/channel-proof.test.mjs re-pins
only that source's hash and documents why. Mos read and approved this exact +3/-1 (mos-1510).
Existing-test authority: Mos1510 via Sage3623 approves render.test.mjs +80/-66 and channel-proof +3/-1.
Both exact numstats are unchanged. The malformed-body fixed-line rule is superseded by design5.2's
whole-body free text, covered by two replacement renderer tests. The old no-options path-badge rule
is retained and covered by the replacement test plus unchecked-cite browser coverage. BUILD.md
explains these two preservation conditions and the three new-file assertion corrections in detail.
## Verification and remaining gate
Evidence logs are under evidence/; commands, deadlines, failures and corrections are in BUILD.md.
Targeted whole-file tests: renderer9/0, routes13/0, browser9/0. Full library81/0, channel-proof2/0.
No skips/cancellations. Invalid-mapping visibility also has an independent RED then GREEN.
All 14 isolated mutants were killed by assertions on source candidate507901277; each mutation
matched one anchor, restored its source hash, and verified unchanged candidate hashes before the next
mutation. Summary: evidence/mutants-final/summary.json. The first attempt stopped at an expected-state
wait (not counted as a kill), then the new browser test was improved and the whole kit rerun.
Raw logs remain under the scratch root; checked-in display copies trim trailing whitespace and record
raw digests in evidence/raw-log-provenance.json.
Before landing, Sage still runs full webui with MOSAIC_REQUIRE_EDITOR_BUILD=1 after the worktree-local
webui npm ci, every applicable package/script suite and integration against217. No full webui/build
in T1 (sage-3647). Dewey approval and Sage's gate/landing remain required; queue moves belong to Sage.
Static audit: the channel-proof source pin matches webui bus.mjs; no hash pin of bus.js, serve.mjs
or library render.mjs was found. packages' only remaining renderItems match is an explanatory comment
in the frozen replacement test; no runtime export, import or call remains. No no-cited-answer match.
Review focus: stored check metadata versus untrusted body, missing/malformed file/last, https-only
links and inert hostile text, Revert authority/receipt uncertainty, async route/sign-out isolation,
and status diagnostics without owner transport leakage to a granted user.
@@ -0,0 +1,64 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 704.129155
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 690.249337
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 595.036289
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 627.082447
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 787.799722
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 587.311743
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 618.209773
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 633.838649
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 619.313248
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6014.970062
@@ -0,0 +1,64 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 636.096036
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 741.239029
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 568.879998
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 641.929005
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 870.041985
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 648.87451
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 566.725897
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 611.150846
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 618.488891
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6014.364529
@@ -0,0 +1,16 @@
✔ chat replies: free text, cites with badges, the agent label, changed since and safe links (583.234335ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (634.71741ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (574.836149ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (607.74749ms)
✔ chat status line: limits when the status carries them, no problems, and refusals (861.034669ms)
✔ chat status line: a project with no repository link reads no library status (611.433469ms)
✔ chat status line: unreadable reply limits show their problem and both fallback keys (606.380675ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (650.947719ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5251.735356
@@ -0,0 +1,78 @@
✖ chat replies: free text, cites with badges, the agent label, changed since and safe links (980.914799ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (753.988771ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (672.814814ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (622.859977ms)
✖ chat status line: limits when the status carries them, no problems, and refusals (583.956229ms)
✔ chat status line: a project with no repository link reads no library status (635.758222ms)
✖ chat status line: unreadable reply limits show their problem and both fallback keys (581.636121ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (653.788881ms)
ℹ tests 8
ℹ suites 0
ℹ pass 5
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5597.371605
✖ failing tests:
test at packages/webui/tests/chat-replies-browser.test.mjs:81:1
✖ chat replies: free text, cites with badges, the agent label, changed since and safe links (980.914799ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ undefined
- false
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:102:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: undefined,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/chat-replies-browser.test.mjs:152:1
✖ chat status line: limits when the status carries them, no problems, and refusals (583.956229ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:156:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems',
expected: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/chat-replies-browser.test.mjs:173:1
✖ chat status line: unreadable reply limits show their problem and both fallback keys (581.636121ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · no problems',
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,22 @@
TAP version 13
# Subtest: channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller
ok 1 - channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller
---
duration_ms: 4.280459
type: 'test'
...
# Subtest: channel recovery still requires the same shim path and rejects an absent or forged proof
ok 2 - channel recovery still requires the same shim path and rejects an absent or forged proof
---
duration_ms: 413.032186
type: 'test'
...
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 539.602759
@@ -0,0 +1,496 @@
TAP version 13
# Subtest: resolver step 1: a path from the repo root, with or without .md
ok 1 - resolver step 1: a path from the repo root, with or without .md
---
duration_ms: 1.051208
type: 'test'
...
# Subtest: resolver step 2: the same path with vaultPrefix/ stripped
ok 2 - resolver step 2: the same path with vaultPrefix/ stripped
---
duration_ms: 0.164795
type: 'test'
...
# Subtest: resolver step 3: a path relative to the linking file, never above the repo root
ok 3 - resolver step 3: a path relative to the linking file, never above the repo root
---
duration_ms: 0.192765
type: 'test'
...
# Subtest: resolver step 4: a base name that matches exactly one indexed file
ok 4 - resolver step 4: a base name that matches exactly one indexed file
---
duration_ms: 0.201611
type: 'test'
...
# Subtest: a link into another of her repos resolves to nothing
ok 5 - a link into another of her repos resolves to nothing
---
duration_ms: 0.130282
type: 'test'
...
# Subtest: wikilink forms, embeds, and links skipped inside code
ok 6 - wikilink forms, embeds, and links skipped inside code
---
duration_ms: 1.445011
type: 'test'
...
# Subtest: only a body embed whose target has an extension other than .md, in any case, is an attachment
ok 7 - only a body embed whose target has an extension other than .md, in any case, is an attachment
---
duration_ms: 0.25003
type: 'test'
...
# Subtest: the fixture mapping checks and comes back frozen
ok 8 - the fixture mapping checks and comes back frozen
---
duration_ms: 1.772928
type: 'test'
...
# Subtest: C2-01c: a prefix inside or holding another entry's book refuses library-playground-other-universe
ok 9 - C2-01c: a prefix inside or holding another entry's book refuses library-playground-other-universe
---
duration_ms: 0.735312
type: 'test'
...
# Subtest: C2-01c: a prefix equal to a book folder, or holding its own, refuses library-mapping-invalid
ok 10 - C2-01c: a prefix equal to a book folder, or holding its own, refuses library-mapping-invalid
---
duration_ms: 0.532498
type: 'test'
...
# Subtest: C2-01c: universe-level prefixes lie outside every book, in a plain universe and in a hybrid with no books
ok 11 - C2-01c: universe-level prefixes lie outside every book, in a plain universe and in a hybrid with no books
---
duration_ms: 0.668173
type: 'test'
...
# Subtest: C2-01c: a draft takes kind, name and playground only, and comes back frozen with books []
ok 12 - C2-01c: a draft takes kind, name and playground only, and comes back frozen with books []
---
duration_ms: 1.032507
type: 'test'
...
# Subtest: no playground prefix holds another, and no book folder holds another
ok 13 - no playground prefix holds another, and no book folder holds another
---
duration_ms: 0.507943
type: 'test'
...
# Subtest: shape refusals: kind, remote, branch, unknown keys, types, manuscript, folder names
ok 14 - shape refusals: kind, remote, branch, unknown keys, types, manuscript, folder names
---
duration_ms: 0.740368
type: 'test'
...
# Subtest: C2-03a sage-2021: user is optional, kept when given, and must be a plain account name
ok 15 - C2-03a sage-2021: user is optional, kept when given, and must be a plain account name
---
duration_ms: 0.659277
type: 'test'
...
# Subtest: a hybrid universe is a shape the query code can take (1.4), with sources that must exist
ok 16 - a hybrid universe is a shape the query code can take (1.4), with sources that must exist
---
duration_ms: 0.695117
type: 'test'
...
# Subtest: a caller-supplied fail receives the code, as business file loading will pass it (C2-04)
ok 17 - a caller-supplied fail receives the code, as business file loading will pass it (C2-04)
---
duration_ms: 0.392362
type: 'test'
...
# Subtest: scope comes from the path alone (design 1.2)
ok 18 - scope comes from the path alone (design 1.2)
---
duration_ms: 0.466406
type: 'test'
...
# Subtest: C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization
ok 19 - C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization
---
duration_ms: 46.645994
type: 'test'
...
# Subtest: C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths
ok 20 - C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths
---
duration_ms: 32.763959
type: 'test'
...
# Subtest: C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner
ok 21 - C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner
---
duration_ms: 109.751094
type: 'test'
...
# Subtest: C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps
ok 22 - C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps
---
duration_ms: 20.129293
type: 'test'
...
# Subtest: C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock
ok 23 - C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock
---
duration_ms: 24.021555
type: 'test'
...
# Subtest: C2-04 state: missing state is empty only while the served ref is absent
ok 24 - C2-04 state: missing state is empty only while the served ref is absent
---
duration_ms: 37.436349
type: 'test'
...
# Subtest: C2-04 state: valid problems and sync metadata return no content and change no state bytes
ok 25 - C2-04 state: valid problems and sync metadata return no content and change no state bytes
---
duration_ms: 37.681315
type: 'test'
...
# Subtest: C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed
ok 26 - C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed
---
duration_ms: 105.483341
type: 'test'
...
# Subtest: C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement
ok 27 - C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement
---
duration_ms: 29.562786
type: 'test'
...
# Subtest: C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text
ok 28 - C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text
---
duration_ms: 74.057232
type: 'test'
...
# Subtest: C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies
ok 29 - C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies
---
duration_ms: 80.573844
type: 'test'
...
# Subtest: C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes
ok 30 - C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes
---
duration_ms: 253.247948
type: 'test'
...
# Subtest: C2-04 lock: same-process waits, timeout code, callback expiry and throw release
ok 31 - C2-04 lock: same-process waits, timeout code, callback expiry and throw release
---
duration_ms: 5031.462795
type: 'test'
...
# Subtest: C2-04 lock: owner death releases the kernel lock without replacing its file
ok 32 - C2-04 lock: owner death releases the kernel lock without replacing its file
---
duration_ms: 61.533295
type: 'test'
...
# Subtest: C2-04 lock: a missing flock binary becomes a handled refusal
ok 33 - C2-04 lock: a missing flock binary becomes a handled refusal
---
duration_ms: 57.176644
type: 'test'
...
# Subtest: C2-04 lock: an unawaited scoped read drains before the lock is released
ok 34 - C2-04 lock: an unawaited scoped read drains before the lock is released
---
duration_ms: 22.766874
type: 'test'
...
# Subtest: the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed
ok 35 - the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed
---
duration_ms: 33.888061
type: 'test'
...
# Subtest: R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed
ok 36 - R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed
---
duration_ms: 68.951498
type: 'test'
...
# Subtest: R2: unmapped paths are reported and not indexed
ok 37 - R2: unmapped paths are reported and not indexed
---
duration_ms: 25.70771
type: 'test'
...
# Subtest: R3: front matter not fully parsed stays raw, with the line where parsing stopped
ok 38 - R3: front matter not fully parsed stays raw, with the line where parsing stopped
---
duration_ms: 26.059427
type: 'test'
...
# Subtest: front matter forms the reader accepts and refuses
ok 39 - front matter forms the reader accepts and refuses
---
duration_ms: 0.448293
type: 'test'
...
# Subtest: R4: a link to no file, to more than one, or into another repo is reported and not followed
ok 40 - R4: a link to no file, to more than one, or into another repo is reported and not followed
---
duration_ms: 26.225692
type: 'test'
...
# Subtest: a block list link carries its item line, a scalar or flow link its key line, and problems follow
ok 41 - a block list link carries its item line, a scalar or flow link its key line, and problems follow
---
duration_ms: 22.565103
type: 'test'
...
# Subtest: an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves
ok 42 - an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves
---
duration_ms: 20.903777
type: 'test'
...
# Subtest: an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed
ok 43 - an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed
---
duration_ms: 25.42747
type: 'test'
...
# Subtest: R5: a canon file that refers into the playground is reported, and the link still goes by path
ok 44 - R5: a canon file that refers into the playground is reported, and the link still goes by path
---
duration_ms: 27.37119
type: 'test'
...
# Subtest: R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources
ok 45 - R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources
---
duration_ms: 30.585458
type: 'test'
...
# Subtest: R7: two paths that differ only in case are both left out
ok 46 - R7: two paths that differ only in case are both left out
---
duration_ms: 40.709509
type: 'test'
...
# Subtest: the reader reads the commit, never the working tree, and refuses a bad commit id
ok 47 - the reader reads the commit, never the working tree, and refuses a bad commit id
---
duration_ms: 33.579454
type: 'test'
...
# Subtest: a bare mirror reads the same as the clone
ok 48 - a bare mirror reads the same as the clone
---
duration_ms: 38.368686
type: 'test'
...
# Subtest: free text renders as itself with no citations
ok 49 - free text renders as itself with no citations
---
duration_ms: 1.571777
type: 'test'
...
# Subtest: a cited reply renders its text, then each cite with the badge from the check result
ok 50 - a cited reply renders its text, then each cite with the badge from the check result
---
duration_ms: 0.537429
type: 'test'
...
# Subtest: a parsed body renders as its string does
ok 51 - a parsed body renders as its string does
---
duration_ms: 0.304068
type: 'test'
...
# Subtest: without a check result a path cite has no badge, no label and is never changed
ok 52 - without a check result a path cite has no badge, no label and is never changed
---
duration_ms: 0.184363
type: 'test'
...
# Subtest: the label and the badge come from the check result only, never from a bad entry
ok 53 - the label and the badge come from the check result only, never from a bad entry
---
duration_ms: 0.180581
type: 'test'
...
# Subtest: a web cite is a link only for https
ok 54 - a web cite is a link only for https
---
duration_ms: 0.293669
type: 'test'
...
# Subtest: a write notice renders its fixed line
ok 55 - a write notice renders its fixed line
---
duration_ms: 0.277197
type: 'test'
...
# Subtest: a body without the reply or notice shape is free text: the whole body, no citations
ok 56 - a body without the reply or notice shape is free text: the whole body, no citations
---
duration_ms: 0.345807
type: 'test'
...
# Subtest: an empty citations list is a reply with no cites
ok 57 - an empty citations list is a reply with no cites
---
duration_ms: 0.187354
type: 'test'
...
# Subtest: C2-01c: each level gives its universe, and its book or null
ok 58 - C2-01c: each level gives its universe, and its book or null
---
duration_ms: 3.363132
type: 'test'
...
# Subtest: C2-01c: prefixes come first: in a book that holds a prefix, a file outside it is canon and one under it playground
ok 59 - C2-01c: prefixes come first: in a book that holds a prefix, a file outside it is canon and one under it playground
---
duration_ms: 0.480483
type: 'test'
...
# Subtest: C2-01c: a canon file linking into a draft gives R6, and into its own universe-level prefix gives R5
ok 60 - C2-01c: a canon file linking into a draft gives R6, and into its own universe-level prefix gives R5
---
duration_ms: 44.606565
type: 'test'
...
# Subtest: the survey prints folder names, counts, key sets and link-form counts, and never text
ok 61 - the survey prints folder names, counts, key sets and link-form counts, and never text
---
duration_ms: 37.28555
type: 'test'
...
# Subtest: folder names print DEL, C1 and bidi controls, U+061C among them, as escapes
ok 62 - folder names print DEL, C1 and bidi controls, U+061C among them, as escapes
---
duration_ms: 26.222951
type: 'test'
...
# Subtest: an embed of an attachment counts as an embed and as an attachment; a plain link to one does not
ok 63 - an embed of an attachment counts as an embed and as an attachment; a plain link to one does not
---
duration_ms: 26.23793
type: 'test'
...
# Subtest: with --mapping the survey adds scope, resolver step and problem counts
ok 64 - with --mapping the survey adds scope, resolver step and problem counts
---
duration_ms: 41.437066
type: 'test'
...
# Subtest: survey exit codes: usage 4, bad mapping 3, unreadable repo 2
ok 65 - survey exit codes: usage 4, bad mapping 3, unreadable repo 2
---
duration_ms: 17.463059
type: 'test'
...
# Subtest: scripts/mosaic library survey reaches the survey
ok 66 - scripts/mosaic library survey reaches the survey
---
duration_ms: 77.634947
type: 'test'
...
# Subtest: C2-03a argv: the fixed sequence, one https allow, no other protocol key
ok 67 - C2-03a argv: the fixed sequence, one https allow, no other protocol key
---
duration_ms: 1.605597
type: 'test'
...
# Subtest: C2-03a argv: only an https URL with a plain host, no userinfo, and a plain user
ok 68 - C2-03a argv: only an https URL with a plain host, no userinfo, and a plain user
---
duration_ms: 0.713147
type: 'test'
...
# Subtest: C2-03a helper: answers only the exact prompts for the configured host and user (dewey-0765)
ok 69 - C2-03a helper: answers only the exact prompts for the configured host and user (dewey-0765)
---
duration_ms: 31.590474
type: 'test'
...
# Subtest: C2-03a first sync: served is the fetched tip, HEAD is served, the survey reads it, and the token stays in the slot
ok 70 - C2-03a first sync: served is the fetched tip, HEAD is served, the survey reads it, and the token stays in the slot
---
duration_ms: 135.075159
type: 'test'
...
# Subtest: C2-03a fast-forward: served moves, problems are rebuilt, and a repeat changes only lastFetch
ok 71 - C2-03a fast-forward: served moves, problems are rebuilt, and a repeat changes only lastFetch
---
duration_ms: 225.707972
type: 'test'
...
# Subtest: C2-03a Filbert N2: a read that took served before a sync still finds its objects after served moves
ok 72 - C2-03a Filbert N2: a read that took served before a sync still finds its objects after served moves
---
duration_ms: 205.10431
type: 'test'
...
# Subtest: C2-03a overlap: a reader during a sync sees the old pair or the new one, never a mix
ok 73 - C2-03a overlap: a reader during a sync sees the old pair or the new one, never a mix
---
duration_ms: 175.907929
type: 'test'
...
# Subtest: C2-03a diverged: a non-fast-forward keeps serving and holds until a human accepts the tip
ok 74 - C2-03a diverged: a non-fast-forward keeps serving and holds until a human accepts the tip
---
duration_ms: 431.553812
type: 'test'
...
# Subtest: C2-03a accept starts the record over, so a changed branch gets a fresh state
ok 75 - C2-03a accept starts the record over, so a changed branch gets a fresh state
---
duration_ms: 195.011432
type: 'test'
...
# Subtest: C2-03a fetch failure: a wrong token keeps serving and records the error, lastFetch unchanged
ok 76 - C2-03a fetch failure: a wrong token keeps serving and records the error, lastFetch unchanged
---
duration_ms: 192.898644
type: 'test'
...
# Subtest: C2-03a a failed first fetch records the error with nothing served
ok 77 - C2-03a a failed first fetch records the error with nothing served
---
duration_ms: 61.594695
type: 'test'
...
# Subtest: C2-03a a 302 to another host fails the fetch and the other host sees nothing
ok 78 - C2-03a a 302 to another host fails the fetch and the other host sees nothing
---
duration_ms: 64.82991
type: 'test'
...
# Subtest: C2-03a the slot: missing or unsafe refuses before git, and sync never opens it
ok 79 - C2-03a the slot: missing or unsafe refuses before git, and sync never opens it
---
duration_ms: 59.167725
type: 'test'
...
# Subtest: C2-03a sage-2021: an entry with no user refuses library-remote-invalid and runs no git
ok 80 - C2-03a sage-2021: an entry with no user refuses library-remote-invalid and runs no git
---
duration_ms: 35.37205
type: 'test'
...
# Subtest: C2-03a one sync per mirror: an overlapping call refuses library-sync-busy
ok 81 - C2-03a one sync per mirror: an overlapping call refuses library-sync-busy
---
duration_ms: 118.260687
type: 'test'
...
1..81
# tests 81
# suites 0
# pass 81
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 9149.325891
@@ -0,0 +1,48 @@
✔ chat replies: free text, cites with badges, the agent label, changed since and safe links (633.807074ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (621.95095ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (577.638161ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (555.847899ms)
✖ chat status line: limits when the status carries them, no problems, and refusals (3725.696272ms)
✔ chat status line: a project with no repository link reads no library status (657.572856ms)
✔ chat status line: unreadable reply limits show their problem and both fallback keys (663.163445ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (616.184063ms)
✖ chat status line: an invalid library mapping remains visible for diagnosis (597.976623ms)
ℹ tests 9
ℹ suites 0
ℹ pass 7
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 8782.679569
✖ failing tests:
test at packages/webui/tests/chat-replies-browser.test.mjs:152:1
✖ chat status line: limits when the status carries them, no problems, and refusals (3725.696272ms)
Error: Uncaught (in promise) Error: window.__statusReads.length && document.querySelector(".chat-conversation") && document.querySelector(".chat-library")?.textContent==="Library status unavailable (library-mapping-invalid).": undefined
at Object.evaluate (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/browser.mjs:49:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:162:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
test at packages/webui/tests/chat-replies-browser.test.mjs:200:1
✖ chat status line: an invalid library mapping remains visible for diagnosis (597.976623ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ null
- 'Library status unavailable (library-mapping-invalid).'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:203:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: null,
expected: 'Library status unavailable (library-mapping-invalid).',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,4 @@
{"mutant": "M1-badges", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-badges.tap"}
{"mutant": "M1-agent-label", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-agent-label.tap"}
{"mutant": "M2-http-links", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M2-http-links.tap"}
{"mutant": "M3-wrong-commit", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": false, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M3-wrong-commit.tap"}
@@ -0,0 +1,192 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 627.180675
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
{
+ agent: null,
- agent: 'agent-written: acme/scribe',
badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: ~
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 778.572201
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 641.728604
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 629.327348
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 837.886073
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 613.037186
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 651.026507
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 735.546636
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 712.174542
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6353.090574
@@ -0,0 +1,206 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 638.589537
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
+ badge: null,
- badge: 'canon',
changed: false,
href: null,
quote: 'her grey eyes',
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa',
rel: null
...
agent: null,
+ badge: null,
- badge: 'playground',
changed: true,
href: null,
quote: 'a storm',
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa',
rel: null
...
agent: 'agent-written: acme/scribe',
+ badge: null,
- badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
rel: null
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: ~
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: ~
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: ~
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 770.070531
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 519.328288
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 647.667301
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 999.237447
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 676.088845
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 634.472637
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 655.963182
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 578.819646
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6239.022812
@@ -0,0 +1,204 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 655.901619
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
changed: false,
+ href: 'http://example.invalid/plain',
- href: null,
quote: 'plain http',
ref: 'http://example.invalid/plain',
+ rel: 'noopener noreferrer'
- rel: null
},
{
agent: null,
badge: 'web',
changed: false,
+ href: 'javascript:window.injected=2',
- href: null,
quote: 'script',
ref: 'javascript:window.injected=2',
+ rel: 'noopener noreferrer'
- rel: null
}
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: 'http://example.invalid/plain'
rel: 'noopener noreferrer'
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: 'javascript:window.injected=2'
rel: 'noopener noreferrer'
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 741.122544
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 616.728572
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 625.818493
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 867.033773
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 655.587703
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 615.997516
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 648.263681
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 624.323397
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6168.633364
@@ -0,0 +1,74 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 640.32042
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 694.233373
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 611.500508
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 606.581119
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 896.462474
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 666.798177
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 629.105559
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 3654.057789
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: 'Uncaught (in promise) Error: document.querySelectorAll(".chat-changed").length===0: undefined'
code: 'ERR_TEST_FAILURE'
stack: |-
Object.evaluate (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/browser.mjs:49:35)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:195:3)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 656.44095
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 9179.328916
@@ -0,0 +1,47 @@
{
"head": "77172ce3b46eb289409068518b7022fc7be47ebe",
"clear": "sage-3647",
"worktreeUnchanged": true,
"results": [
{
"mutant": "M1-badges",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-badges.tap"
},
{
"mutant": "M1-agent-label",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-agent-label.tap"
},
{
"mutant": "M2-http-links",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M2-http-links.tap"
},
{
"mutant": "M3-wrong-commit",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": false,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M3-wrong-commit.tap"
}
]
}
@@ -0,0 +1,14 @@
{"mutant": "M1-badges", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-badges.tap"}
{"mutant": "M1-agent-label", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-agent-label.tap"}
{"mutant": "M2-http-links", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M2-http-links.tap"}
{"mutant": "M3-wrong-commit", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-wrong-commit.tap"}
{"mutant": "M3-missing-file", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-missing-file.tap"}
{"mutant": "M4-fallback", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-fallback.tap"}
{"mutant": "M4-problem", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-problem.tap"}
{"mutant": "M5-send", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-send.tap"}
{"mutant": "M5-project-receipt", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-project-receipt.tap"}
{"mutant": "M5-commit-input", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-commit-input.tap"}
{"mutant": "M5-origin", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-origin.tap"}
{"mutant": "M5-fetch-metadata", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-fetch-metadata.tap"}
{"mutant": "M6-duplicate-call", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M6-duplicate-call.tap"}
{"mutant": "M7-user-fence", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M7-user-fence.tap"}
@@ -0,0 +1,192 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 646.851404
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
{
+ agent: null,
- agent: 'agent-written: acme/scribe',
badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: ~
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 667.90945
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 588.346681
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 485.882289
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 709.847041
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 544.885551
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 514.410016
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 542.746154
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 582.608663
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5409.980177
@@ -0,0 +1,206 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 621.562133
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
+ badge: null,
- badge: 'canon',
changed: false,
href: null,
quote: 'her grey eyes',
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa',
rel: null
...
agent: null,
+ badge: null,
- badge: 'playground',
changed: true,
href: null,
quote: 'a storm',
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa',
rel: null
...
agent: 'agent-written: acme/scribe',
+ badge: null,
- badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
rel: null
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: ~
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: ~
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: ~
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 731.101767
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 633.163696
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 637.761161
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 847.929653
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 635.512979
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 645.427688
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 672.272209
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 629.043251
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6169.954716
@@ -0,0 +1,204 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 582.248796
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
changed: false,
+ href: 'http://example.invalid/plain',
- href: null,
quote: 'plain http',
ref: 'http://example.invalid/plain',
+ rel: 'noopener noreferrer'
- rel: null
},
{
agent: null,
badge: 'web',
changed: false,
+ href: 'javascript:window.injected=2',
- href: null,
quote: 'script',
ref: 'javascript:window.injected=2',
+ rel: 'noopener noreferrer'
- rel: null
}
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: 'http://example.invalid/plain'
rel: 'noopener noreferrer'
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: 'javascript:window.injected=2'
rel: 'noopener noreferrer'
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 640.769563
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 572.506418
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 519.031091
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 743.513043
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 676.282756
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 595.166769
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 626.475981
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 596.828198
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5658.889854
@@ -0,0 +1,81 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 639.027823
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 693.772757
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 683.094789
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 637.029355
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 772.100011
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 599.342035
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 574.320042
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 621.466015
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-missing-file/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-missing-file/packages/webui/tests/chat-replies-browser.test.mjs:196:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 590.165121
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5921.651346
@@ -0,0 +1,81 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 704.141588
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 816.229452
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 697.826745
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 638.579753
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 850.914117
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 634.004327
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 585.469632
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 673.386968
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:196:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 579.53998
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6292.579267
@@ -0,0 +1,102 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 683.334532
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 686.685624
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 612.757087
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 584.651483
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
not ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 637.405759
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:152:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:156:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 626.790802
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
not ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 643.680732
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:173:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · reply-limits-unreadable · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · reply-limits-unreadable · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 645.032205
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 616.242947
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 7
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5845.226677
@@ -0,0 +1,83 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 663.005514
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 662.360161
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 620.68694
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 744.825035
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 826.418848
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 549.365993
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
not ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 589.776762
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-problem/packages/webui/tests/chat-replies-browser.test.mjs:173:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-problem/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 590.919434
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 545.326381
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5914.325706
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 16.128237
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.987607
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
not ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 4.805637
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-commit-input/packages/webui/tests/library-routes.test.mjs:64:1'
failureType: 'testCodeFailure'
error: |-
{"project":"books","commit":"abc"}
200 !== 400
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 400
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-commit-input/packages/webui/tests/library-routes.test.mjs:69:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.383811
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.000988
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.487122
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 4.9292
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.266317
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.443059
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.048018
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.864934
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.064626
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.803048
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 153.3549
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 20.271342
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
not ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.700968
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-fetch-metadata/packages/webui/tests/library-routes.test.mjs:48:1'
failureType: 'testCodeFailure'
error: |-
sec-fetch-site
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-fetch-metadata/packages/webui/tests/library-routes.test.mjs:57:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.427935
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 7.39929
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 5.425467
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.634037
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.727409
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.317954
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.583799
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.207375
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.956772
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.147745
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.819337
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 166.134058
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 16.249491
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
not ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.463985
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-origin/packages/webui/tests/library-routes.test.mjs:48:1'
failureType: 'testCodeFailure'
error: |-
origin
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-origin/packages/webui/tests/library-routes.test.mjs:57:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.035196
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 5.621744
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.928333
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.455282
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 4.838747
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.235209
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.436399
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.100574
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.943474
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.176957
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.861834
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 150.969641
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.918199
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.26916
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.352342
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.134713
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.288914
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.745385
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.739155
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.264793
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.471965
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.045204
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
not ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.531252
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-project-receipt/packages/webui/tests/library-routes.test.mjs:145:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
200 !== 503
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 503
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-project-receipt/packages/webui/tests/library-routes.test.mjs:148:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.60161
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.816068
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 171.289003
@@ -0,0 +1,106 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.930067
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.470645
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.646929
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.637697
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.174441
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.513131
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.145692
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.182362
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.471806
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.064814
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.910004
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.16191
type: 'test'
...
# 2026-10-23T19:28:51.488Z library-revert user="jason" project="books" commit="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" commit=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
# Subtest: a configured revert callback remains unreachable when send is off
not ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.624909
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-send/packages/webui/tests/library-routes.test.mjs:175:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-send/packages/webui/tests/library-routes.test.mjs:181:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 165.919088
@@ -0,0 +1,177 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
not ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 17.922489
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:40:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
{
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
project: 'books'
},
+ {
+ commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ project: 'books'
+ }
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
actual:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
1:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:43:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.053366
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.869486
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.852952
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
not ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 2.870452
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:88:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
2 !== 1
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 1
actual: 2
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:96:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.904694
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.7962
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.518332
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.563294
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.17446
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
not ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.258633
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:145:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
{
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
project: 'books'
},
+ {
+ commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ project: 'books'
+ }
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
actual:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
1:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:151:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.362877
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.848309
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 10
# fail 3
# cancelled 0
# skipped 0
# todo 0
# duration_ms 169.162917
@@ -0,0 +1,126 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.215173
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 6.416847
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.529629
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.92431
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.588509
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.817257
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 6.020444
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.377224
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
not ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 1.015984
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:132:1'
failureType: 'testCodeFailure'
error: |-
/api/library/status
true !== false
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: false
actual: true
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:133:106)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.processPendingSubtests (node:internal/test_runner/test:974:18)
Test.postRun (node:internal/test_runner/test:1542:19)
Test.run (node:internal/test_runner/test:1467:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.695555
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.378325
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
not ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.178045
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:155:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
503 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 503
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:167:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.057394
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 11
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 176.864112
@@ -0,0 +1,147 @@
{
"head": "50790127716002f97154800ecbfb968690a9d644",
"clear": "sage-3647",
"worktreeUnchanged": true,
"results": [
{
"mutant": "M1-badges",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-badges.tap"
},
{
"mutant": "M1-agent-label",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-agent-label.tap"
},
{
"mutant": "M2-http-links",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M2-http-links.tap"
},
{
"mutant": "M3-wrong-commit",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-wrong-commit.tap"
},
{
"mutant": "M3-missing-file",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-missing-file.tap"
},
{
"mutant": "M4-fallback",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-fallback.tap"
},
{
"mutant": "M4-problem",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-problem.tap"
},
{
"mutant": "M5-send",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-send.tap"
},
{
"mutant": "M5-project-receipt",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-project-receipt.tap"
},
{
"mutant": "M5-commit-input",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-commit-input.tap"
},
{
"mutant": "M5-origin",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-origin.tap"
},
{
"mutant": "M5-fetch-metadata",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-fetch-metadata.tap"
},
{
"mutant": "M6-duplicate-call",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M6-duplicate-call.tap"
},
{
"mutant": "M7-user-fence",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M7-user-fence.tap"
}
]
}
@@ -0,0 +1,155 @@
{
"rawRoot": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2",
"logs": [
{
"path": "evidence/browser-final.tap",
"rawSha256": "4e6d5874786aa5d407b59d0b701652d419d0fecda5402ede40f52b313def12b0",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-final2.tap",
"rawSha256": "194b3150e59868aab023d9ccfc75c69b82e43afba38c1b12e87a1fb3d68fe821",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-green.tap",
"rawSha256": "030d7a5c9af70d4a7ade177906fb971b29fa4cf1fc5efd39f35643ed1969cd01",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-red.tap",
"rawSha256": "db00bee89b4353efe7d324b17417347b30964f79ad4469a5dc98c6d125691f55",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/channel-proof.tap",
"rawSha256": "cb7c0398af2a2a0e7cae24ee2c8167601cbf7e3f9cf3ed7479b2a6ef65f5da8a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/library-full.tap",
"rawSha256": "5447e3fce497a0dfb03ddd3eb2795dc2a179554f794e4c37458219c70736a645",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mapping-red.tap",
"rawSha256": "8ca7310e46df2d35fa96e3aba378721ad388dce41f003ee133b684eeb7eaf245",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M1-agent-label.tap",
"rawSha256": "db7ab1c64bc05b719e5164ed4ce1dd8b79ad53309d9b8e3e1ca0cc8867bca70f",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M1-badges.tap",
"rawSha256": "08351b9061351812008ebd7067a9aed24a9e5386d8f6284fb44ae0f59c8f2eca",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M2-http-links.tap",
"rawSha256": "69921707c219d19fb2c33a3995268b6442d783ec702050b32f78d8d2db6fb581",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M3-wrong-commit.tap",
"rawSha256": "fe9fb027efae26799e2dfac539e0986402cefe806ca31479f52420e67a8df423",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M1-agent-label.tap",
"rawSha256": "afbab60cbc12348c4d0c4be1a8c4590dd157c71f9ffc7375bbde82facb9868bb",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M1-badges.tap",
"rawSha256": "3bb9fb9dcf74a398934d6de48c2ca99ab2eab618fb944297b3d7db00eec0e69f",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M2-http-links.tap",
"rawSha256": "8909921fcf602fcafa94edd19c1167a8f91ed970a3e649110e3503836690e567",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M3-missing-file.tap",
"rawSha256": "3fd2749ea5bc4245f7d49f585b82eae60f61ed150ab4dd22e8148a1e9467c8c7",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M3-wrong-commit.tap",
"rawSha256": "85696c155f0a2844ee903c8fb38cab0a0fb6b0610c8b9ee8aa77a08cb6012ca2",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M4-fallback.tap",
"rawSha256": "6f59f2845e0130d4d2fe1cb4aa7a435502f6043e11ed4efd67339c12e986103a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M4-problem.tap",
"rawSha256": "61ab08b823eb08cbb9c9eeaac82b297ea5d1272bb5dc64e9598734688b55eb80",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-commit-input.tap",
"rawSha256": "b703ee5ca47e5c64749aab3edfb5ae2e085367921ffa75337f4f0e5c7b3dc404",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-fetch-metadata.tap",
"rawSha256": "6951725340e4d74764552e7e406e0fc4bafeec35172de56d13bca852b92daa1a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-origin.tap",
"rawSha256": "95fcb5a92e82fa7de2972fd8f78c4f0f6d1c5f49885123e01fb1b69c3b62ba21",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-project-receipt.tap",
"rawSha256": "a86806a189ef73f61ddd88479336321964eadef0793c7b45da6573ccd3e38801",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-send.tap",
"rawSha256": "4cce523356a3e2041197156412eb35088f8b6d38fcceca2d9b76b6098ad17627",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M6-duplicate-call.tap",
"rawSha256": "6dda962389fbc7379ac7e7bc79cc24b68db00d4f9bb649ab73e75eaa87a5da00",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M7-user-fence.tap",
"rawSha256": "abec36c65c3ece6e7d0c4dfd41eede62c9a585f07b3ed4cb3b8d05f0dbabaed3",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/render-green.tap",
"rawSha256": "98463ac3d1262f84758def8bbdc27c46fa7c674bb8e8c20b9c752083163d0864",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/render-red.tap",
"rawSha256": "03645c7a5ba0c3d285e58e1d6fd05dc58a1174b463a258b01bcf258b2141a85e",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-final.tap",
"rawSha256": "f2b58a97c301dc9e57458d82785bd50debb2254df5d411d8e8c4e6b9c0aa00d2",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-green.tap",
"rawSha256": "c34451a0eef5f82fe6ddcbbf370179d8e8a14e36168175bdeff5b32e2072e9d6",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-red.tap",
"rawSha256": "5ca5bafe2465209128508e5459f4826c34d00de86d1e79671196dc4e5c57f874",
"normalization": "trailing whitespace removed from display copy"
}
]
}
@@ -0,0 +1,17 @@
✔ free text renders as itself with no citations (1.332106ms)
✔ a cited reply renders its text, then each cite with the badge from the check result (0.493062ms)
✔ a parsed body renders as its string does (0.2706ms)
✔ without a check result a path cite has no badge, no label and is never changed (0.175324ms)
✔ the label and the badge come from the check result only, never from a bad entry (0.171275ms)
✔ a web cite is a link only for https (0.254363ms)
✔ a write notice renders its fixed line (0.201038ms)
✔ a body without the reply or notice shape is free text: the whole body, no citations (0.315846ms)
✔ an empty citations list is a reply with no cites (0.167552ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.188818
@@ -0,0 +1,17 @@
✔ free text renders as itself with no citations (1.211164ms)
✔ a cited reply renders its text, then each cite with the badge from the check result (0.457937ms)
✔ a parsed body renders as its string does (0.255138ms)
✔ without a check result a path cite has no badge, no label and is never changed (0.189285ms)
✔ the label and the badge come from the check result only, never from a bad entry (0.162195ms)
✔ a web cite is a link only for https (0.231665ms)
✔ a write notice renders its fixed line (0.167596ms)
✔ a body without the reply or notice shape is free text: the whole body, no citations (0.283804ms)
✔ an empty citations list is a reply with no cites (0.145774ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 73.226696
@@ -0,0 +1,88 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.873338
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.061714
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.45503
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.964122
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 5.745801
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.815134
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 7.105984
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 2.002948
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.708324
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.553573
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.371025
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.586864
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.226995
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 13
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 177.384993
@@ -0,0 +1,20 @@
✔ a signed-in same-origin revert reaches exactly one library.revert and returns the new commit (19.266366ms)
✔ a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata (7.415028ms)
✔ a revert takes a project and a 40-hex commit, nothing else, before the bus is asked (6.176121ms)
✔ a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal (7.325625ms)
✔ a revert whose answer is lost or unusable is reported unknown and is never retried (5.0635ms)
✔ the status read takes one project query and answers the bus result for that project (3.008847ms)
✔ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (6.314096ms)
✔ the status read needs no send; a read-only Console still has no revert (1.440122ms)
✔ library routes are the owner's: a granted user is refused them, and the tester never reverts (0.563979ms)
✔ the page gets the one library renderer, byte for byte, as a JavaScript module (1.180551ms)
✔ a revert receipt for another project is unknown and is never retried (1.12255ms)
✔ a granted-user HTTP session cannot reach owner library callbacks (1.351581ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 182.202771
@@ -0,0 +1,76 @@
✔ a signed-in same-origin revert reaches exactly one library.revert and returns the new commit (18.853567ms)
✔ a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata (6.491576ms)
✔ a revert takes a project and a 40-hex commit, nothing else, before the bus is asked (5.647113ms)
✔ a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal (6.08423ms)
✔ a revert whose answer is lost or unusable is reported unknown and is never retried (5.445934ms)
✖ the status read takes one project query and answers the bus result for that project (3.470803ms)
✖ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (5.42219ms)
✔ the status read needs no send; a read-only Console still has no revert (1.428304ms)
✔ library routes are the owner's: a granted user is refused them, and the tester never reverts (0.509039ms)
✔ the page gets the one library renderer, byte for byte, as a JavaScript module (1.123365ms)
✖ a revert receipt for another project is unknown and is never retried (1.209291ms)
✔ a granted-user HTTP session cannot reach owner library callbacks (1.398731ms)
ℹ tests 12
ℹ suites 0
ℹ pass 9
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 198.061254
✖ failing tests:
test at packages/webui/tests/library-routes.test.mjs:100:1
✖ the status read takes one project query and answers the bus result for that project (3.470803ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
403 !== 405
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:107:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 403,
expected: 405,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/library-routes.test.mjs:111:1
✖ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (5.42219ms)
AssertionError [ERR_ASSERTION]: project-not-declared
400 !== 502
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:119:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 400,
expected: 502,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/library-routes.test.mjs:145:1
✖ a revert receipt for another project is unknown and is never retried (1.209291ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
200 !== 503
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:148:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 200,
expected: 503,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,170 @@
diff --git a/packages/bus/tests/channel-proof.test.mjs b/packages/bus/tests/channel-proof.test.mjs
index ba0179832..435d8fa2d 100644
--- a/packages/bus/tests/channel-proof.test.mjs
+++ b/packages/bus/tests/channel-proof.test.mjs
@@ -11,0 +12,2 @@
+// Row 225 (C2-10b) re-pins webui bus.mjs for one STATUS entry, library-revert-not-agent: 409; no proof
+// or transport change.
@@ -32 +34 @@ const baselines = {
- "packages/webui/src/bus.mjs": "604092cb706b152d458f299ff1071f6cd488774629012f96b672f7e9a16c0b2c",
+ "packages/webui/src/bus.mjs": "9f33e9ca4feb7265ae33fd3ddf0ead0b75375d07ecbe9d8e55c618d29edea658",
diff --git a/packages/library/tests/render.test.mjs b/packages/library/tests/render.test.mjs
index b07e26d25..9c861bfe9 100644
--- a/packages/library/tests/render.test.mjs
+++ b/packages/library/tests/render.test.mjs
@@ -3 +3 @@ import assert from 'node:assert/strict';
-import { renderItems, UNREADABLE } from '../src/index.mjs';
+import { renderReply } from '../src/index.mjs';
@@ -4,0 +5 @@ import { renderItems, UNREADABLE } from '../src/index.mjs';
+// Row 225 (C2-10b): renderReply replaces renderItems (design 7.4, the reply schema of 5.2).
@@ -6,6 +7,12 @@ const A = 'a'.repeat(40), B = 'b'.repeat(40);
-const cite = (path, lines, quote, commit = A) => ({ path, commit, lines, quote });
-const options = {
- scopeOf: path => path.includes('/Playground/') ? 'playground' : path.endsWith('.md') ? 'canon' : undefined,
- changed: ({ path, commit }) => path === 'Book One/Characters/Mara Vell.md' && commit === A,
-};
-const render = items => renderItems(JSON.stringify({ items }), options);
+const cite = (path, lines, quote, commit = A, extra = {}) => ({ path, commit, lines, quote, ...extra });
+const web = (url, quote = 'spring tides') => ({ url, quote });
+const reply = (text, citations) => JSON.stringify({ text, citations });
+const CITED = reply("Mara's eyes are grey in chapter 3.", [
+ cite('Book One/Chapters/Chapter 03.md', [41, 41], 'her grey eyes', A, { as: 'canon' }),
+ cite('Book One/Playground/What If.md', [5, 6], 'a storm', B),
+ cite('Notes/Eyes.md', [9, 10], 'green eyes'),
+ web('https://example.invalid/tides'),
+]);
+const CHECK = { citations: [{ scope: 'canon', agent: null }, { scope: 'playground', agent: null },
+ { scope: 'supporting', agent: 'acme/scribe' }, { scope: 'web', agent: null }] };
+const changed = ({ path, commit }) => path === 'Notes/Eyes.md' && commit === A;
@@ -13,21 +20,4 @@ const render = items => renderItems(JSON.stringify({ items }), options);
-// One snapshot per item kind: the plain text both channels show.
-const SNAPSHOTS = {
- fact: [{ kind: 'fact', text: 'Tomas is the ferryman.', cites: [cite('Book One/Characters/Tomas.md', [3, 3], 'the ferryman', B)] }],
- finding: [{ kind: 'finding', text: 'Eye colour differs.', cites: [cite('Book One/Chapters/Chapter 01.md', [41, 41], 'her grey eyes'),
- cite('Book One/Characters/Mara Vell.md', [9, 10], 'green eyes')] }],
- correction: [{ kind: 'correction', text: 'Chapter 1 says grey.', from: 'green eyes', to: 'grey eyes',
- cites: [cite('Book One/Characters/Mara Vell.md', [9, 9], 'green eyes', B)] }],
- question: [{ kind: 'question', text: 'Is the harbor north of the town?', cites: [cite('Book One/Places/Harbor Town.md', [2, 4], 'the harbor', B)] }],
- idea: [{ kind: 'idea', text: 'A storm could close the harbor.', cites: [cite('Book One/Playground/What If.md', [5, 5], 'a storm', B),
- { url: 'https://example.invalid/tides', quote: 'spring tides' }] }],
-};
-const EXPECTED = {
- fact: '1. Fact: Tomas is the ferryman.\n [canon] Book One/Characters/Tomas.md:3 @bbbbbbb "the ferryman"',
- finding: '1. Finding: Eye colour differs.\n [canon] Book One/Chapters/Chapter 01.md:41 @aaaaaaa "her grey eyes"\n'
- + ' [canon] Book One/Characters/Mara Vell.md:9-10 @aaaaaaa "green eyes" (changed since)',
- correction: '1. Correction: Chapter 1 says grey.\n From: "green eyes"\n To: "grey eyes"\n'
- + ' [canon] Book One/Characters/Mara Vell.md:9 @bbbbbbb "green eyes"',
- question: '1. Question: Is the harbor north of the town?\n [canon] Book One/Places/Harbor Town.md:2-4 @bbbbbbb "the harbor"',
- idea: '1. Idea: A storm could close the harbor.\n [playground] Book One/Playground/What If.md:5 @bbbbbbb "a storm"\n'
- + ' [web] https://example.invalid/tides "spring tides"',
-};
+test('free text renders as itself with no citations', () => {
+ for (const body of ['Grey, in chapter 3.', '', '"a JSON string"', '[1, 2]', '42', 'null'])
+ assert.deepEqual(renderReply(body, { check: CHECK }), { kind: 'text', text: body, plain: body });
+});
@@ -35,8 +25,15 @@ const EXPECTED = {
-for (const kind of Object.keys(SNAPSHOTS)) {
- test(`renderItems snapshot: ${kind}`, () => {
- const out = render(SNAPSHOTS[kind]);
- assert.equal(out.kind, 'items');
- assert.equal(out.text, EXPECTED[kind]);
- assert.equal(out.items[0].kind, kind);
- });
-}
+test('a cited reply renders its text, then each cite with the badge from the check result', () => {
+ const out = renderReply(CITED, { check: CHECK, changed });
+ assert.equal(out.kind, 'reply');
+ assert.equal(out.text, "Mara's eyes are grey in chapter 3.");
+ assert.deepEqual(out.citations.map(c => c.badge), ['canon', 'playground', 'supporting', 'web']);
+ assert.equal(out.plain, "Mara's eyes are grey in chapter 3.\n"
+ + ' [canon] Book One/Chapters/Chapter 03.md:41 @aaaaaaa "her grey eyes"\n'
+ + ' [playground] Book One/Playground/What If.md:5-6 @bbbbbbb "a storm"\n'
+ + ' [supporting] Notes/Eyes.md:9-10 @aaaaaaa "green eyes" (agent-written: acme/scribe) (changed since)\n'
+ + ' [web] https://example.invalid/tides "spring tides"');
+ assert.deepEqual(out.citations[0], { badge: 'canon', path: 'Book One/Chapters/Chapter 03.md', commit: A, lines: [41, 41],
+ ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa', quote: 'her grey eyes', canon: true, agent: null, changed: false });
+ assert.deepEqual(out.citations[3], { badge: 'web', url: 'https://example.invalid/tides', link: 'https://example.invalid/tides',
+ ref: 'https://example.invalid/tides', quote: 'spring tides', agent: null, changed: false });
+});
@@ -44,6 +41,2 @@ for (const kind of Object.keys(SNAPSHOTS)) {
-test('renderItems numbers several items and takes a parsed body as well as a string', () => {
- const items = [...SNAPSHOTS.fact, ...SNAPSHOTS.question];
- const out = renderItems({ items }, options);
- assert.equal(out.text, `${EXPECTED.fact}\n${EXPECTED.question.replace(/^1\./, '2.')}`);
- assert.deepEqual(out.items[1].cites[0], { badge: 'canon', path: 'Book One/Places/Harbor Town.md', commit: B, lines: [2, 4],
- ref: 'Book One/Places/Harbor Town.md:2-4 @bbbbbbb', quote: 'the harbor', changed: false });
+test('a parsed body renders as its string does', () => {
+ assert.deepEqual(renderReply(JSON.parse(CITED), { check: CHECK, changed }), renderReply(CITED, { check: CHECK, changed }));
@@ -52,4 +45,4 @@ test('renderItems numbers several items and takes a parsed body as well as a str
-test('without options a path cite has no badge and is never marked changed', () => {
- assert.equal(renderItems({ items: SNAPSHOTS.finding }).text,
- '1. Finding: Eye colour differs.\n Book One/Chapters/Chapter 01.md:41 @aaaaaaa "her grey eyes"\n'
- + ' Book One/Characters/Mara Vell.md:9-10 @aaaaaaa "green eyes"');
+test('without a check result a path cite has no badge, no label and is never changed', () => {
+ const out = renderReply(CITED);
+ assert.deepEqual(out.citations.map(c => [c.badge, c.agent, c.changed]),
+ [[null, null, false], [null, null, false], [null, null, false], ['web', null, false]]);
@@ -58,6 +51,6 @@ test('without options a path cite has no badge and is never marked changed', ()
-const NOTICE_SNAPSHOTS = { 'no-cited-answer': 'No cited answer', 'no-answer': 'No answer', 'turn-failed': 'The turn failed' };
-for (const [notice, text] of Object.entries(NOTICE_SNAPSHOTS)) {
- test(`renderItems snapshot: notice ${notice}`, () => {
- assert.deepEqual(renderItems(JSON.stringify({ items: [], notice }), options), { kind: 'notice', notice, text });
- });
-}
+test('the label and the badge come from the check result only, never from a bad entry', () => {
+ const bad = { citations: [{ scope: 'web', agent: '' }, { scope: 'story', agent: 7 }, null, { scope: 'canon', agent: 'x/y' }] };
+ const out = renderReply(CITED, { check: bad });
+ // A path cite can't be web; a url cite is web by its shape whatever the check says, and never agent-written.
+ assert.deepEqual(out.citations.map(c => [c.badge, c.agent]), [[null, null], [null, null], [null, null], ['web', null]]);
+});
@@ -65,14 +58,8 @@ for (const [notice, text] of Object.entries(NOTICE_SNAPSHOTS)) {
-test('a body without the reply shape shows a fixed line and none of its text', () => {
- const secret = 'free prose the guard would refuse';
- const bodies = [secret, JSON.stringify(secret), '{"items": [], "notice": "other"}', JSON.stringify({ items: [], notice: 'no-answer', extra: secret }),
- JSON.stringify({ items: [] }), JSON.stringify({ items: [{ kind: 'story', text: secret, cites: [] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret }] }), JSON.stringify({ items: SNAPSHOTS.fact, prose: secret }),
- JSON.stringify({ items: [{ kind: 'correction', text: secret, cites: [] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [cite('a.md', [2, 1], 'q')] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [{ path: 'a.md', commit: 'abc', lines: [1, 1], quote: 'q' }] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [{ url: 'https://x.invalid', path: 'a.md', quote: 'q' }] }] }),
- null, 42, '[]'];
- for (const body of bodies) {
- const out = renderItems(body, options);
- assert.deepEqual(out, { kind: 'unreadable', text: UNREADABLE }, String(body));
- assert.ok(!JSON.stringify(out).includes(secret));
+test('a web cite is a link only for https', () => {
+ const urls = { 'https://example.invalid/a': 'https://example.invalid/a', 'http://example.invalid/a': null,
+ 'javascript:alert(1)': null, 'JAVASCRIPT:alert(1)': null, 'data:text/html,x': null, 'not a url': null, '//example.invalid': null };
+ for (const [url, link] of Object.entries(urls)) {
+ const out = renderReply(reply('t', [web(url)]));
+ assert.equal(out.kind, 'reply', url);
+ assert.equal(out.citations[0].link, link, url);
+ assert.equal(out.citations[0].ref, url);
@@ -80,0 +68,27 @@ test('a body without the reply shape shows a fixed line and none of its text', (
+
+test('a write notice renders its fixed line', () => {
+ const body = op => JSON.stringify({ notice: 'library-write', op, path: 'Notes/Eyes.md', commit: B });
+ assert.deepEqual(renderReply(body('write'), { name: 'Scribe' }),
+ { kind: 'notice', op: 'write', path: 'Notes/Eyes.md', commit: B, text: 'Scribe wrote Notes/Eyes.md @bbbbbbb', plain: 'Scribe wrote Notes/Eyes.md @bbbbbbb' });
+ assert.equal(renderReply(body('delete'), { name: 'Scribe' }).text, 'Scribe deleted Notes/Eyes.md @bbbbbbb');
+ assert.equal(renderReply(body('write')).text, 'An agent wrote Notes/Eyes.md @bbbbbbb');
+});
+
+test('a body without the reply or notice shape is free text: the whole body, no citations', () => {
+ const bodies = [
+ JSON.stringify({ text: 't' }), JSON.stringify({ citations: [] }), JSON.stringify({ text: 1, citations: [] }),
+ JSON.stringify({ text: 't', citations: [], extra: 1 }), JSON.stringify({ items: [] }),
+ reply('t', [cite('a.md', [2, 1], 'q')]), reply('t', [cite('a.md', [0, 1], 'q')]), reply('t', [cite('a.md', [1], 'q')]),
+ reply('t', [{ path: 'a.md', commit: 'abc', lines: [1, 1], quote: 'q' }]), reply('t', [cite('a.md', [1, 1], 'q', A, { as: 'playground' })]),
+ reply('t', [cite('a.md', [1, 1], 'q', A, { scope: 'canon' })]), reply('t', [{ url: 'https://x.invalid', path: 'a.md', quote: 'q' }]),
+ reply('t', [{ url: 'https://x.invalid' }]), reply('t', [{ url: 'https://x.invalid', quote: 'q', agent: 'x/y' }]),
+ JSON.stringify({ notice: 'library-write', op: 'move', path: 'a.md', commit: A }),
+ JSON.stringify({ notice: 'library-write', op: 'write', path: 'a.md', commit: 'abc' }),
+ JSON.stringify({ notice: 'library-write', op: 'write', path: 'a.md', commit: A, by: 'x' }),
+ ];
+ for (const body of bodies) assert.deepEqual(renderReply(body, { check: CHECK }), { kind: 'text', text: body, plain: body }, body);
+});
+
+test('an empty citations list is a reply with no cites', () => {
+ assert.deepEqual(renderReply(reply('Grey.', [])), { kind: 'reply', text: 'Grey.', citations: [], plain: 'Grey.' });
+});
@@ -0,0 +1,13 @@
6bdaf870be3d8d03a68eb03f5f6916150bb1b3db134ffbe84018e91d47e6434d packages/bus/tests/channel-proof.test.mjs
0385fc49b445ff730021eb11c20b29027631e0d27350e4591d5a0936ffbdccf6 packages/library/README.md
653ff444ba66cb73e1ca4df1f6037f15a2ec7d3d009cf614a0dc51deabda4b50 packages/library/src/index.mjs
dfcc9f271afc7afdab908395a7f3c402100fa89b4994b20d5bcae3140d5c335e packages/library/src/render.mjs
f3c213f3b0bd080d0bc5837f741a29e324acb91cf0c27144dee08a4113262210 packages/library/tests/render.test.mjs
9f33e9ca4feb7265ae33fd3ddf0ead0b75375d07ecbe9d8e55c618d29edea658 packages/webui/src/bus.mjs
f1a0c9a55b1ae933e205c7e2b9753afe0c2f1fc9b0faa2cf18765396f99ac8dd packages/webui/src/cli.mjs
bf49a22147be0535076640d20149116a744dad9b0b32dd667ee297a061d56134 packages/webui/src/console-bus.mjs
793e7dd9edbe5a55c2deab3c8aef4fbd5b4c838e132e2ac9294043c7176ec6f7 packages/webui/src/public/chat.css
af3a6f382e0d40cf2b28c31db7cb58ba775f1e242651ba42c7426bda99dc0d89 packages/webui/src/public/chat.js
6412fe34bc640433e9cf7610f65d3574a3415c934354d8cb52e056793a81948f packages/webui/src/serve.mjs
f191752f867a112c867ca24268f70aa99ca0903c9208f76bc4bb4c1587cb55df packages/webui/tests/chat-replies-browser.test.mjs
df4ff385ac4ca8244ffa00faf82fcca1f4fcdac8da02a4f4d71233d4df8e1157 packages/webui/tests/library-routes.test.mjs
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Run row225 mutants in disposable snapshots, only on Sage CLEAR.
Commit the tested candidate first. Never change the worktree or shared dependencies.
A timeout, import failure or process failure is not a mutation kill.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tarfile
import tempfile
BROWSER = 'packages/webui/tests/chat-replies-browser.test.mjs'
ROUTES = 'packages/webui/tests/library-routes.test.mjs'
CHAT = 'packages/webui/src/public/chat.js'
SERVE = 'packages/webui/src/serve.mjs'
RENDER = 'packages/library/src/render.mjs'
MUTANTS = [
('M1-badges', RENDER, 'badge: scope, path:', 'badge: null, path:', BROWSER),
('M1-agent-label', RENDER, 'canon: c.canon, agent,', 'canon: c.canon, agent: null,', BROWSER),
('M2-http-links', RENDER, "new URL(url).protocol === 'https:' ? url : null", 'url', BROWSER),
('M3-wrong-commit', CHAT, 'e.file !== e.last', 'cite.commit !== e.last', BROWSER),
('M3-missing-file', CHAT, "SHA.test(e?.file ?? '') && SHA.test(e?.last ?? '')", "SHA.test(e?.last ?? '')", BROWSER),
('M4-fallback', CHAT, 'Array.isArray(limits.fallback) ? limits.fallback.filter', 'Array.isArray(v.replyLimitsFallback) ? v.replyLimitsFallback.filter', BROWSER),
('M4-problem', CHAT, "if (CODE.test(limits.problem ?? '')) parts.push(`<code>${txt(limits.problem)}</code>`);", '', BROWSER),
('M5-send', SERVE, 'if (!send || !libraryRevert || !named)', 'if (!libraryRevert || !named)', ROUTES),
('M5-project-receipt', SERVE, 'result.project !== value.project || ', '', ROUTES),
('M5-commit-input', SERVE, '!COMMIT.test(value.commit)', 'false', ROUTES),
('M5-origin', SERVE, "if (one(req, 'origin') !== ownOrigin(preview)) return refuse(403, 'cross-origin request refused', 'a revert needs the Console\\'s own Origin');", '', ROUTES),
('M5-fetch-metadata', SERVE, "if (one(req, 'sec-fetch-site') !== 'same-origin') return refuse(403, 'cross-site request refused', 'a revert needs same-origin Fetch Metadata');", '', ROUTES),
('M6-duplicate-call', SERVE, 'result = await libraryRevert({ project: value.project, commit: value.commit });', 'result = await libraryRevert({ project: value.project, commit: value.commit }); await libraryRevert({ project: value.project, commit: value.commit });', ROUTES),
('M7-user-fence', SERVE, "if (path === '/api/chat' || path.startsWith('/api/chat/')) return true;", "if (path.startsWith('/api/library/')) return true; if (path === '/api/chat' || path.startsWith('/api/chat/')) return true;", ROUTES),
]
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--clear', required=True, help='Sage CLEAR authorizing this run')
parser.add_argument('--output', required=True, type=Path)
args = parser.parse_args()
repo = Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip())
if subprocess.check_output(['git', 'status', '--porcelain', '--', 'packages'], text=True).strip():
raise SystemExit('Commit tested packages first: snapshot must match candidate.')
head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
before = {p: digest(repo / p) for p in [CHAT, SERVE, RENDER]}
args.output.mkdir(parents=True, exist_ok=True)
results = []
with tempfile.TemporaryDirectory(prefix='mosaic-row225-mutants-') as directory:
scratch = Path(directory)
archive = scratch / 'candidate.tar'
with archive.open('wb') as stream:
subprocess.run(['git', 'archive', 'HEAD', 'packages', 'roles', 'contracts', 'tasks', 'missions', 'package.json', 'RELEASE'], stdout=stream, check=True)
for name, target, old, new, test in MUTANTS:
tree = scratch / name
tree.mkdir()
with tarfile.open(archive) as bundle:
bundle.extractall(tree, filter='data')
(tree / 'node_modules').symlink_to((repo / 'node_modules').resolve(), target_is_directory=True)
path = tree / target
source = path.read_text()
hits = source.count(old)
if hits != 1:
raise SystemExit(f'{name}: expected one anchor, found {hits}')
path.write_text(source.replace(old, new, 1))
command = ['timeout', '-k', '10', '240' if test == BROWSER else '120', 'node', '--test', '--test-reporter=tap', '--test-concurrency=1', '--test-timeout=30000', test]
run = subprocess.run(command, cwd=tree, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=os.environ)
log = args.output / f'{name}.tap'
log.write_text(run.stdout)
path.write_text(source)
restored = digest(path) == hashlib.sha256(source.encode()).hexdigest()
worktree_clean = before == {p: digest(repo / p) for p in before}
if not restored or not worktree_clean:
raise SystemExit(f'{name}: restore/hash check failed')
killed = run.returncode == 1 and 'ERR_ASSERTION' in run.stdout and 'not ok ' in run.stdout
results.append({'mutant': name, 'anchorHits': hits, 'restored': restored, 'worktreeUnchanged': worktree_clean, 'exit': run.returncode, 'killed': killed, 'test': test, 'log': str(log)})
print(json.dumps(results[-1]), flush=True)
shutil.rmtree(tree)
if not killed:
break
after = {p: digest(repo / p) for p in before}
report = {'head': head, 'clear': args.clear, 'worktreeUnchanged': before == after, 'results': results}
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
if before != after or len(results) != len(MUTANTS) or not all(r['killed'] for r in results):
raise SystemExit(1)
if __name__ == '__main__':
main()
-9
View File
@@ -1,9 +0,0 @@
# Gladstone
Builder seat, Codex gpt-6.1-sol, started by Mos (mos-1531, mos-1532). Owns the Case 1/2 host fixes for the 433 P1 rerun,
F1 to F6 (#1816, Vikunja 614).
No launcher in this directory. The seat runs as a T3 thread (thread 433058fc) and works in its own worktree. It has no
credentials of its own; it pushes only its own `gladstone/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-304
View File
@@ -1,304 +0,0 @@
# Hostcheck item 5 and bundle verification (Gitea #1816, Vikunja 614)
Queue row pending from Sage. Owner Gladstone, reviewer Morgana, gate/landing Sage.
Base: fetched origin/refactor 93694c5185d0a7f989d2fe78fb620e44599b744e;
branch gladstone/hostcheck-item5. Scope: mos-1531 / ops-0256, sage-3738 and sage-3740.
## Before and after
Ubuntu 24.04 dpkg 1.22.6ubuntu6.6 prints `root mosaic 750 /usr/bin/bwrap`.
Item 5 compared the literal `0750`, falsely refusing a correctly configured host;
--enforce would unload the profile, close bwrap and record DRIFT.
The candidate accepts exactly four fields, root, mosaic, octal 0750, and $BWRAP.
`0*750` represents all octal spellings numerically equal to 0750 without arithmetic
conversion/overflow. Horizontal whitespace separates fields; extra fields and
embedded newlines refuse. Owner, group and path remain exact. The stat comparison
and existing output strings remain unchanged. Only the item 5 comment also changes.
The two release-transport bundle checks previously ran outside any repository.
Each now creates an empty bare scratch repository, verifies with git -C, and removes
the scratch directory using an EXIT trap. A failure exits the shell before cloning
or fetching. No other run-sheet text changes.
## Tests and mutants
Only packages/cli/tests/case1-hostcheck.test.mjs changes among tests, by appending
three new test() blocks; the existing passing 0750 fake and failing fake are intact.
The pass test exercises 750, 0750 and 000750. The enforce test checks no unload,
chown, chmod, DRIFT or hostcheck log for 750. Refusals independently cover 755,
0755, 4750, 75, 7500, 075, non-octal strings, other owner/group/path, missing path,
extra fields, multiple lines, blank/empty output and a missing fixture file.
| Mutant | Change | Killer | Result |
|---|---|---|---|
| M1 | Restore the old literal root mosaic 0750 comparison (drop 750) | new check pass test | Unrun |
| M2 | Accept any mode while preserving owner/group/path | new refusal test | Unrun |
Mutant procedure: preserve script bytes, assert one replacement, run the named
killer alone, require failure for its intended assertion, restore in finally and
check the original bytes/hash. Run the original targeted file again after restore.
## Verification status
Untested. Sage-3738 prohibits all Node until CLEAR because the joint full gate
runs alone. Sage-3740 explicitly directs pushing this untested build for Morgana's
static review before CLEAR. This supersedes test-first execution for this draft;
no Node, tests, node --check, full suites or live-host operations have run.
Queue next refused linked-worktree execution; Sage owns queue creation/movement.
Push only gladstone/*, with MOSAIC_GITEA_CREDENTIAL_FILE unset; no amend/force.
Static draft receipt: `git diff --check` passed. Test-file numstat against the
base is `46 0 packages/cli/tests/case1-hostcheck.test.mjs` (added/deleted).
## Scope and implementation amendment (mos-1534, sage-3741)
F1 correction: Sage requires precisely `750` or `0750` and exactly single spaces,
so the candidate now uses `0?750` and literal spaces. It refuses 00750/000750,
a doubled space and a tab. This supersedes the initial numeric-equivalence design
above. Only this row's own appended tests change; no base test line changes.
F3: case2-unit.sh plain() adds @. abs_plain is its only caller and requires an
absolute normalized path, so @ cannot become an ExecStart prefix. In sed @ is
ordinary replacement text (the delimiter is |); &, backslash and | still refuse.
Environment=MOSAIC_CLAUDE_BIN carries @ literally; the parser compares the complete
value, with no shell evaluation. @anthropic-ai also cannot match the residual
@[A-Z_]+@ token check. Tests render/reinstall with a regular executable under
@anthropic-ai, checking Environment and the new copy's WorkingDirectory. Since a
nonroot test cannot create root ownership, a stat fake models root:0755 only for
that file and its ancestor chain. The real file type/executable checks, render,
installed-unit parser, and all other stat calls remain active. A separate block
refuses space, %, $, both quote characters, backslash, & and |.
F4: copy_tree changes to / before archive/npm/find, after all its path arguments
are absolute. copy_bundle retains its existing relative-bundle resolution before
calling copy_tree. Checkout copies have absolute REPO and COPIES too. Source-only
use from stack-release.sh changes no cwd because copy_tree is not called there.
Audit of every AS_USER command in stack-upgrade.sh: fetch and merge use git -C;
plan, gate, revert, confirm-rollback, stage (activate/rollback), confirm and rehearse
invoke stack-release.sh, which changes to its checkout at line 34. No other
AS_USER command needs a fix. The new test enters a directory mode 0111, verifies
that ls cannot read it, then runs both a relative-bundle copy and checkout copy,
asserting success, no find restore error, and an immutable completed copy.
Additional mutants (unrun under the same no-Node hold):
| Mutant | Change | Killer | Result |
|---|---|---|---|
| M3 | Drop @ from plain() | new factory scoped Claude-path test | Unrun |
| M4 | Remove copy_tree's cd / | new unreadable-cwd copy test | Unrun |
All four fixes remain untested. Follow-up is an additive commit on the pushed
branch, per sage-3741, with no amend or force. Morgana's static F1/F2 read of
f30c74b12 was CLEAN (morgana-0205); F3/F4 and execution remain open.
Morgana N1: the outer bundle-verification failure now prints
`STOP: bundle verify failed; do not continue` instead of exiting the interactive
root shell. The subshell still returns failure and cleans up. This follows the
sheet's human read-and-stop convention and avoids sending remaining pasted lines
to a parent shell after exiting sudo -i. Inner mktemp failure still exits only the
subshell. This supersedes the initial outer-shell failure behavior noted above.
## File numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 4 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 114 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 12 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 29 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## F5 (mos-1536 / ops-0260): explicit notifier decision
Before: no run-sheet step creates the required notifier decision, and precheck
accepts a host whose bus will refuse missing notify.json. After: section 9a
requires the mosaic user to create its business notify directory at 0700 and an
explicit {"notifyVersion": 1, "binding": null} file at 0600 before section 10.
The shell runs with cwd / and umask 077; noclobber refuses an existing decision.
Named binding is explicitly a later per-host choice requiring Mos's go.
case2-install.sh is unchanged and makes no silent default.
case2-precheck.sh's existing facts helper dynamically imports readNotifyConfig
from cli.mjs and calls it directly. File type, uid, exact 0600 mode, JSON keys,
version and binding-name validation therefore stay identical to the bus. The
shell checks the business notify directory with existing private_dir (same uid,
not a symlink, exactly 0700) and prints the new notifier config line. No notifier
is started and no configuration is written. Tests append pass cases for null and
a valid named binding, and refusal cases for missing file, 0644, 0755 directory,
extra key, malformed JSON, invalid binding/version and a symlinked file.
M5 (unrun): remove the notifier shell check, including its refusal branch; the
new notifier refusal test must fail. Also include Morgana's F3 negative mutant
M3b (allow $ in plain), killed by the new unsafe-character test.
Existing positive precheck tests need an explicit notify fixture. Approval
requested via Sage in gladstone-0006/-0007 for minimal setup-only additions to
case2-unit.test.mjs host(t) and case2-install.test.mjs world(t); assertions remain
unchanged. Those additions are not yet applied. Correction to gladstone-0007:
case1-accept.test.mjs also names case2-precheck.sh, but uses a stand-in; it does
not execute the changed script and needs no fixture amendment.
Execution remains held by Sage: no Node/tests run. Morgana-0207 static F1-F4
verdict on a75532db1 is CLEAN; execution and F5 review are still open.
F5 acceptance from mos-1536 (for review):
1. Before section 10, as mosaic, explicitly create <dataRoot>/notify/<business>
at 0700 and notify.json at 0600 with default binding null; a later named
binding needs Mos's go. Do not make case2-install.sh write it.
2. Precheck must refuse missing file, non-0600 file, directory looser than 0700
and a document outside the CLI's exact shape. Reuse/match cli.mjs validation,
and name the new line in this run-sheet step's Precheck entry.
3. Append-only test blocks cover pass, missing, 0644, 0755 dir, extra key; record
a skip-check mutant, the touched test file and its numstat.
4. F3/F4 precede F5, each follow-up commit stays on gladstone/hostcheck-item5;
send pushes to Sage/Morgana with Mos cc. No Node until Sage CLEAR.
Static F5 draft receipt: git diff --check passed. Existing fixture approvals
remain pending; F5's new pass test creates its own explicit decisions. No claim
that the old positive precheck tests pass without the pending setup changes.
## F5 candidate file numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 6 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 179 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 34 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 65 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## Fixture approval and separate follow-up (mos-1537)
Mos approved exactly setup-only additions to case2-unit.test.mjs host(t) and
case2-install.test.mjs world(t): each gets three added lines for a 0700
notify/acme directory and 0600 notify.json with {"notifyVersion":1,"binding":null}.
No existing line, fake or assertion changes or is deleted. F5's missing-file
negative case removes the file after setup, so the new default cannot mask it.
The newly appended install test checks preserved bytes and mode after success,
and separately removes the decision before install and checks it stays absent.
case1-accept.test.mjs is unchanged. This follow-up is committed separately on
33950cf9b as directed, with no amend. No Node/tests run; suite claims remain open.
Morgana-0211 found F5 source/run sheet CLEAN statically; the setup prerequisite B1
is addressed by this approved fixture patch, pending execution.
Additional planned mutants from Morgana-0211 (unrun): M5b drops private_dir,
killed by the directory fault; M5c prints NOTIFY ok unconditionally, killed by
the document faults. Existing test preservation is checked by git diff showing
only additions (numstat below).
Fixture follow-up numstat: case2-unit.test.mjs +3/-0,
case2-install.test.mjs +18/-0. Against the original base: case1-hostcheck.test.mjs
+48/-0, case2-unit.test.mjs +68/-0, stack-copy.test.mjs +16/-0,
case2-install.test.mjs +18/-0. git diff --check passed.
## F6 and notify parent correction (mos-1537 / ops-0261)
Before: install creates a copy but no human CLI wrapper; the sheet's
stack-copy.sh check --units external therefore fails even with correct units.
After: the sheet explicitly places a wrapper step after successful install and
before that check. On Mos's separate live-step go, mosaic changes cwd to /,
creates $HOME/.npm-global/bin if absent, then invokes its clone's stack-copy.sh
wrapper <sha> --create. Existing wrappers refuse and require review; install is
unchanged. This is documentation of a gated host action, not execution of it.
There are no tests pinning these sheet steps to adjust.
Section 9a now explicitly creates/chmods <dataRoot>/notify and then its business
leaf, each 0700. This addresses ops-0261's observation that mkdir -p -m only
controls the leaf. The existing umask 077 remains. All exits are inside the
unprivileged shell, so no failure exits the operator's interactive root shell.
Static receipt: git diff --check passed. No Node, tests or live action. F6 and
this parent-directory documentation correction are a separate commit after the
approved fixture-only 5fd2328b6 follow-up. Reviewer should retain the earlier
F1-F5 source verdicts and inspect this delta. Queue row still pending from Sage.
## F6 candidate file numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 10 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 241 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 51 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-install.test.mjs` | 18 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 68 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## F6 gate citation (sage-3747)
Added scripts/stack-copy.sh:3 to the F6 wrapper step's explicit Mos-go requirement.
This is doc-only; commands and tests are unchanged. Sage ruled the delete-after-
setup missing-file negative meets mos-1537, so no fixture change is needed.
Morgana-0213 found the fixture/F6 deltas CLEAN statically. No Node/tests run;
Morgana's eventual run waits for Sage's row and CLEAR after the 220+216 landing.
Numstat for this follow-up: host-run-sheet.md +1/-1; BUILD.md +9/-0.
Static receipt: git diff --check passed.
Correction: 0f3d874d4 added 10 BUILD.md lines, not 9; the git numstat is authoritative.
## First review run and F4 test correction (morgana-0217)
Morgana ran af201b4a9 (6fa47e07d only relocates this file) under her authorized
slot with timeout -k 10 and TMPDIR under /mnt/storage/scratch/tmp. Receipts:
/mnt/storage/scratch/morgana-g320-out/base-summary.txt and mutants-summary.txt.
| Targeted file | Passed | Failed |
|---|---:|---:|
| case1-hostcheck.test.mjs | 19 | 0 |
| case2-unit.test.mjs | 47 | 0 |
| case2-install.test.mjs | 24 | 0 |
| stack-copy.test.mjs | 17 | 1 |
Seven of eight mutants were killed: M1 literal0750, M2 anymode, M3a drop@,
M3b allow$, M5 drop shell block, M5b drop private_dir, M5c always NOTIFY ok.
M4 drop cd / SURVIVED: base and mutant both failed only setup's cleanup hook.
This is a test defect, not a green acceptance result. The 0111 cwd retained
search permission and did not reproduce the inaccessible inherited cwd. It
also blocked setup's chmod -R cleanup because the directory remained unreadable.
B1 correction touches only this row's appended F4 test: the child shell enters
an initially searchable directory, chmods it 000, checks both ! -r and ! -x, then
execs stack-copy. The parent asserts all read/search bits remain cleared. The
bundle now lives outside cwd and is passed absolutely, matching the real flow;
existing row232 coverage still tests relative bundles from an accessible cwd.
Both bundle and checkout paths remain covered.
B2 correction: each child run/assertion sequence has a finally that restores the
cwd to0700 before setup's existing cleanup. No old setup/test lines change.
Production scripts are unchanged; the cd / fix remains correct by static review.
Correction is untested locally: no builder Node/test CLEAR was given. Morgana
will rerun stack-copy whole, M4 and residue cleanup on the pushed correction;
other files need no rerun unless changed. git diff --check passed.
## F4 corrected candidate numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 12 | 0 |
| `agents/gladstone/work/queue-320/BUILD.md` | 304 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 51 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-install.test.mjs` | 18 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 68 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 22 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
-171
View File
@@ -1,171 +0,0 @@
# Row 321: F7 wrapper environment and F10 first adoption (#1817)
Owner Gladstone; reviewer Boyd (separate Codex thread); queue/review/landing Sage.
Authorized by sage-3791. Branch gladstone/row321 starts at fetched origin/refactor
833ec1cc8d7c11842efb82e57c1a0b256c8d22d7. The row320 branch is unchanged.
Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, section
"Brief: F7, the wrapper step's PATH and umask under sudo", including F10;
verified blob 7333f94ba9cfd58f87c5a3580d428b84c65845b6 (requested pin 7333f94ba9cf).
Only the run sheet and this BUILD.md change. No scripts, tests, config, credentials,
queue state, deployment or live host operation changed. No tests or Node commands run.
## Before and after
F7: sudo secure_path omits the pinned Node directory, so wrapper creation reaches
instance.sh's bare node and exits127. An inherited umask can leave the bin dir0775.
The MOSAIC_WRAPPER heredoc now sets umask077 and the sheet's existing pinned Node
PATH before mkdir. One sentence names secure_path and why the step sets both.
The existing Mos go remains explicit. No wrapper command is executed here.
F10: first install omits adoption, so the first activate refuses no-pointer.
The release-transport section now places explicit adoption after both units are
running (section10) and before any activate/upgrade, run as mosaic on Mos's go by
Jason or Mos, citing stack-release.sh:14. It uses the configured dev instance and
external units, with the same pinned Node PATH under sudo.
Adjacent to the upgrade command, the sheet now says only the first upgrade after
adopt takes --no-rehearsal: adopt writes no backup; missing flag refuses no-backup;
later upgrades omit it, because a backup with that flag refuses rehearsal-required.
Source checks: stack-release.sh pointer_for_business refuses no-pointer and directs
adopt; stack-upgrade.sh rehearse handles rc3/no-backup and rc0/rehearsal-required.
This records existing behavior and authority; it adds no requirement or live go.
## Test-reference grep (negative and positive control)
The required negative grep covers both F7 and F10 because neither is read by any
matched test. The positive control locates the precheck path that the unit/install
tests actually execute, showing the pathspecs reach their test files.
Negative grep:
```sh
git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 1
# no matches
```
Positive control:
```sh
git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 0
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----
```
## Run-sheet diff against the pinned base (git diff --unified=0)
```diff
diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index d2ba28d7c..96fd4f363 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -209,0 +210,9 @@ sudo -u mosaic -H git clone -q -b refactor /srv/mosaic/incoming/<sha>.bundle /sr
+After a first install, once both units are running (section 10), adopt the installed copy before
+any activate or upgrade. This step runs only on Mos's go, with Jason or Mos running it
+(`scripts/stack-release.sh:14`):
+
+```sh
+sudo -u mosaic -H env PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin \
+ /srv/mosaic/src/mosaic-stack/scripts/stack-release.sh adopt <business> --instance dev --units external
+```
+
@@ -211,0 +221,4 @@ Each upgrade, before `stack-upgrade.sh upgrade`:
+Only the first upgrade after adopt takes `--no-rehearsal`: append it to the upgrade command below.
+Adopt writes no backup, so omitting the flag then refuses `no-backup`. Subsequent upgrades omit it;
+when a backup exists, supplying it refuses `rehearsal-required`.
+
@@ -246,0 +260 @@ step requiring Mos's go (`scripts/stack-copy.sh:3`); the install does not create
+The step runs under sudo's `secure_path`, so it sets its own PATH and umask.
@@ -250,0 +265,2 @@ cd / || exit 1
+umask 077
+PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
```
## Verification
No matching test reads the changed lines, so the stop condition did not apply.
No test run or slot is required under sage-3791. Verification is static: the
required grep and positive control above, exact brief/base pins, scoped diff,
and git diff --check. Reviewer Boyd and Sage own review/landing; no approval
or landing is claimed here.
Receipt correction: the first commit embedded ordinary diff context lines that
contain a single space. git diff --check flagged those as trailing whitespace.
This follow-up uses the exact zero-context diff instead. The run sheet is
unchanged, no tests or Node commands run, and no amend or force-push is used.
## F11 addition (sage-3805, mos-1549, ops-0275)
The brief was extended after F7/F10 drafting and re-pinned at revision3733:
canonical local commit a9bae9af2, same F7 section anchor, full verified blob
0e684ef2103447dcc2013b6bf81b1c4ca5bbc0bd (requested pin0e684ef21034).
Read using git show from the canonical checkout; no fetch, rebase or queue write
is required. This new pin supersedes the earlier brief pin for this follow-up.
Defect: a precheck one second after unit start found no bus.sqlite yet.
Section10 now requires loopback healthz success after a unit start before any
case2-precheck.sh, including section9's --library-status. It uses the same
127.0.0.1:<port>/healthz curl form as scripts/case1-accept.sh:458. The subshell
tries at most30 times, each curl capped at2 seconds, with1 second between failed
attempts (at most89 seconds plus scheduling overhead). Exhaustion prints STOP
and exits only the subshell. The instruction says to stop before precheck/adopt;
it does not exit the interactive operator shell. The section9 library-gitea row
now explicitly requires this bounded wait before --library-status. <port> is the
installed Console unit's --port, not an assumed port or business name.
No host action, test, Node command or credential access occurred.
Required grep repeated for the widened scope:
```sh
git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit1, no matches
git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit0, positive control; output follows
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----
```
No test reads the changed run-sheet lines; the stop condition does not apply.
No test run is needed under the docs-only brief. Static verification checks both
locations, retry limit/timeout, the loopback URL, unchanged F7/F10 and scoped diff.
F11 delta against d9400f0d3 (git diff --unified=0):
```diff
diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index 96fd4f363..e9fa87f24 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -322 +322 @@ file with `stat -c '%U %a %n' PATH`, never `cat`. `<business>` is the host's bus
-| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | `case2-precheck.sh --business <business> --library-status`, after the bus is up: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
+| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | After a unit start, wait for loopback `/healthz` with the bounded retry in section 10 before `case2-precheck.sh --business <business> --library-status`: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
@@ -357,0 +358,16 @@ None by hand. I2's `case2-unit.sh` enables both units, starts the bus, then the
+After a unit start, wait for the Console's loopback `/healthz` to succeed before running
+`case2-precheck.sh`, including `--library-status` in section 9. Replace `<port>` with the installed
+Console unit's `--port`. This retry is bounded to 30 attempts, each with a 2-second timeout, with
+1 second between failed attempts. If it prints STOP, stop here; do not run the precheck or adopt.
+
+```sh
+(
+ attempts=0
+ until curl -fsS --max-time 2 -o /dev/null 'http://127.0.0.1:<port>/healthz'; do
+ attempts=$((attempts + 1))
+ [ "$attempts" -lt 30 ] || { echo "STOP: loopback healthz did not become ready; do not continue" >&2; exit 1; }
+ sleep 1
+ done
+)
+```
+
```
-9
View File
@@ -1,9 +0,0 @@
# Goldie
Builder seat, Codex gpt-6.1-sol, started by Sage under mos-1542 and mos-1546. Owns F8 and F9, the library persona
import in `scripts/case2-install.sh` (#1818), for the 433 Run B rerun.
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
own; it pushes only its own `goldie/*` branches through the repository credential helper (mos-0981), never `refactor`,
`main` or another seat's branch. Claude seats orchestrate and review the install path (mos-1542 rule 4). Registered by
Sage on 2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-69
View File
@@ -1,69 +0,0 @@
# Row 322 / #1818 — F8 and F9
Goldie, 2026-10-23. Branch goldie/row322, base 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7.
Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, F8/F9 section at 7333f94ba9cf.
Issue #1818 is supplied by Sage; no new issue is needed. Reviewer Morgana; Sage owns landing/full gate.
## Implementation and authority
F8 runs the importer from `$COPIES/$COMMIT/scripts/case2-persona-import.mjs`. This uses Node's normal ESM resolution, finding yaml beside the copy and the relative harness module in that same commit. It avoids a custom loader or ambient resolution setting. The copy closure already includes harness/src and business/src, but omitted the importer itself; COPY_PATHS now includes that one script. No new dependency or dependency version change.
F9 selects the first line beginning with an Error type (including TypeError etc.) or ERR_, falling back to the last line for the importer's handled refusal. awk exits after the first matching line and emits exactly one line, keeping the stack and version banner out of the install refusal. Anchoring avoids selecting Node's earlier `throw new ERR_MODULE_NOT_FOUND(...)` source excerpt instead of the actual Error line.
Mos-1550 authorizes the copy-path addition, setup-only additions to world(t), and adding the single new filename to any existing copy-file pin. Existing test edits outside the appended row-322 block:
- case2-install.test.mjs: the fake stack-copy setup gains a loop copying the importer, harness bundle.mjs/tools.mjs, and business vocabulary.mjs; it copies yaml into the fake copy's node_modules. The existing last template-string line is extended to add those setup commands. No existing assertion changes.
- packages/webui/tests/pinned-copy.test.mjs:76: the scripts file-list assertion gains only `case2-persona-import.mjs`, in sorted order. No other change in this test.
The new append-only block builds a scratch clone outside the repository with no node_modules, real installer and importer, stubbed bootstrap/business validation, and a copy with the real importer, relative module closure and yaml. F8 checks installed persona bytes/mode; F9 removes yaml from that copy to produce a real uncaught Node missing-package error, then checks one refusal line carries the cause and no stack/banner. Existing tests missed F8 because the real importer ran from the development checkout, which has yaml installed; the fake copy held only scripts/mosaic. The old fixtures thus never exercised the dependency boundary or an uncaught importer load error.
## Class check and copy-test audit
Before change:
```
grep -n 'node "$REPO/' scripts/case2-install.sh scripts/case2-precheck.sh scripts/stack-copy.sh
scripts/case2-install.sh:185: out="$(node "$REPO/scripts/case2-persona-import.mjs" ...
```
After change: same grep has no hits (exit 1). Direct clone-side .mjs entry points in the three scripts with a bare package import: **scripts/case2-persona-import.mjs → yaml**, moved to the copy by F8. No other direct entry point imports a bare package. Precheck's inline Node programs dynamically import cli/src/config.mjs, business/src/index.mjs, library/src/index.mjs and cli/src/cli.mjs; those entry files import builtins/relative modules only. Library reader.mjs uses its own front-matter parser and has no yaml import. stack-copy sources instance.sh, whose mosaic-config.mjs imports builtins only. Installer bootstrap likewise runs mosaic-config.mjs, and business validation runs business/src/cli.mjs, also no direct bare imports. No other product fix made.
Audit commands (read-only):
```
rg -n 'COPY_PATHS|copy carries|copy.*(hash|sha256|files|file set)|sha256.*copy' packages/*/tests/* scripts/test-*.sh
rg -n 'COPY_PATHS|stack-copy\.sh|readdirSync|sha256|deepEqual' packages/webui/tests/pinned-copy.test.mjs packages/cli/tests/stack-copy.test.mjs
```
Relevant hits: cli/tests/stack-copy.test.mjs:35-47 checks harness file closure (positive control: COPY_PATHS at :36), :52 asserts a required-file subset, :248-264 computes hashes/modes and compares bundle/check-out copy trees dynamically, :274 compares another bundle tree. None freezes a complete path list/hash. webui/tests/pinned-copy.test.mjs:75 pins top-level entries (unchanged), :76 pins scripts' exact file set (approved one-path update). Other readdir hits in those files check cleanup/existing-target behavior, not COPY_PATHS identity. Broad initial grep also hits business/tests/helpers.mjs:33, cli/tests/stack-upgrade.test.mjs:295, webui/tests/views.test.mjs:172, cli/tests/stack-release.test.mjs:1751 and :1987; inspected contexts are unrelated comments/UI copy or different fixture copies. No scripts/test-*.sh hit for these path/hash patterns.
## Verification plan
Pending Sage CLEAR before any test: run new tests on fixed build first, then whole case2-install.test.mjs, whole stack-copy.test.mjs and webui pinned-copy.test.mjs. Demonstrate expected failure at 094124b4e with new block applied (baseline importer path), and kill the `$REPO` reversion mutant. Record all outcomes. First unexpected new-test red is recorded/reported under mos-1272; existing-test edits outside mos-1550 or a second unexpected red stop for Sage.
No tests run yet. No commit/push yet. No host changes, live state, secrets, deploy or release actions.
## Verified results — sage-3808 CLEAR, 2026-10-23T22:47Z
All runs were serialized with `TMPDIR=/mnt/storage/scratch/tmp/goldieuQ2664` (created by `mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX`), using `timeout -k 10 90` for focused tests and `timeout -k 10 180` for whole files/negative-proof driver. Node v26.8.1. Dependencies were read through the ignored, uncommitted node_modules symlink approved by sage-3804; each scratch clone had no node_modules. No /tmp fixture was used.
| Run | Result | Evidence |
| --- | --- | --- |
| New tests, first run | 2/2 pass, exit 0 | test-new.log |
| Whole case2-install.test.mjs | 26/26 pass, exit 0 | test-install.log |
| Whole stack-copy.test.mjs | 18/18 pass, exit 0 | test-copy.log |
| Whole webui pinned-copy.test.mjs | 10/10 pass, exit 0 | test-pinned-copy.log |
| Installer from 094124b4e, new tests | 0/2 pass, exit 1, expected red | baseline-094124b4e.log |
| F8 mutant: restore `$REPO` importer path only | 0/1 pass, exit 1, killed | mutant-F8.log |
| F9 mutant: restore tail-only refusal only | 0/1 pass, exit 1, killed | mutant-F9.log |
| Fixed installer restored, new tests rerun | 2/2 pass, exit 0 | test-restored.log |
The baseline proof temporarily installs the exact installer bytes from `git show 094124b4e:scripts/case2-install.sh`, retaining the newly appended tests and approved fixture setup. Relevant installer/importer/copy/test source paths have no changes between N and the branch base 833ec1cc8 (`git diff 094124b4e 833ec1cc8 --` those paths is empty). It is a targeted baseline proof, not a full checkout/suite run at N. Baseline failures show `persona-import: Node.js v26.8.1`; the F8-only mutant exposes the underlying `Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml'` from the scratch clone. F9-only mutant again prints the banner. All expected failures are deliberate acceptance checks; no unexpected first-run red occurred.
negative-proof.py records each run, checks the expected failure count/exit, restores in a finally block, and verifies fixed installer bytes after each run. Restored SHA-256 each time: `c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462`. Final focused green confirms restoration. git diff --check passed.
`git diff --numstat -- packages/cli/tests/case2-install.test.mjs`: **57 additions, 1 deletion**. Outside the new appended block, original line 70's template-string close moves after four new setup lines: the existing mkdir/empty mosaic/echo command is unchanged, followed by the `for file ...` line, mkdir/cp loop body, `done`, and mkdir/cp yaml line. All pre-existing assertions remain byte-for-byte unchanged. The only pre-existing assertion edit anywhere is the expressly approved one-filename addition at packages/webui/tests/pinned-copy.test.mjs:76.
Sage's full package/script gate and Morgana's review remain pending. This builder ran only the approved three whole test files and targeted acceptance checks. Sage retains queue transition/review posting and integration authority; no PR was created by Goldie.
Evidence log lines have trailing whitespace removed for git diff --check; test content and outcomes are unchanged.
@@ -1,4 +0,0 @@
c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462 scripts/case2-install.sh
cb1a4fef997b9696406e324878d7a0e5181892622162c9ce77a8fd625b5cd59a scripts/stack-copy.sh
a7b02af4b5df92e6d727fe8c67565ea0483d6d57531765dee68f54fd97e7c23f packages/cli/tests/case2-install.test.mjs
2574b414459f4cfb187de64b9a2fbe34ab4b4d64f582caae8baf6a1985e8cd3c packages/webui/tests/pinned-copy.test.mjs
@@ -1,70 +0,0 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.885888
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 2 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 91.239243
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.processPendingSubtests (node:internal/test_runner/test:974:18)
Test.postRun (node:internal/test_runner/test:1542:19)
Test.run (node:internal/test_runner/test:1467:12)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..2
# tests 2
# suites 0
# pass 0
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 287.462723
@@ -1,37 +0,0 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.468116
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml' imported from /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/clone/scripts/case2-persona-import.mjs
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 194.231229
@@ -1,41 +0,0 @@
TAP version 13
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 1 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 92.814376
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 200.516406
@@ -1,6 +0,0 @@
baseline-094124b4e: exit 1, expected failures 2
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F8: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F9: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
@@ -1,39 +0,0 @@
"""Run row-322 expected-red proofs, always restoring installer bytes."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
installer = root / 'scripts/case2-install.sh'
logs = Path(__file__).resolve().parent
fixed = installer.read_bytes()
baseline = subprocess.check_output(['git', 'show', '094124b4e:scripts/case2-install.sh'], cwd=root)
f8 = fixed.replace(b'$COPIES/$COMMIT/scripts/case2-persona-import.mjs', b'$REPO/scripts/case2-persona-import.mjs')
assert f8 != fixed
old_error = baseline.splitlines()[185]
new_error = fixed.splitlines()[185]
f9 = fixed.replace(new_error, old_error)
assert f9 != fixed
try:
for label, source, pattern, failures in [
('baseline-094124b4e', baseline, 'row 322', 2),
('mutant-F8', f8, 'row 322 F8', 1),
('mutant-F9', f9, 'row 322 F9', 1),
]:
installer.write_bytes(source)
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
f'--test-name-pattern={pattern}', 'packages/cli/tests/case2-install.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(logs / f'{label}.log').write_text(result.stdout)
print(f'{label}: exit {result.returncode}, expected failures {failures}', flush=True)
assert result.returncode == 1, result.stdout
assert f'# fail {failures}\n' in result.stdout, result.stdout
assert 'ERR_MODULE_NOT_FOUND' in result.stdout, result.stdout
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
finally:
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
@@ -1,26 +0,0 @@
✔ a copy carries every file the harness modules read from it (891.310838ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (537.82282ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (713.621566ms)
✔ row 190 N6: check --instance passes only when the wrapper exports the instance's MOSAIC_CONFIG (657.082198ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (799.380065ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (499.872009ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3824.368055ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (668.500662ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (399.490955ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (318.110324ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (347.788225ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (403.142763ms)
✔ inherited service.d override refuses without changing the unit (384.337811ms)
✔ inherited drop-in unreadable directory or file refuses before writing (370.630432ms)
✔ row 232: copy --bundle copies a SHA the bundle's refactor reaches, byte-identical to copy SHA, and refuses the rest (2257.394077ms)
✔ row 232: wrapper --create writes the first wrapper only, the same bytes wrapper ID writes, and refuses over any file (779.276348ms)
✔ row 232: check --units external reads the system units, the default reads the user units (641.792634ms)
✔ Vikunja 614: bundle and checkout copy leave an unreadable caller directory before find (1047.143772ms)
ℹ tests 18
ℹ suites 0
ℹ pass 18
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 15638.417631
@@ -1,34 +0,0 @@
✔ the library install writes the business file, personas and slot dir, makes the copy and starts nothing (258.707115ms)
✔ a library re-run passes and leaves every file as it was (391.278216ms)
✔ a re-run refuses business-file-differs and leaves the file unchanged (274.686097ms)
✔ a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing (274.084555ms)
✔ the factory install writes no library mapping, persona or slot dir (175.791904ms)
✔ the factory install refuses a library project and bus.tester off, before anything is written (72.301025ms)
✔ the library install refuses bus.tester on and a persona var in the input (76.469642ms)
✔ the install refuses root, a business id mismatch, another --home and a config dir with group bits (57.89739ms)
✔ the library install needs --personas (5.495139ms)
✔ a validate refusal removes the business file this run wrote (230.311754ms)
✔ N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte (397.123924ms)
✔ a clean library host passes the precheck with no mirror yet (404.408689ms)
✔ the library precheck refuses a persona file at 0644 (399.17119ms)
✔ the library precheck refuses a persona whose digest isn't persona.sha256 (402.47159ms)
✔ the library precheck refuses a persona dir with group bits (390.108413ms)
✔ the library precheck refuses a business file at 0644 (402.829663ms)
✔ the library precheck refuses a changes.jsonl at 0644 (573.861228ms)
✔ the library precheck refuses a Discord binding (386.405794ms)
✔ --library-status refuses library-mirror-missing before the bus has made the mirror (309.213305ms)
✔ --library-status refuses library-never-synced on a fresh mirror (330.922116ms)
✔ --library-status names a recorded lastError before the null lastFetch it leaves (N3) (335.576032ms)
✔ --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes (379.69927ms)
✔ --library-status takes no --profile (5.544173ms)
✔ Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent (465.015156ms)
✔ row 322 F8: library personas install from the copy with no clone node_modules (139.662489ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (95.452368ms)
ℹ tests 26
ℹ suites 0
ℹ pass 26
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7340.739711
-10
View File
@@ -1,10 +0,0 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (178.794273ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (101.739988ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 394.59041
@@ -1,18 +0,0 @@
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (872.104795ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (1245.376299ms)
✔ copy writes nothing when an archived path is missing at the commit (409.033887ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (548.743561ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (534.468073ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (541.303916ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (547.966283ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (856.865742ms)
✔ serve refuses a --checkout that is not an absolute directory path (274.552284ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (49.311324ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6003.825092
@@ -1,10 +0,0 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (148.410784ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (94.205865ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 344.990842
-141
View File
@@ -1,141 +0,0 @@
# Row 323 / #1819 — Case 1 factory business file
Goldie, 2026-10-23. New isolated worktree `/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323`, branch `goldie/row323`, base local refactor `da537e7d159be0bf39a36eaaa65cd16c20373b72` (descends from brief commit 68f3bded5). Row322 worktree remains unchanged.
## Sources and input boundary
Read brief docs/plans/2026-10-23_case1-business-file-brief.md, row195 DESIGN.md (host values sections1/2/6/7), installer factory HELPER, precheck profile checks, case1-accept F3/F7, business loader and credentials/launch checks, role definitions and business fixture helpers. No host access or credential read.
DESIGN fixes business `mosaic-stack`, OS user mosaic, home `/srv/mosaic`, config `/srv/mosaic/.config/mosaic-dev`, data `/srv/mosaic/.mosaic-dev`, Claude only, tester on. Human id is explicitly left to the go (:82/:300), no project root is named. Business loader requires at least one project/root (:261-275), tracker.sync bot/botId/Vikunja credential reference (:85-94), launch.models and nonempty launch.instances excluding launcher (:186-193). These host/business values are not supplied by DESIGN. Per sage-3815 and brief, stop configuration rather than guess. Asked Sage in goldie-0008 for exact values.
Recommended minimal role set: chief-of-staff instance chief with claude-code harness plus credential-free assistant solely because launch.instances must contain a declared non-launcher. Both arbiters may reference chief; no additional PM/CTO role credential required. This remains a recommendation pending input, not a written host config.
Literal top-level `library` is ignored by the installer plan and rejected later by business validation as unsupported key; a library project is refused by the factory plan as profile-library-business. Asked Sage which intended refusal assertion to use; recommend covering both through their real paths without a product change.
## Authority and status
Sage-3815 explicitly authorizes claimant queue moves despite brief's generic no-queue-writes boundary. Claim op goldie-0323-start-1 retries every20s on lock contention per sage-3814; no lock touched. Tests and mutant require new Sage CLEAR; none run. No business JSON or test written yet. No commit/push yet. BUILD/SESSIONS/BUILD-LOG notes record investigation only.
## Rulings and static blocker, 23:02Z
Sage-3820 accepts chief + credential-free assistant because launch.instances must be nonempty and exclude chief, both arbiters chief. It accepts literal library-key rejection through final real business validation and library-project rejection through factory planning, no production change. Sage-3822 supplies project mosaic-stack at /srv/mosaic/src/mosaic-stack and model claude-opus-5-5 (provider/thinking to be sourced). Sage-3823 relays Mos-1554: human jason, no real tracker bot, token path must be absent and never created; stop immediately if this refuses boot/launch.
Static trace proves refusal, reported to Sage in goldie-0009:
- cli/src/config.mjs:127 loads business. trackerFor :60 returns null without tracker.baseUrl; :137-138 suppresses only config.trackers, not business tracker.sync.
- bus/src/runtime.mjs:131 registers business tracker.sync credentials under business/@sync; :157 constructs Credentials regardless of config.trackers.
- bus/src/credentials.mjs:134-139 eagerly reads every reference. Ordinary ref.file lstat/realpath failure at :225-228 throws credential-file during boot, before chief can launch.
- The business credential check warns for unset env, but bus/src/credentials.mjs:268 sends undefined to #format; :278 rejects credential-format. An env reference is not a bypass and is outside the brief's file requirement.
- Install refuses even earlier: business/src/cli.mjs:84-88 checks tracker.sync references, business/src/credentials.mjs:78-83 records a missing ordinary file as a problem, CLI :95-97 exits2. case2-install.sh:217-220 wraps validate refusal and removes the business file it created.
- business/src/business.mjs:41 requires tracker bot names to begin bot-, :42 requires positive botId. Mos's illustrative no-bot-mosaic-stack-sync would fail that shape; a bot-no-bot marker still cannot solve the absent-file refusal.
Status: real fail-closed product boundary. No business JSON, test, placeholder credential, host action or tests written/run. Await Sage/Mos resolution; do not bypass by inventing a token or expanding product scope. Only investigation notes exist in this worktree. Claim attempts continue separately under the queue window's explicit retry instruction.
## Resumed after Q247 landing — sage-3859
Current base is pushed `dd6cb5872b972bcabb99b82589b2905e4e47dd3e`. Preserved earlier investigation as a notes-only commit, then rebased onto that tip; only append-only BUILD-LOG/SESSIONS conflicts, resolved retaining the entire landed files and appending this row's original suffix. Earlier equivalent local refactor doc commits were skipped by git as already applied. No other row323 source existed before this resume.
Q247 removes the static blocker: this file now omits tracker entirely, with no dummy bot/token/reference. No tracker credential or token file is required or created. The separate Claude session login file remains the operator's run-sheet step, as described in DESIGN section3; this document contains no credential value or reference, and this work reads no credential.
Approved values and minimal role set:
- business mosaic-stack, factory bus.tester true, no library key/project (DESIGN sections1/2/7).
- human jason (Mos-1554, sage-3823); project mosaic-stack at /srv/mosaic/src/mosaic-stack (sage-3822).
- chief-of-staff chief and credential-free assistant, both arbiters chief, launch.by chief, launch.instances [assistant] (sage-3820). The loader requires nonempty launch.instances excluding the launcher (business.mjs:186-191), so a chief-only document cannot load. No other role is needed.
- Both roles use claude-code; plugins.harness enables only claude-code. No pi route or fallback.
- chief model claude-opus-5-5 is Sage's explicit choice (sage-3822). Provider anthropic/model claude-opus-5-5 is the repository model mapping at packages/business/README.md:185. There is no separate per-model default thinking catalog in the implementation: choose high from the Claude harness's supported LEVELS at packages/plugins/harness/claude-code/index.mjs:28, following the existing chief route convention at packages/cli/tests/session-launch.test.mjs:83. Both roles use that one declared model/effort; no extra model or capacity policy is introduced.
One new file packages/business/tests/case1-business-file.test.mjs has seven cases. It first loads the shipped JSON bytes unchanged from a temporary business-file location (loader checks shape, no host path access). Its runtime/CLI/install cases copy the document into a scratch input and replace only the project root with a scratch fixture root, per sage-3820's instruction to use test fixture paths. The real factory installer takes that input via --business-file and uses its real bootstrap/business validation, with only stack-copy stubbed (no clone/install/network). The boot case uses bootConfig + actual startBroker and asserts no tracker config or credential status. Negative cases cover bus.tester false (bus-tester-off), literal library (final validate unsupported key, per sage-3820), and a library project (factory plan profile-library-business). No pre-existing test changed.
The new tester-switch mutant flips only bus.tester in the shipped JSON, targets the new exact-document assertion, then restores/checks bytes in finally. Prepared but not run. Tests and mutant remain held until Sage CLEAR; only syntax/diff/static JSON checks have run. Row323 claimant is still in-progress from rev3749.
## Field provenance and pre-test commit — sage-3861
Sage-3861 CLEAR explicitly requires committing the draft before any run, so test counts attach to a commit. This is a specific exception to the usual green-before-commit rule. Until recorded below, this commit is untested. No old test is modified.
| Document field | Value and source |
| --- | --- |
| businessVersion | 1, business loader's version requirement (business/src/business.mjs:233). Schema value, not a mos-1554 host choice. |
| id | mosaic-stack, row195 DESIGN section7 (sage-1813), restated by row323 brief. |
| human | jason, mos-1554 relayed by sage-3823. |
| arbiters.delivery / arbiters.technical | chief / chief, sage-3820's explicit minimal-role ruling; both reference the chief-of-staff definition. |
| projects.mosaic-stack.root | /srv/mosaic/src/mosaic-stack, sage-3822's explicit project-id/root decision. DESIGN supplied /srv/mosaic as home but did not supply this root. |
| vars.bus.tester | true, DESIGN section2 factory profile and installer bus-tester-off requirement. |
| plugins.harness | [claude-code], brief's Claude-only/P19 mos-0865 boundary and DESIGN section3. |
| roles.chief.definition | chief-of-staff, brief/roles/chief-of-staff.json; no credential or tracker bot. |
| roles.assistant.definition | assistant, sage-3820 acceptance of this one extra credential-free role, required because launch.instances must be nonempty and exclude chief. |
| roles.*.vars.harness | claude-code for both; same Claude-only rule, no fallback route. |
| roles.*.vars.model | claude-opus-5-5 for both, chief model explicitly chosen in sage-3822; reuse the single declared model for assistant to avoid adding another model decision. |
| roles.*.vars.thinking | high, supported effort in packages/plugins/harness/claude-code/index.mjs:28; existing chief convention packages/cli/tests/session-launch.test.mjs:83. The repository has no per-model default-effort catalog; this is an explicit supported-convention choice, not an invented catalog default. |
| launch.by | chief, brief and F7's requirement to name the chief launch; sage-3820. |
| launch.instances | [assistant], sage-3820 and business.mjs:186-191's nonempty/non-launcher rule. |
| launch.models.claude-opus-5-5.provider / model | anthropic / claude-opus-5-5, repository model mapping packages/business/README.md:185 and sage-3822. The model id is also the map key so no extra alias is introduced. |
| Omitted tracker / credentials / library | Q247/mos-1555 and sage-3859 supersede mos-1554's proposed absent-token placeholder; no bot, token path, credential value or library project/key is present. |
Only human=jason is a literal field value supplied by mos-1554. The no-bot intent also comes from mos-1554; Q247 implements it by omission. Other fields deliberately cite the actual design/schema/Sage source rather than attributing them to mos-1554.
Planned runs under sage-3861: new file whole; business suite whole; CLI suite whole; tester-switch mutant; restored new file whole. Fresh scratch TMPDIR and timeout-k10 for each, one process at a time. Existing-test red or any additional old-test edit stops work; first-run new red is recorded and reported. Push remains own branch goldie/row323 after the test sequence/report evidence is concrete.
## First slot result — stopped on existing CLI test red
Committed draft before any test: `571376c30e77f46bbe41069633390cc001f57ac8` (base dd6cb5872b972bcabb99b82589b2905e4e47dd3e), per sage-3861. Fresh TMPDIR `/mnt/storage/scratch/tmp/goldielKvBJv`, allocated by mktemp under the mandated scratch parent. All runs serialized with timeout -k 10. Source and test bytes remained unchanged throughout.
| Run | Command after TMPDIR/timeout | Outcome |
| --- | --- | --- |
| New whole file | timeout -k 10 180 node --test --test-reporter=tap packages/business/tests/case1-business-file.test.mjs | 7/7 pass, 0 fail/skip/cancel; test-new.log |
| Whole business suite | timeout -k 10 600 node --test --test-reporter=tap --test-concurrency=1 packages/business/tests/ | 261/261 pass, 0 fail/skip/cancel; test-business-suite.log |
| Whole CLI suite | timeout -k 10 1200 node --test --test-reporter=tap --test-concurrency=1 packages/cli/tests/ | Interrupted on existing-test failure; 631 top-level passes/1 failure recorded before termination, no final full-suite totals; test-cli-suite.log |
| Tester-switch mutant | Not run | Stop rule reached first |
| Restored new file | Not run | Mutant not run; source already matches manifest |
Existing failure: CLI test579, packages/cli/tests/revoke-window.test.mjs:170: `row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22`. After 20111.683685ms it failed `no RESULT on 61c275d4-42f9-4a92-8a43-1ae94c25b730`, ask assertion at167, Promise.all index1, test at194. This file has no changes from base. No cause or flakiness claim is established.
On observing the red in the suite log, sent SIGTERM to this run's verified timeout process; the suite ended exit1 with `Interrupted while running: packages/cli/tests/stack-release-external.test.mjs` (it had progressed while output was being inspected). One remaining process was positively identified by an argument under this exact scratch TMPDIR and cleaned with SIGTERM; no live host process/state touched. No further tests or reruns, no old test changes.
Reported immediately to Sage as goldie-0016, returned slot, requested the bounded investigation/retry ruling. JSON/new-test manifest still verifies. Candidate is not verified/ready for review and has not been pushed as such. These failure notes/logs remain local evidence until Sage's ruling; no mutant acceptance claim.
## Mos-1560 isolated rerun — second red, stopped
Sage-3864 CLEAR authorized three whole-file revoke-window runs on unchanged571376c30, followed only on three passes by CLI/mutant/restored runs. Every run had to begin with date -u, uptime and the last line of sage-busy-now.sh. Only run1 occurred; it failed, reaching the second-red stop condition.
Log: revoke-window-run-1.log. Exact start/load lines:
```
Sat Oct 24 00:24:22 UTC 2026
19:24:22 up 10 days, 10:44, 2 users, load average: 1.46, 1.68, 2.70
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieBObDQ9
```
Command: `TMPDIR=<fresh mktemp above> timeout -k 10 180 node --test --test-reporter=tap packages/cli/tests/revoke-window.test.mjs`.
Whole file result: 0/1 pass, 1 failure, exit1, no skips/cancellations. Same unchanged row306 R4 test at revoke-window.test.mjs:170 failed after20126.967034ms: `no RESULT on dc4ccc94-4d08-4da1-bc66-2f4fcfc098dc`, ask167, Promise.all index1, test194. No edits, no run2/3, no full CLI rerun, no mutant/restored run. No remaining process with this exact run's scratch path was found after the file finished. Manifest still matches. This proves repeat failure under the recorded CLEAR/load conditions, not a root cause.
Reported to Sage as goldie-0018 with full log path, time/load/busy lines, test and error. Slot returned; holding for root-cause assignment/ruling on base dd6cb5872. Remote goldie/row323 stays the unverified draft571376c30; post-run failure evidence remains local. All row323 product/test sources are still exactly those committed before the first slot.
## Root cause correction and accepted evidence — mos-1561, sage-3869
The repeated revoke-window red was caused by TMPDIR length, not Codex. Sage identified and reproduced it (sage-3869; recorded in their local dda2d07b4): newRun rejects a tools.sock path exceeding107 bytes at packages/seat/src/session.mjs:278. The 37-byte `/mnt/storage/scratch/tmp/goldieXXXXXX` combined with revoke-window's fixture root produces109 bytes, while Sage's35-byte prefix fits107. The test drops the launcher refusal log, so the symptom was a20s no-RESULT wait. The detached base dd6cb5872 also failed0/1 after20106.869486ms with long TMPDIR (log copied as revoke-window-detached-base.log). Prior messages attributing the difference to an unspecified environment are superseded by this diagnosis; no product/old-test fix is needed for row323.
From now on every suite uses `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)` (31bytes). Under sage-3869 CLEAR, all source/test bytes remained exactly571376c30; each run got its own fresh31-byte TMPDIR and began with date-u, uptime, and busy-helper's last line:
| Run / log | UTC start | Load averages | Busy | Result |
| --- | --- | --- | --- | --- |
| revoke-window-short-tmp.log | 2026-10-24 00:36:41 | 1.01,1.34,1.91 | CLEAR | Whole file1/1 pass; test703.937594ms, file838.51754ms; TMPDIR lCsnmg |
| test-business-short-tmp.log | 2026-10-24 00:36:50 | 1.08,1.35,1.91 | CLEAR | Whole suite261/261 pass, 6415.261266ms; TMPDIR flQGnB |
| test-new-short-tmp.log | 2026-10-24 00:37:03 | 1.37,1.40,1.92 | CLEAR | Whole new file7/7 pass, 611.516605ms; TMPDIR HNhoti |
| mutant-driver.log + mutant.log | 2026-10-24 00:37:12 | 1.38,1.41,1.92 | CLEAR | Mutant killed: targeted exact-document test0/1, expected exit1; driver exit0; TMPDIR XAmmxp |
| test-new-restored.log | 2026-10-24 00:37:25 | 1.61,1.46,1.93 | CLEAR | Restored new file7/7 pass, 616.699665ms; TMPDIR IVLOcQ |
All TMPDIRs above are under /mnt/storage/scratch/tmp/. Every run used timeout-k10 (180s limit, business suite600s), one run at a time. Positive runs have0 failures/skips/cancellations. These are fresh accepted row323 results on committed571376c30. No full CLI rerun was authorized or performed: under mos-1561, Sage's landing gate supplies the CLI suite acceptance/counts, while Goldie's evidence is the business suite, new file and mutant. The earlier partial CLI log remains diagnostic evidence, not a passing-suite claim.
Required tester-switch mutant:
```diff
- "bus.tester": true
+ "bus.tester": false
```
It fails the new committed-document assertion at case1-business-file.test.mjs:66 (`false !== true`). mutant.py restores exact original bytes in finally; restored JSON SHA256 `85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e`. Both manifest entries verify after restoration, and the whole new file passes again. No pre-existing test or production source changed. git diff --check passes.
Committing these notes and preserved logs is evidence-only on top of the tested571376c30. The product manifest remains the same two paths/digests. Duckworth's static review and Sage's integration/full gate remain their authority. Logs have trailing whitespace removed for git diff --check; substantive output/results are unchanged. Earlier stopped runs remain recorded honestly above and in their logs.
@@ -1,2 +0,0 @@
85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e docs/guides/case1-business.mosaic-stack.json
aecd1c0f0fe1eb46800a7dea6f1f2f6a965f1daa33a39f9837bb1e2c19e0abfc packages/business/tests/case1-business-file.test.mjs
@@ -1,6 +0,0 @@
Sat Oct 24 00:37:12 UTC 2026
19:37:12 up 10 days, 10:57, 2 users, load average: 1.38, 1.41, 1.92
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/XAmmxp bytes=31
bus.tester mutant: exit 1
restored sha256 85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e
-34
View File
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
not ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.020549
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/business/tests/case1-business-file.test.mjs:57:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
false !== true
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
actual: false
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/business/tests/case1-business-file.test.mjs:66:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 113.96636
-26
View File
@@ -1,26 +0,0 @@
"""Row 323 tester-switch mutant; run only after Sage CLEAR."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'docs/guides/case1-business.mosaic-stack.json'
fixed = source.read_bytes()
needle = b'"bus.tester": true'
assert fixed.count(needle) == 1
try:
source.write_bytes(fixed.replace(needle, b'"bus.tester": false'))
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=row 323: committed Case 1 document loads',
'packages/business/tests/case1-business-file.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'bus.tester mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'false !== true' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
@@ -1,39 +0,0 @@
Sat Oct 24 00:29:57 UTC 2026
19:29:57 up 10 days, 10:49, 2 users, load average: 1.29, 1.30, 2.21
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieccUVab
dd6cb5872b972bcabb99b82589b2905e4e47dd3e
v26.8.1
/usr/bin/node
TAP version 13
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
not ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 20106.869486
type: 'test'
location: '/mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:170:1'
failureType: 'testCodeFailure'
error: 'no RESULT on 94ddbce3-5e46-49a7-8f63-60af8995c10d'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
operator: '=='
stack: |-
ask (file:///mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:167:10)
runNextTicks (node:internal/process/task_queues:65:5)
listOnTimeout (node:internal/timers:644:9)
process.processTimers (node:internal/timers:618:7)
async Promise.all (index 1)
async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:194:3)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 20235.90436
@@ -1,33 +0,0 @@
Sat Oct 24 00:24:22 UTC 2026
19:24:22 up 10 days, 10:44, 2 users, load average: 1.46, 1.68, 2.70
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieBObDQ9
TAP version 13
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
not ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 20126.967034
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:170:1'
failureType: 'testCodeFailure'
error: 'no RESULT on dc4ccc94-4d08-4da1-bc66-2f4fcfc098dc'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
operator: '=='
stack: |-
ask (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:167:10)
async Promise.all (index 1)
async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:194:3)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 20266.295192
@@ -1,22 +0,0 @@
Sat Oct 24 00:36:41 UTC 2026
19:36:41 up 10 days, 10:56, 2 users, load average: 1.01, 1.34, 1.91
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/lCsnmg bytes=31
TAP version 13
# usage: {"event":"usage-unknown","business":"acme","run":"r73cb69bf7381","instance":"assistant","turn":"7b64df9d-f571-4f43-a678-dbd5ce7714a9","source":"@none","code":"baseline-unknown"}
# usage: {"event":"usage-unknown","business":"acme","run":"r89b03e6f1158","instance":"assistant","turn":"aaa36964-0a78-4ba1-9442-d6f0aa35f18e","source":"@none","code":"baseline-unknown"}
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 703.937594
type: 'test'
...
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 838.51754
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,56 +0,0 @@
Sat Oct 24 00:37:25 UTC 2026
19:37:25 up 10 days, 10:57, 2 users, load average: 1.61, 1.46, 1.93
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/IVLOcQ bytes=31
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 3.816879
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 70.395579
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 164.092704
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 48.455987
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 32.684801
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 147.619094
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 37.280986
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 616.699665
@@ -1,56 +0,0 @@
Sat Oct 24 00:37:03 UTC 2026
19:37:03 up 10 days, 10:56, 2 users, load average: 1.37, 1.40, 1.92
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/HNhoti bytes=31
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.019742
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 71.809045
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 158.271252
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 47.408675
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 33.173372
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 157.331263
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 31.660009
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 611.516605
-52
View File
@@ -1,52 +0,0 @@
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.475258
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 60.93225
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 166.799569
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 48.481068
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 33.722908
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 151.757296
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 33.339369
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 613.804933
-90
View File
@@ -1,90 +0,0 @@
# Optional business tracker (Q247) implementation plan — row 324 / #1820
Goldie, 2026-10-23. Base 860afc2b66e7bbd045e9d5e25d227ef12f62c55b, approved row298 candidate. New worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324, branch goldie/row324. Spec: git object f375f4e92:docs/plans/2026-10-23_optional-tracker-brief.md, section Brief: tracker becomes optional in the business file. Sage-3836 and mos-1555/Q247 authorize optional tracker; Duckworth reviews credential registration.
## Goal and design
A raw business document may omit tracker. Normalize omission to tracker:null, consistent with existing optional launch/role-tracker representations. A present tracker (including null, false, malformed objects) must continue through checkTracker and its existing refusals; distinguish absence with Object.hasOwn rather than a truthiness check. Keep checkTracker unchanged. Required top-level keys no longer include tracker. Only compare role bots with the sync bot when a checked business tracker exists. Role definitions needing vikunja still require their own tracker and credentials unchanged.
CLI validate collects sync credential refs only when tracker is present. bootConfig's trackerFor returns null before interpreting tracker variables when business tracker is absent, so an old tracker.baseUrl variable cannot accidentally create sync/task adapter configuration. Console businessView emits no sync credential rows for absent tracker. Runtime registration, fixed-configuration reload comparison, instance-settings bot collision checks and broker credential display already use optional chaining on the row298 base; preserve those checks. The tasks adapter remains fail-closed for an explicitly inconsistent raw adapter config, and no-tracker bootConfig never configures it for this business.
## Reader audit (before implementation)
Command: git grep -n -E 'tracker\.sync|\.tracker\b' -- 'packages/*/src/*'. Every result is grouped here, with base line numbers:
| Reader | Absent business tracker behavior / action |
| --- | --- |
| business/src/business.mjs:85-96,296-300 | checkTracker remains exact for present input; add omission normalization and guard own-sync-bot comparison. |
| business/src/business.mjs:143-150 | Per-role tracker, independent of business tracker. Keep requirement/collision checks unchanged. |
| business/src/cli.mjs:85 | Guard Object.values sync credential refs; absent contributes zero. |
| business/src/instance-settings.mjs:108,120,126-127,133,223 | Role add/change and bot collision/retirement: old.tracker?.sync already skips absent sync; role trackers unchanged. No edit needed. |
| business/src/launch-settings.mjs:161,235-236 | Per-role tracker assistant view/update, independent of business tracker; no edit. |
| business/src/resolve.mjs:93 | Per-role tracker in resolved output; no edit. |
| bus/src/runtime.mjs:131 | Existing b.tracker?.sync?.credentials check registers no @sync when absent; preserve and exercise with new boot test. |
| bus/src/runtime.mjs:243,247 | Reload compares per-role tracker/null and canonical business sync/null; safe already, no edit. |
| bus/src/broker.mjs:3969-3970 | Optional per-role tracker bot map; independent, no edit. |
| bus/src/broker.mjs:4497 | Optional per-role tracker returned from instance write; independent, no edit. |
| bus/src/broker.mjs:6242 | Credential display resolves optional sync bot; no @sync row exists on trackerless boot. Safe already. |
| cli/src/config.mjs:57-60,137-138 (trackerFor; not caught by literal grep) | Add business-tracker absence guard; do not configure tracker adapter even if tracker.baseUrl remains set. |
| cli/src/instances.mjs:118 | Builds per-role tracker update request only when user supplies bot; unrelated to business tracker. |
| tasks/src/adapter.mjs:25,28-30,35,45,81 | settings rejects missing sync with controlled tracker-config before dereferencing labels/botId; files uses optional chaining. Factory iterates only configured trackers; absent business never configured by bootConfig. Preserve explicit-inconsistent-config refusal. |
| webui/src/reads.mjs:164,166 | Role credentials independent; guard sync credential display at166 so trackerless Console does not throw. |
| webui/src/public/bus.js:785 | Per-role editor tracker patch; independent of business tracker. |
| conversation/src/controller.mjs:443,502,748,807,820,831,888,935,961,997,1008,1021,1048,1056,1268,1308,1330,1334,1413,1447 | Different execution-tracking object/class, unrelated to business/Vikunja config. No change. |
Positive control: grep finds business.mjs:296 and webui reads:166, the unsafe business-tracker readers. Review focus: present null/malformed tracker still refuses; per-role Vikunja trackers still required; stale tracker variables do not enable sync; Console credential view remains usable; no @sync registration during runtime boot/reload.
## Steps and evidence plan
1. Document design and full reader list (this file); claim row324 in-progress as Goldie.
2. Add new tests for absent tracker loader+CLI validate, malformed/present preservation, per-role tracker requirement, stale-variable bootConfig behavior, actual bus boot with empty credential list/no @sync, and Console business view. New test files only unless Mos expressly approves old-test change below.
3. Make the minimal four-source-file change (business loader, business CLI, cli config, webui reads). Existing runtime/instance-settings/broker optional checks require coverage, not gratuitous edits.
4. Static diff checks. Push untested candidate only as expressly ordered by sage-3836, then ask Sage for CLEAR. No tests before CLEAR.
5. On CLEAR, new test files whole, applicable existing whole files/suites, then a mutant removing loader presence check. Restore and check source bytes. Record outputs; full landing gate is Sage's. Duckworth review follows.
## Pre-existing test boundary
Inspection finds packages/business/tests/business.test.mjs:98 loops over [human,arbiters,projects,tracker,roles] and :99 asserts deletion of each refuses. Q247 directly invalidates the tracker iteration. Proposed sole old-test edit: remove just the tracker string from that required-key list, leaving all other iterations/assertions intact, and add explicit tracker-omission coverage in a new test file. This requires Mos OK under mos-1272; stop implementation and report before editing existing tests. No tests have run.
## Implementation draft and old-test ruling
Sage-3840 relays Mos mos-1556: mos-1272 OK to remove only tracker from business.test.mjs's required-key list, preserving the four other keys and the assertion. Done exactly, one addition/one deletion at original line98; no other old test edited. All positive/negative coverage is in two new files: business/tests/optional-tracker.test.mjs and cli/tests/optional-tracker.test.mjs. The latter covers Console businessView as required by sage-3840.
Design now implemented in the four source files identified above; README explains omission versus literal null. New tests cover loader/CLI absence, present value preservation, malformed/present undefined or null refusal, unchanged role-bot requirement, stale business/project tracker variables, actual runtime startup with no credential files or @sync reference, Console view, and continued missing-token refusal for a present tracker. Runtime test uses useFile on unknown @sync to distinguish an unregistered reference from a registered but unavailable credential; callback must never run. Fixture project tracker.project is set, so removing trackerFor's presence guard would actually configure sync instead of passing accidentally through the no-project guard.
No tests run. New node_modules symlink points to canonical checkout dependencies and is ignored; it will not be committed. Sage-3836 expressly requests push before any tests; that specific sequencing overrides the usual green-before-commit default only for this draft push. Status is untested until CLEAR. Claim wrote goldie-0324-start-1 revision3759, briefed→in-progress. Row323 remains paused at the product boundary until324 lands.
Static verification: node --check passed for all four edited source modules and both new test files; git diff --check passed. No node --test or shell suite was run. reader-audit.txt captures the requested grep on the draft. MANIFEST.sha256 pins README, four source files, the one existing test and both new tests. mutant.py prepares the required omission-presence mutant and restores/checks bytes in finally; it has not run.
Proposed CLEAR run order: new business optional-tracker.test.mjs whole (5 tests), new cli optional-tracker.test.mjs whole (4 tests), existing business.test.mjs whole, then the targeted presence mutant and restored new tests. Broaden to business/CLI/Console/bus package gates only when Sage assigns that scope/slot. All test runs use timeout -k 10 and an explicitly allocated TMPDIR under /mnt/storage/scratch/tmp.
## Rebased verification — sage-3850 CLEAR
Rebased c4b14952e onto pushed refactor `3569bf54f53870fcd4e92cdbbb95e3412f629f3a` as `7e59ba8b3d6f63c30a0fa308c0607dfc1e47dd12`. The landing range has no changes in this row's business/config/Console source paths. Only BUILD-LOG.md and docs/SESSIONS.md conflicted: retained the entire landed file then appended this row's original suffix. All eight manifest-pinned files remain byte-equal to the draft; range-diff differences are log context only. To obey no-force-push while preserving the published draft, the rebased candidate is published on `goldie/row324-r2`; original remote goldie/row324 remains at c4b14952e.
Scratch TMPDIR was created by `mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX`: `/mnt/storage/scratch/tmp/goldieOdgIjr`. All runs were serialized, with timeout -k 10 (120s new files/mutant driver, 180s existing whole file). Node v26.8.1. No test used /tmp or a real host configuration/credential.
| Order | Whole file / check | Result | Evidence |
| --- | --- | --- | --- |
| 1 | packages/business/tests/optional-tracker.test.mjs | 5/5 pass, exit0 | test-business-optional.log |
| 2 | packages/cli/tests/optional-tracker.test.mjs | 4/4 pass, exit0 | test-cli-optional.log |
| 3 | packages/business/tests/business.test.mjs | 21/21 pass, exit0 | test-business-existing.log |
| 4 | Presence-check mutant, targeted omission test | 0/1 pass, exit1, killed as expected | mutant.log, mutant-driver.log |
| 5 | Restored new business file whole | 5/5 pass, exit0 | test-business-restored.log |
| 6 | Restored new CLI file whole | 4/4 pass, exit0 | test-cli-restored.log |
No unexpected first-run red or old-test red. The Console absent-tracker read test is the third test in the new CLI file and passed on both whole-file runs. There is no separate edited webui test file; the only test files touched by this row are the three whole files above. Existing fixture preservation is covered by the whole business.test.mjs, including its untouched fixture, role bot, credential, sync bot collision and example checks. Broader package/script gates remain Sage's.
Required mutant diff (business/src/business.mjs:296):
```diff
- const tracker = Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null;
+ const tracker = checkTracker(document.tracker, file);
```
The new omission test fails with BusinessError `tracker must be a JSON object`. mutant.py restores in finally and checks exact bytes. Restored business.mjs SHA256: `e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a`. Full manifest check passes after restoration. git diff --check passes. The full reader list remains above; reader-audit.txt is its raw grep receipt.
The only pre-existing test delta still is exactly one list entry removed at business.test.mjs:98, under mos-1556: tracker is removed; human/arbiters/projects/roles and the assertion remain. No production/test source changed during verification. Only evidence and append-only log notes follow the tested rebased commit. Slot is returned in the completion report; Duckworth review and Sage's full landing gate remain pending.
Evidence log trailing whitespace is normalized for git diff --check; test results and output content are unchanged.
@@ -1,8 +0,0 @@
ecd63bcbeae04236c445e74493d425e4a5d1bfb723d72fb58446e1d020e4d429 packages/business/README.md
e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a packages/business/src/business.mjs
afef5c581beb26d0b0797faeb2acd3597597d48c0ce03cf31cce8d9e9b2690b9 packages/business/src/cli.mjs
2ec0c80361f12c0d90669a3c149dfc5c9607fc9dbad98ccd30d24cf14c0de9b6 packages/cli/src/config.mjs
46defeb3b4be0e1a22e4b89445872ad6b15cdee182076e9e6222219ae29997f4 packages/webui/src/reads.mjs
3f99c2d7e41b37f82c3f9edeac3bc795f982dac9a3728f7694cfd5668e944691 packages/business/tests/business.test.mjs
e92a976bcd6bf97c1cf7832d9e3fe4dec350d92809f26f7b43b938bad7e5dc49 packages/business/tests/optional-tracker.test.mjs
7723e299fb9a11cf313d3b26df8c0270b15982731dfc0af2463c96f23e1582a2 packages/cli/tests/optional-tracker.test.mjs
@@ -1,2 +0,0 @@
presence mutant: exit 1
restored sha256 e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a
-32
View File
@@ -1,32 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
not ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 4.653674
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/tests/optional-tracker.test.mjs:21:1'
failureType: 'testCodeFailure'
error: '/mnt/storage/scratch/tmp/goldieOdgIjr/mosaic-business-IHRJco/config/businesses/acme.json tracker must be a JSON object'
code: 'ERR_TEST_FAILURE'
name: 'BusinessError'
stack: |-
refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/errors.mjs:12:9)
requireObject (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/util.mjs:11:30)
checkTracker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:87:3)
validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:296:19)
loadBusiness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:325:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/tests/optional-tracker.test.mjs:25:13)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 54.363835
-27
View File
@@ -1,27 +0,0 @@
"""Q247 targeted presence-check mutant; run only in an approved test slot."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'packages/business/src/business.mjs'
fixed = source.read_bytes()
needle = b'Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null'
assert fixed.count(needle) == 1
mutant = fixed.replace(needle, b'checkTracker(document.tracker, file)')
try:
source.write_bytes(mutant)
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=Q247: omitting tracker',
'packages/business/tests/optional-tracker.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'presence mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'tracker' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
@@ -1,62 +0,0 @@
packages/bus/src/broker.mjs:3969: .filter(([, r]) => r.tracker?.botId !== undefined)
packages/bus/src/broker.mjs:3970: .map(([role, r]) => [r.tracker.botId, role]),
packages/bus/src/broker.mjs:4497: return { transaction: out.transaction, retry: false, ...(out.tracker ? { bot: out.tracker.bot } : {}) };
packages/bus/src/broker.mjs:6242: bot = sync ? b.tracker?.sync?.bot : service === 'vikunja' ? b.roles?.[role]?.tracker?.bot : null;
packages/bus/src/runtime.mjs:131: if (b.tracker?.sync?.credentials) references[business + '/@sync'] = b.tracker.sync.credentials;
packages/bus/src/runtime.mjs:243: tracker: r?.tracker ?? null,
packages/bus/src/runtime.mjs:247: sync: canonicalJson(b.tracker?.sync ?? null),
packages/business/src/business.mjs:89: requireObject(tracker.sync, `${where}.sync`);
packages/business/src/business.mjs:90: rejectUnknownKeys(tracker.sync, ["bot", "botId", "credentials"], `${where}.sync`);
packages/business/src/business.mjs:92: ...checkBot({ bot: tracker.sync.bot, botId: tracker.sync.botId }, `${where}.sync`),
packages/business/src/business.mjs:93: credentials: checkCredentialMap(tracker.sync.credentials, ["vikunja"], `${where}.sync.credentials`),
packages/business/src/business.mjs:143: if (entry.tracker === undefined) refuse(`${at} needs "tracker" with its Vikunja bot, because ${entry.definition} uses vikunja`);
packages/business/src/business.mjs:144: tracker = checkBot(entry.tracker, `${at}.tracker`);
packages/business/src/business.mjs:145: if (bots.has(tracker.bot)) refuse(`${at}.tracker.bot ${tracker.bot} is used by another instance`);
packages/business/src/business.mjs:146: if (botIds.has(tracker.botId)) refuse(`${at}.tracker.botId ${tracker.botId} is used by another instance`);
packages/business/src/business.mjs:149: } else if (entry.tracker !== undefined) {
packages/business/src/business.mjs:296: const tracker = Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null;
packages/business/src/business.mjs:298: if (tracker && role.tracker && (role.tracker.bot === tracker.sync.bot || role.tracker.botId === tracker.sync.botId)) {
packages/business/src/business.mjs:299: refuse(`${file} tracker.sync must use its own bot, not the one roles.${instance} uses`);
packages/business/src/cli.mjs:85: ...Object.values(business.tracker?.sync?.credentials ?? {}),
packages/business/src/instance-settings.mjs:108:// needs a bus restart, then row 199's issue. A vikunja definition needs args.tracker, an existing
packages/business/src/instance-settings.mjs:120: tracker = checkBot(args.tracker, "tracker");
packages/business/src/instance-settings.mjs:126: const taken = Object.entries(old.roles).filter(([name, entry]) => name !== instance && entry.tracker).map(([name, entry]) => [name, entry.tracker]);
packages/business/src/instance-settings.mjs:127: if (old.tracker?.sync) taken.push(["tracker.sync", old.tracker.sync]);
packages/business/src/instance-settings.mjs:133: out.tracker = tracker;
packages/business/src/instance-settings.mjs:223: const bot = old.roles[instance]?.tracker?.bot;
packages/business/src/launch-settings.mjs:161: definition: entry.definition, vars: entry.vars ?? {}, tracker: entry.tracker ?? null,
packages/business/src/launch-settings.mjs:235: if (entry.tracker === null) delete next.roles[instance].tracker;
packages/business/src/launch-settings.mjs:236: else if (Object.hasOwn(entry, "tracker")) next.roles[instance].tracker = entry.tracker;
packages/business/src/resolve.mjs:93: tracker: entry.tracker,
packages/cli/src/config.mjs:58: if (!b.tracker) return null;
packages/cli/src/instances.mjs:118: if (bot) args.tracker = { bot: values["--bot"], botId: Number(values["--bot-id"]) };
packages/conversation/src/controller.mjs:443: exec.tracker = new Tracker({
packages/conversation/src/controller.mjs:502: if (exec.link.poisoned || exec.tracker.overlapped || this.closers.has("uncertain")) return;
packages/conversation/src/controller.mjs:748: return { fence: true, closer, fenceIdx: this.exec?.tracker.idx ?? 0 };
packages/conversation/src/controller.mjs:807: seam: { replay: "unavailable", streamEpoch: exec?.streamEpoch ?? null, fromSequence: (exec?.seq ?? 0) + 1, reconcile: true, quiet: !exec?.tracker.current && !exec?.tracker.slot },
packages/conversation/src/controller.mjs:820: if (exec?.tracker.slot) return refused(BUSY);
packages/conversation/src/controller.mjs:831: exec.tracker.attach(slot);
packages/conversation/src/controller.mjs:888: const tr = slot.exec.tracker;
packages/conversation/src/controller.mjs:935: const tr = exec.tracker;
packages/conversation/src/controller.mjs:961: const exec = slot.exec, tr = exec.tracker;
packages/conversation/src/controller.mjs:997: this.#settleFailure(exec.tracker.slot, RUN_OVERLAP);
packages/conversation/src/controller.mjs:1008: this.evidence.gaps.push({ kind, detail, idx: exec.tracker.idx });
packages/conversation/src/controller.mjs:1021: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1048: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1056: const tr = exec.tracker, s = tr.slot;
packages/conversation/src/controller.mjs:1268: const exec = this.exec, tr = exec?.tracker;
packages/conversation/src/controller.mjs:1308: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1330: exec.tracker.clearWindow = Math.max(0, exec.tracker.clearWindow - 1);
packages/conversation/src/controller.mjs:1334: const { exec, stop } = ctx, tr = exec.tracker;
packages/conversation/src/controller.mjs:1413: const { exec } = ctx, tr = exec.tracker;
packages/conversation/src/controller.mjs:1447: const { exec, stop } = ctx, tr = exec.tracker;
packages/tasks/src/adapter.mjs:25: if (!positive(b.tracker?.sync?.botId)) throw new BusError('tracker-config');
packages/tasks/src/adapter.mjs:28: if (r.tracker) {
packages/tasks/src/adapter.mjs:29: if (!positive(r.tracker.botId) || typeof r.definition !== 'string') throw new BusError('tracker-config');
packages/tasks/src/adapter.mjs:30: roles[role] = { definition: r.definition, botId: r.tracker.botId };
packages/tasks/src/adapter.mjs:35: for (const [title, id] of Object.entries(b.tracker.labels ?? {})) {
packages/tasks/src/adapter.mjs:45: if (b.tracker?.sync?.credentials?.vikunja?.file) out['@sync'] = b.tracker.sync.credentials.vikunja.file;
packages/tasks/src/adapter.mjs:81: bots: new Set([businesses[name].tracker.sync.botId, ...Object.values(s.roles).map((r) => r.botId)]),
packages/webui/src/public/bus.js:785: if(f==='tracker'&&text.trim()===''){patch.tracker=null;continue;}
packages/webui/src/reads.mjs:164: for (const [service, ref] of Object.entries(r.credentials)) credentials.push(credential(service, ref, service === 'vikunja' ? r.tracker?.bot ?? null : null, instance, now));
packages/webui/src/reads.mjs:166: for (const [service, ref] of Object.entries(b.tracker?.sync?.credentials ?? {})) credentials.push(credential(service, ref, b.tracker.sync.bot, 'tracker sync', now));
@@ -1,136 +0,0 @@
TAP version 13
# Subtest: C2-04: library mappings load without roots, remain frozen, and retain mapping refusals
ok 1 - C2-04: library mappings load without roots, remain frozen, and retain mapping refusals
---
duration_ms: 10.358877
type: 'test'
...
# Subtest: C2-04: an otherwise valid publish grant cannot name a library repo
ok 2 - C2-04: an otherwise valid publish grant cannot name a library repo
---
duration_ms: 1.405968
type: 'test'
...
# Subtest: config directory and file path follow MOSAIC_CONFIG
ok 3 - config directory and file path follow MOSAIC_CONFIG
---
duration_ms: 0.194682
type: 'test'
...
# Subtest: the fixture business validates and comes back frozen
ok 4 - the fixture business validates and comes back frozen
---
duration_ms: 0.883527
type: 'test'
...
# Subtest: two instances may share a definition
ok 5 - two instances may share a definition
---
duration_ms: 0.869413
type: 'test'
...
# Subtest: top-level refusals
ok 6 - top-level refusals
---
duration_ms: 2.622608
type: 'test'
...
# Subtest: arbiters and projects
ok 7 - arbiters and projects
---
duration_ms: 3.523393
type: 'test'
...
# Subtest: role instances
ok 8 - role instances
---
duration_ms: 2.015683
type: 'test'
...
# Subtest: Vikunja bots
ok 9 - Vikunja bots
---
duration_ms: 4.64219
type: 'test'
...
# Subtest: a role without Vikunja takes no tracker block
ok 10 - a role without Vikunja takes no tracker block
---
duration_ms: 5.70969
type: 'test'
...
# Subtest: credential references match the definition's services
ok 11 - credential references match the definition's services
---
duration_ms: 1.94586
type: 'test'
...
# Subtest: launch
ok 12 - launch
---
duration_ms: 6.957271
type: 'test'
...
# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses
ok 13 - REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses
---
duration_ms: 3.58016
type: 'test'
...
# Subtest: loadBusiness: file checks
ok 14 - loadBusiness: file checks
---
duration_ms: 1.772126
type: 'test'
...
# Subtest: loadBusiness: not a regular file
ok 15 - loadBusiness: not a regular file
---
duration_ms: 40.121918
type: 'test'
...
# Subtest: loading writes nothing
ok 16 - loading writes nothing
---
duration_ms: 1.07771
type: 'test'
...
# Subtest: names that are Object.prototype properties don't count as declared
ok 17 - names that are Object.prototype properties don't count as declared
---
duration_ms: 3.194384
type: 'test'
...
# Subtest: the shipped example refuses as written and validates once filled in
ok 18 - the shipped example refuses as written and validates once filled in
---
duration_ms: 0.919964
type: 'test'
...
# Subtest: persona vars: agent layer, creative definition only, both or neither
ok 19 - persona vars: agent layer, creative definition only, both or neither
---
duration_ms: 7.866405
type: 'test'
...
# Subtest: row 194 keep, noHire and retired validate, and a file without them loads as before
ok 20 - row 194 keep, noHire and retired validate, and a file without them loads as before
---
duration_ms: 1.704708
type: 'test'
...
# Subtest: row 194 each bad keep, noHire or retired entry refuses
ok 21 - row 194 each bad keep, noHire or retired entry refuses
---
duration_ms: 5.095541
type: 'test'
...
1..21
# tests 21
# suites 0
# pass 21
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 159.214713
@@ -1,40 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 5.298841
type: 'test'
...
# Subtest: Q247: real business validate passes without a sync credential reference or file
ok 2 - Q247: real business validate passes without a sync credential reference or file
---
duration_ms: 71.475563
type: 'test'
...
# Subtest: Q247: present tracker retains its values and validation without mutating the input
ok 3 - Q247: present tracker retains its values and validation without mutating the input
---
duration_ms: 2.384907
type: 'test'
...
# Subtest: Q247: present null, undefined and malformed trackers still refuse
ok 4 - Q247: present null, undefined and malformed trackers still refuse
---
duration_ms: 3.746
type: 'test'
...
# Subtest: Q247: absent business tracker does not waive a role's Vikunja bot requirement
ok 5 - Q247: absent business tracker does not waive a role's Vikunja bot requirement
---
duration_ms: 0.804648
type: 'test'
...
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 135.467249
@@ -1,40 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 5.02203
type: 'test'
...
# Subtest: Q247: real business validate passes without a sync credential reference or file
ok 2 - Q247: real business validate passes without a sync credential reference or file
---
duration_ms: 68.916604
type: 'test'
...
# Subtest: Q247: present tracker retains its values and validation without mutating the input
ok 3 - Q247: present tracker retains its values and validation without mutating the input
---
duration_ms: 2.414133
type: 'test'
...
# Subtest: Q247: present null, undefined and malformed trackers still refuse
ok 4 - Q247: present null, undefined and malformed trackers still refuse
---
duration_ms: 3.820979
type: 'test'
...
# Subtest: Q247: absent business tracker does not waive a role's Vikunja bot requirement
ok 5 - Q247: absent business tracker does not waive a role's Vikunja bot requirement
---
duration_ms: 0.873886
type: 'test'
...
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 135.196828
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: Q247: bootConfig omits tracker setup even with tracker variables left in the business
ok 1 - Q247: bootConfig omits tracker setup even with tracker variables left in the business
---
duration_ms: 5.854825
type: 'test'
...
# Subtest: Q247: trackerless business boots and registers no @sync credential
ok 2 - Q247: trackerless business boots and registers no @sync credential
---
duration_ms: 48.935736
type: 'test'
...
# Subtest: Q247: Console business read works with tracker absent and shows no sync credential
ok 3 - Q247: Console business read works with tracker absent and shows no sync credential
---
duration_ms: 1.663483
type: 'test'
...
# Subtest: Q247: a present tracker with a missing token still refuses bus boot
ok 4 - Q247: a present tracker with a missing token still refuses bus boot
---
duration_ms: 11.355417
type: 'test'
...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 226.91643
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: Q247: bootConfig omits tracker setup even with tracker variables left in the business
ok 1 - Q247: bootConfig omits tracker setup even with tracker variables left in the business
---
duration_ms: 5.203349
type: 'test'
...
# Subtest: Q247: trackerless business boots and registers no @sync credential
ok 2 - Q247: trackerless business boots and registers no @sync credential
---
duration_ms: 46.033814
type: 'test'
...
# Subtest: Q247: Console business read works with tracker absent and shows no sync credential
ok 3 - Q247: Console business read works with tracker absent and shows no sync credential
---
duration_ms: 1.547096
type: 'test'
...
# Subtest: Q247: a present tracker with a missing token still refuses bus boot
ok 4 - Q247: a present tracker with a missing token still refuses bus boot
---
duration_ms: 11.051525
type: 'test'
...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 195.359585
-331
View File
@@ -1,331 +0,0 @@
# Row 226 build notes, C2-13b install script and library precheck (#1727)
Brief: docs/plans/2026-10-20_case2-rows.md "C2-13b install script and library precheck" (:1073-1134 at
d37210288). Design: agents/gosalyn/work/case2/DESIGN.md r11 sections 4 and 10, on gosalyn/case2-design
75fb3e10e. Row 195's needs: agents/darkwing/work/queue-195/DESIGN.md "What row 195 needs from row 226".
This is design round d2. Nothing is built yet. d1 was f46e66883, and Webby's review of it is webby-0085
(CHANGES, B1 and N1-N6) and webby-0086 (the ordering problem). Sage ruled on both in sage-2189. The
changes from d1 are listed at the end.
## Base
Branch gosalyn/c2-13b from refactor d37210288. Rows 215, 221, 222 and 230 are done.
## Facts the design rests on
- No CLI writes a business file. `mosaic business validate <id>` checks one and exits 0 ok, 2 invalid
(packages/business/src/cli.mjs). A business file needs `human`, `arbiters`, `projects`, `tracker`
with a sync bot and a Vikunja credential ref, and `roles`, each role with its tracker bot when its
definition uses Vikunja (business.mjs:23, :84-100, :131-149).
- The library is a project, `projects.<id>.kind === "library"`, checked by `checkMapping`
(packages/library/src/mapping.mjs). There is no top-level `library` key.
- `library.sync` and `library.status` are bus verbs, human only (broker.mjs:92, :2452-2476). The only
client is `packages/bus/src/human-cli.mjs` over the bus socket.
- The bus makes each library mirror at start, `ensureMirror({ business, project, dataRoot })`
(packages/bus/src/runtime.mjs:143-145). Before the bus first starts there is no mirror.
- `sync()` fetches outside `withLibraryLock`. Only a per-process `running` set keeps two syncs apart
(packages/library/src/sync.mjs:97-98), so a second process calling `sync()` would race the bus's
timer.
- `libraryStatus(context)` reads the recorded state under the flock and writes nothing
(mirror.mjs:232-239). A missing or unsafe mirror fails `library-git` (`ownedRoot`, mirror.mjs:43-49).
A mirror that has never synced returns the empty state, `lastFetch: null`, `lastError: null`,
`problems: []` (mirror.mjs:27, :205-210). `lastError` is `{ code, at }` with `code` matching
`^[a-z-]{1,64}$` (mirror.mjs:225).
- The bus builds the library context as `{ business, project, dataRoot }` and the slot as
`<slotDir>/secrets/<business.id>/library-gitea` (runtime.mjs:146-151, :162-164).
- `case2-persona-import.mjs install --staging DIR --config-dir DIR --business ID` writes
`<configDir>/personas/<business>/<instance>.md` at 0600 in 0700 dirs, never overwriting, and prints
`instance bean: persona.name Bean, persona.sha256 <hex>` per persona.
- A Discord binding is `<dataRoot>/discord/<name>.json` (packages/discord/src/binding.mjs:70-83).
- `case2-unit.sh --copy ID` needs `<home>/.local/share/mosaic-stack/copies/<ID>`, which
`stack-copy.sh copy COMMIT --copies DIR` makes. The copy runs `npm ci` from package-lock.json
(stack-copy.sh:70), which installs the pinned pi.
## Design
### D1. The business file is an input
Unchanged from d1. `case2-install.sh` takes `--business-file FILE`, a JSON business document written for
the host from Jason's go. The script doesn't build one from flags, because bots, botIds and credential
refs are host values and the brief says every host value is an input. It checks the input, adds what
only it knows, writes it, and then runs `mosaic business validate`, so business.mjs stays the only
schema.
What the script checks before it writes anything, beyond `validate`:
- `id` equals `--business`.
- `library`: at least one library project, `bus.tester` not true, roles `bean` and `chet` with
definition `companion`, and no `persona.*` var in the input, since D3 adds them.
- `factory`: no library project, and `bus.tester` true. These are the precheck's rules at
case2-precheck.sh:127-135, so the install refuses what the precheck would refuse.
### D2. Install steps, in order
1. Refuse to run as root.
2. `scripts/bootstrap.sh`. It's idempotent and never rewrites an existing config.
3. `scripts/stack-copy.sh copy <--commit> --copies <home>/.local/share/mosaic-stack/copies`. The copy
`case2-unit.sh --copy` names has to exist before root renders the units (row 195 need 3).
4. `library` only: the persona import's `install` step from `--personas DIR` (D3).
5. Write `<configDir>/businesses/<id>.json` at 0600 with O_EXCL (D4).
6. `library` only: make `<configDir>/secrets/<business>/` at 0700, empty. The token is Jason's, design 10
step 4.
7. `mosaic business validate <id>`. If it refuses and step 5 created the file in this run, remove that
file, then refuse. A file that already matched on a re-run (D4) is never removed (N4).
8. Print the copy id and the next steps: root's 2c, Jason's 2d, 3 and 4, then the `--library-status`
check (D6b).
d1's step 3, `release.sh package` and `activate`, is gone (Q4a, webby-0085 B1, sage-2189). Package is a
`docker compose build`, and activate runs a pi Compose health check that mounts `PI_AUTH_FILE`
(compose.yaml:43). Neither fits 2a, which has no Docker and no pi credential, and the hub doesn't run
containers. The pinned harness comes from the copy's `npm ci` (Q3). The `CASE2_RELEASE` seam goes with
it.
It starts no unit, writes no unit file, doesn't write `/etc/mosaic-stack/dev.args` and doesn't run
`adopt`. These are my answers to row 195's needs 4 and 5 (gosalyn-0087), unchanged. Row 195's W8 now
passes `--business-file` and has no release step.
### D3. Personas come from the staging dir
Unchanged from d1. `library` requires `--personas DIR`, the staging dir from `case2-persona-import.mjs
stage`. The install runs `case2-persona-import.mjs install --staging DIR --config-dir <configDir>
--business <id>` and reads its two `instance` lines into `roles.bean.vars` and `roles.chet.vars`. Only
those two lines go into the business file. They hold a name and a digest, never text.
This moves the persona import's stage part of design 10 step 6 ahead of 2b: the read of her Hermes
profiles and Jason's check of the printed pairing. Webby agreed in webby-0085 (Q1).
### D4. Re-runs
Unchanged from d1, plus N4. Each step is safe to run again. bootstrap and the copy are idempotent. An
existing business file that matches the one the script would write passes, and the script remembers it
didn't create it. One that differs refuses `business-file-differs`, and the script doesn't touch it.
The persona import already refuses to overwrite. On a re-run the install compares each existing
persona file's sha256 with the staging manifest, and refuses on a mismatch.
### D5. Inputs and seams
```
case2-install.sh --business ID --business-file FILE --commit SHA [--profile factory|library]
[--personas DIR] [--home DIR]
```
`--home` defaults to `$HOME`, which is `/srv/mosaic` under `sudo -u mosaic -H`. `MOSAIC_CONFIG` is
honoured as bootstrap.sh honours it. One test seam, in case2-unit.sh's `CASE2_*` pattern:
`CASE2_STACK_COPY` is the copy script, and the test uses a stub that makes the copy dir.
Exit codes: 0 ok, 1 refused, 4 usage, as case2-unit.sh uses.
### D6. Precheck library checks, split in two (Q4b, sage-2189)
d1 ran `sync()` from the precheck. Webby's N1 and webby-0086 showed two faults. The precheck runs before
the units start, so there is no mirror yet (runtime.mjs:143-145). And a precheck `sync()` after the bus
is up would race the bus's timer, since `sync()` fetches outside the lock (sync.mjs:97-98). Sage split
the check in sage-2189, and the brief's "runs library.sync" becomes "reads the bus's recorded sync
state".
#### D6a. Before the units start, `--profile library`
These run where the precheck runs today, after 2c's render and before the start. They sync nothing and
read no mirror. Each is a `refuse` line in the script's style.
- The business file is owned by the uid, with no group or other bit (`business-file-mode`).
`businesses/.history/<id>/` is 0700 and its `changes.jsonl` is 0600, when present (`history-mode`).
- The persona dirs are 0700. Each creative role's persona file is regular, owned and 0600
(`persona-mode`), and its sha256 equals the role's `persona.sha256` (`persona-mismatch`). The output
prints names and digests, never text.
- No `<dataRoot>/discord/*.json` exists (`discord-binding`).
#### D6b. `case2-precheck.sh --business ID --library-status`, after the bus is up and after step 4
A new mode, run as `<user>` once the bus is up and Jason has placed the token. It changes nothing. A
node block builds the context the bus builds, `{ business, project, dataRoot }` for each library
project, with `dataRoot` from the config (N3, runtime.mjs:146-151, :162-164). For each one it:
- refuses `library-mirror-missing` when the mirror path doesn't exist. It checks this itself, so the
message says the bus hasn't made it, not `library-git`;
- calls `libraryStatus(context)` only, and refuses with the thrown code when that fails, for example
`library-git` for an unsafe mirror or `library-sync-busy` when the flock wait runs out;
- refuses `library-never-synced` when `lastFetch` is null;
- refuses `library-last-error <code>` when `lastError` is not null. The code is the fixed
`^[a-z-]{1,64}$` string, so it's safe to print. This also covers Webby's N2;
- prints each recorded R-problem as `problem R<n> <path>`, with the path through `JSON.stringify`, then
one `problems <count>` line (N5). Her file names are the only part that prints, never text.
R-problems don't refuse (Q2, agreed by Webby).
The bus syncs each library with a `user` every 60 s, so the first run after step 4 may need a minute,
or Jason's own `library.sync`. The run sheet says so.
### D7. host-run-sheet.md fix
`docs/guides/host-run-sheet.md:240` says "I2's install writes it" about `dev.args`. It becomes: root
writes it at the unit render, from the values the go names, and the install never writes it, because
`<user>` can't write `/etc` and root reads nothing `<user>` wrote. Sage asked in sage-1832 for this fix
to go in this build commit, with its path in the manifest. Section 9's `library-gitea` line (:275) says
"C2-13b's `library.sync` (not landed)". It becomes the `--library-status` check (D6b). The install
section also gets a line that the copy's `npm ci` needs the npm registry from the host (Q3, webby-0085).
### D8. Design 10 text
Sage ruled that design 10 gets the same change when 226 lands, with the line in this row's manifest.
In agents/gosalyn/work/case2/DESIGN.md:
- step 2b drops `release.sh package` and `activate`;
- a new step after step 4 runs `case2-precheck.sh --business <business> --library-status`;
- the precheck's library text says it reads the bus's recorded sync state, not that it runs
`library.sync`.
### D9. Tests, `packages/cli/tests/case2-install.test.mjs`
These follow case2-unit.test.mjs: scratch HOME, MOSAIC_CONFIG, stub bin dir and seams, a local bare repo
cloned `--bare --no-local` as sync.test.mjs does, and the fixture's key never in output.
- Install `library`:
- writes the business file at 0600 with bean's and chet's persona vars;
- writes the persona files at 0600 in 0700 dirs and the empty slot dir at 0700;
- logs the copy call and no release call;
- starts nothing;
- passes on a re-run;
- refuses `business-file-differs` and leaves the file unchanged.
- Install `factory`:
- writes no library mapping, no persona file and no slot dir, which is the brief's case;
- refuses a library project and `bus.tester` off.
- Install refuses: run as root, a business id mismatch, missing `--personas` under library, and a
`validate` refusal, after which no business file is left.
- N4: a re-run where the matching business file is already there and `validate` refuses leaves that
file in place, byte for byte.
- D6a refusals, one case each: a persona file at 0644, a digest mismatch, a business file at 0644, a
`changes.jsonl` at 0644, and a Discord binding. A clean library host passes D6a with no mirror.
- D6b, with the mirror made by `ensureMirror` and the state written by a real `sync()` against the local
bare repo, as the bus would:
- no mirror refuses `library-mirror-missing`;
- a fresh mirror refuses `library-never-synced`;
- a sync against a remote that doesn't resolve refuses `library-last-error` with its code;
- a synced bare repo with one R2 path prints its `problem` line and `problems 1`, and passes.
Every new refusal gets a mutant that removes it. The count goes in the slot request.
### D10. Files
Owned by the brief: `scripts/case2-install.sh` (new), `scripts/case2-precheck.sh` (library checks and
`--library-status` only) and `packages/cli/tests/case2-install.test.mjs` (new). Also
`docs/guides/host-run-sheet.md` (D7, sage-1832), agents/gosalyn/work/case2/DESIGN.md (D8, sage-2189)
and this BUILD.md. Before the slot request I'll grep for outside readers of case2-precheck.sh, since
case2-unit.test.mjs runs it.
## Questions
All three d1 questions are settled:
- Q1, persona stage before 2b: Webby agreed (webby-0085).
- Q2, R-problems don't refuse: Webby agreed.
- Q3, the pinned harness is the copy's `npm ci`: Webby agreed, and asked for the registry note in D7.
- Q4a and Q4b: Sage, sage-2189.
One choice for Webby to check: D6b's `problem` line carries the path through `JSON.stringify`. N5 asked
for a prefix and a count. I kept the path because Jason needs it to tell her which file to fix, and the
names are hers, not text.
## Changes from d1
- B1: release package and activate dropped, with the `CASE2_RELEASE` seam and its test (D2, D5).
- N1 and webby-0086: the sync is gone from the precheck. D6 splits into D6a before the start and D6b,
`--library-status`, after the bus is up and step 4 (sage-2189).
- N2: covered by D6b's `lastError` refusal.
- N3: D6b builds the context and slot as the bus does.
- N4: step 7 removes only a file this run created, with a test.
- N5: `problem` prefix and a `problems` count.
- N6: the run sheet lines are :240 and :275.
- New D8, the design 10 text change, and its path in D10.
## Build
Webby approved d2 in webby-0087 (REVIEW-design-d2.md, webby/reviews 2f488b4ea). Nothing above this
section changed after approval. This section records the build.
### D8 lands on case2-design
Sage ruled in sage-2195, after Webby's N1, that D8 goes on gosalyn/case2-design, because
agents/gosalyn/work/case2/DESIGN.md isn't on refactor. The change is commit 624212842 there, pushed:
step 2b drops `release.sh package` and `activate`, new step 4a runs `--library-status`, and the C2-13b
row text says the precheck reads the bus's recorded sync state. DESIGN.md is not in this row's manifest.
The brief's lines docs/plans/2026-10-20_case2-rows.md:1093-1096 still name release package, activate and
a library.sync fetch. Sage owns the brief and will amend those lines to match D8 in a doc-only commit
after the re-pin window (sage-2205). They are not in this row's manifest.
### Webby's d2 notes
- N2: the `problem` line keeps the path. After `JSON.stringify` the precheck also escapes DEL, the C1
controls U+0080-009F, U+200E and U+200F, U+2028 and U+2029, and the bidi controls U+202A-202E and
U+2066-2069 as `\uXXXX`. Webby named C1 and bidi. I added the rest because `JSON.stringify` leaves
them raw too, and each one can change how a terminal shows the line. The test path holds U+202E and
U+0085, and the test checks that neither prints raw.
- N3: `lastError` is checked before `lastFetch`. A first sync with no token records
`library-credential-missing` and leaves `lastFetch` null, so it refuses `library-last-error`, not
`library-never-synced`. The fresh-mirror test has `lastError` null and refuses `library-never-synced`.
### Choices made in the build
- The config dir. The persona import refuses a config dir with group or other bits, and bootstrap makes
one with the default mode. So the install makes the config dir at 0700 when it's absent, before
bootstrap, and refuses `config-dir-mode` when it exists with a group or other bit. It checks
`secrets/` and `secrets/<id>/` the same way under library (`secrets-mode`, `slot-mode`).
- `--home` must equal `$HOME` (`home-mismatch`). Every path comes from `$HOME` and `MOSAIC_CONFIG`, as
bootstrap's does, so a different `--home` would only name a second home the script doesn't use.
- A plan step runs before anything is written. It reads the input and the staging manifest, computes
the persona vars from the manifest, and compares the result with what's on disk. It refuses every
input rule, `business-file-differs` and `persona-differs` there, so a refused run writes nothing.
After the import the script checks that the import's two `instance` lines carry the manifest's
digests.
- A re-run with both persona files present and matching skips the import, which would refuse to
overwrite them. One matching file and one missing refuses `persona-differs`.
- A diverged library prints one line under `--library-status`, naming the tip `library.sync --accept`
would take. It doesn't refuse. The design doesn't list it as a refusal, and the bus still serves the
old tip.
- The precheck's library checks print `ok` lines for each check they pass, as the rest of the script
does. A business file that's missing prints no extra line, since the existing business-file check
already refuses it.
- The business id must match the persona import's rule, `^[a-z0-9][a-z0-9-]{0,63}$`. That's narrower
than the precheck's, which also takes `.` and `_`, because the persona dir uses the id.
### Outside readers
`git grep` over packages/*/tests/*, packages/*/src/* and scripts/test-*.sh finds case2-precheck.sh read
by case2-unit.test.mjs, which runs it, and case1-accept.test.mjs, which names a copy's path but runs a
fake. No test reads host-run-sheet.md. No test pins a hash of either script. case2-unit.test.mjs's
precheck host has a 0600 business file and no persona vars, so the new D6a checks pass there.
### Mutants
24, in /mnt/storage/scratch/gosalyn-r226/mutate.py. Each replaces one exact string once. Line numbers
are the candidate's. run.sh stops with BASE RED before any mutant unless the base run is rc=0 and
fail 0.
| Mutant | Line | Check removed |
|---|---|---|
| I1 | install :73 | run-as-root |
| I2 | install :74 | home-mismatch |
| I3 | install :81 | config-dir-mode |
| I4 | install :101 | business-id-mismatch |
| I5 | install :107 | profile-library-business |
| I6 | install :108 | bus-tester-off |
| I7 | install :111 | bus-tester-on |
| I8 | install :115 | persona-var-input |
| I9 | install :158 | business-file-differs |
| I10 | install :152 | persona-differs |
| I11 | install :218 | N4, removes the file even when this run didn't write it |
| I12 | install :218 | validate's removal of the file this run wrote |
| P1 | precheck :229 | business-file-mode |
| P2 | precheck :235 | history-mode for changes.jsonl |
| P3 | precheck :246 | persona-mode for a persona file |
| P4 | precheck :251 | persona-mismatch |
| P5 | precheck :241 | persona-mode for a persona dir |
| P6 | precheck :258 | discord-binding |
| P7 | precheck :104 | library-mirror-missing |
| P8 | precheck :107 | library-last-error |
| P9 | precheck :107 | N3, the order swapped |
| P10 | precheck :108 | library-never-synced |
| P11 | precheck :92 | N2, the escape after stringify |
| P12 | precheck :65 | --library-status refuses --profile |
`history-mode` for the history dir itself has no mutant and no test. The brief names only the
`changes.jsonl` case.
### Tests
Not run yet. They wait for a slot.
-177
View File
@@ -1,177 +0,0 @@
# Row 239 (#1745) build notes (append only)
## 2026-10-22 b1 (Honker)
Design d1 5da5f28f6 was approved by Duckworth with C1 and C2 and no blockers (duckworth-0165, note
agents/duckworth/work/row239/REVIEW-design-d1.md). The code is on honker/row239 from aca16404c. No test
has run yet: every run waits for Sage's CLEAR.
### Duckworth's conditions and answers
- C1 (option a): `outcome-unknown-revoked` is in the Console's `OPEN` list (webui bus.mjs), so
`sendOutcome` answers it as `outcome-unknown` with `sent: 'unknown'` and never "Nothing changed". T6
covers it and M3f is its mutant.
- C2: `write()` sets `revoked = true` after `beforeWrite()` returns, and a catch around the whole
`withLock(...)` call marks the error. So a throw from the lock's release in its finally is marked as
well. This is structural and has no test, as Duckworth said.
- Q1: `beforeWrite` runs after both validations. Q2: two codes. Q3: M2 removes the `:188` check (now
organization.mjs:195).
### Notes N1-N5
- N1: `beforeWrite` must not write the same business, because it would find its own lock and refuse
`organization-busy`. This is said in the comments on `write()` and `commit()`. Today's revokes (the
bus's credential revoke) write no business file.
- N2: the lock is held across the revoke. A fire or a delete now keeps other writers of that business
out for as long as the revoke takes. They refuse `organization-busy`, as they do for any held lock.
- N3: a revoke that fails partway (some tokens revoked, then `credential-revoke-failed`) behaves as
today. The error carries no `revoked` mark, because `beforeWrite` didn't return, and nothing is
written. This row doesn't change that.
- N4: a delete closed with `outcome-unknown-revoked` exits 3 in the CLI, as every closed refusal does.
Exit 1 (transport EXIT) applies only to a direct verb answer.
- N5: I grepped for "before the write" and similar comments. The ones left (lifecycle-revoke.test:2,
instance-settings:56, cli instances.mjs:53, serve.mjs:365) are still true and are unchanged. T21 in
bus lifecycle-revoke.test:173 is unchanged.
### Differences from the design
- The business tests are in lifecycle-settings.test.mjs, not instance-settings.test.mjs. The fire tests
and their fixture already live there. The fixture now returns `dir` and `rolesDir` too.
- T3 covers T3b (the delete) in the same test, looping over fire and delete. It also checks that a
revoke throwing `credential-revoke-failed` carries no mark and writes nothing.
- T4b is the plain-code check. The design's "T4b, outcome-unknown-revoked for an fs-style error" is in
T4, which checks EIO with and without the mark for both a fire and a delete.
- The bus test's fake org gets `deleteError`, thrown from its `instances.set` branch, so T4 can drive a
delete.
- transport.test gets one assertion, `busExit("outcome-unknown-revoked") === 1`, for M3g.
- A latent Console text: a direct (not closed-request) `revision-conflict-revoked` answer in the
Console would still get " Nothing changed." appended, because only `OPEN` codes skip it. That sentence
is true there: the file is unchanged. No direct human path gives this code today, since a delete
reaches the Console as a closed request's refusal.
- The Console's STATUS entry for `outcome-unknown-revoked` (503) can't be reached while the code is in
`OPEN`. It stays for symmetry with the CLI and is harmless.
### What changed
- packages/business/src/organization.mjs: `write()` takes `beforeWrite`, runs it under the lock after
the revision check and both validations, and marks any later error `revoked`. The body is reindented
inside the try.
- packages/business/src/instance-settings.mjs: `instanceSettings` takes an optional `writer`. `commit()`
passes `beforeWrite` to `writer.write` instead of calling it. The early check stays, and its comment
is rewritten.
- packages/bus/src/broker.mjs: `#organizationFail` maps a marked error to `revision-conflict-revoked` or
`outcome-unknown-revoked` before the existing mappings. `#fireJob`'s notice adds "Its credential
tokens may already be revoked." for a `-revoked` code.
- packages/bus/src/runtime.mjs: comment only.
- packages/cli/src/instances.mjs: two REASONS entries. packages/cli/src/transport.mjs: EXIT
`outcome-unknown-revoked` 1.
- packages/webui/src/serve.mjs: two AGENT_REASONS entries. packages/webui/src/bus.mjs: `OPEN` entry and
two STATUS entries.
- Tests:
- business lifecycle-settings.test: "row 239 T1", "row 239 T2", "row 239 T3".
- bus fire-revokes-grants.test: "row 239 T4", "row 239 T4b".
- cli instances.test: "row 239 T5 CLI". cli transport.test: one assertion in "busExit and
refuseInsideAgent".
- webui agents.test: "row 239 T6".
- bus channel-proof.test re-pins packages/cli/src/transport.mjs (662642a8...) and
packages/webui/src/bus.mjs (1eba9774...), with a comment on each. Neither changes a proof or the
transport. The design's file list missed this. The pin grep found it before the first run.
### Mutants for the gate (exact-once replacements)
- M1 (instance-settings.mjs, beforeWrite before the lock): replace
` const receipt = writer.write(business, next, { revision, actor, context, beforeWrite });` with
` if (beforeWrite) beforeWrite();\n const receipt = writer.write(business, next, { revision, actor, context });`.
The kill is lifecycle-settings "row 239 T1" (the revoke spy has a call).
- M2 (organization.mjs, the fire refusal dropped): delete the line
` if (revision !== before) fail("revision-conflict", "organization revision changed; read again");`.
The kill is "row 239 T1" (the write lands over the hand edit).
- M3a (organization.mjs, the mark dropped): ` if (revoked && e instanceof Error) e.revoked = true;`
becomes ` void revoked;`. The kills are "row 239 T2" and "row 239 T3" (business). T4 drives the mark
through a fake org, so it doesn't see M3a.
- M3b (broker.mjs): delete the line
` if (e.revoked) fail(e.code === "revision-conflict" ? "revision-conflict-revoked" : "outcome-unknown-revoked");`.
The kill is fire-revokes-grants "row 239 T4".
- M3c (cli instances.mjs): delete the five lines from ` "revision-conflict-revoked": "the business file`
through ` "business file again with \`mosaic business instances\` before trying again",`. The kill is
instances.test "row 239 T5 CLI".
- M3d (webui serve.mjs): delete the two lines starting ` 'revision-conflict-revoked': 'the business file`
and ` 'outcome-unknown-revoked': 'the agent\'s tokens`. The kill is agents.test "row 239 T6".
- M3e (broker.mjs): ` const tokens = code.endsWith('-revoked') ? ' Its credential tokens may already be revoked.' : '';`
becomes ` const tokens = '';`. The kill is "row 239 T4".
- M3f (webui bus.mjs, C1): the `OPEN` line loses `, 'outcome-unknown-revoked'`. The kill is "row 239 T6"
(the launch POST answers 503 outcome-unknown, sent unknown).
- M3g (cli transport.mjs): delete the line ` "outcome-unknown-revoked": 1,`. The kill is transport.test
"busExit and refuseInsideAgent".
A kill counts only as a failed assertion in the named test, not a load error or a different test.
### Gate plan (mos-1275 timeouts)
- My row 256 gate (2026-10-22 10:34Z, same machine) took 590s for the cli package, 57s for webui and
38s for test-foundation, the longest shell script.
- Packages: `timeout -k 10 1800` per package (3x the cli run).
- Each scripts/test-*.sh: `timeout -k 10 300` (more than 3x the 38s longest).
- Single files and mutants: `timeout -k 10 600` per file run (cli instances.test whole is the longest
single file).
- Order on Sage's CLEAR: the touched test files whole (business lifecycle-settings, bus
fire-revokes-grants and channel-proof, cli instances and transport, webui agents), then M1-M3g, then
the full gate.
- Before the in-review move: grep packages/*/tests for the changed comment and error text and for
64-hex pins on the changed files, and drift-check BASE..refactor against them.
## 2026-10-22 r2 rebase (Honker)
- Duckworth approved b1 b0c28366f statically, with no blockers (duckworth-0168). On N1 (`endsWith('-revoked')`
would also match `access-revoked`, which can't reach a fire job today), the check stays as it is, so the
approved candidate doesn't change before the gate (honker-0163).
- Rows 253 (48e450ce8) and 275 landed. b1 is rebased onto refactor 94c33a43c as honker/row239-r2. The
push branch honker/row239 isn't rewritten.
- Drift between aca16404c and 94c33a43c on the 15 paths: only packages/bus/src/broker.mjs, where row 275
changes the viewer verbs. Neither of row 239's two hunks (`#fireJob` and `#organizationFail`) moves in
meaning. The rebase applied with no conflict. The two pinned files (cli transport.mjs, webui bus.mjs)
and channel-proof.test have no upstream change, so the b1 pins stand.
- Each M1-M3g replacement string still matches exactly once.
- The touched test files run whole again on the new base, in the slot (the rebase rule).
## 2026-10-23 r3 rebase (Honker, sage-3730)
- r2 (70c6e3772) is rebased onto refactor 93694c518 as honker/row239-r3. honker/row239-r2 isn't rewritten.
- Between 94c33a43c and 93694c518, five of the 16 paths changed upstream: bus broker.mjs and runtime.mjs,
channel-proof.test, and webui bus.mjs and serve.mjs. Every file merged on its own except channel-proof.test,
where row 301 (59f0e44f4, mos-1449) re-pinned webui bus.mjs for the user transport fold. The merged
pin keeps row 301's comment and row 239's, and takes sha256 e3f52172…1c2a89 of the merged bus.mjs.
The transport.mjs pin (662642a8…6e55b) still matches the file.
- Row 239's hunks in broker.mjs (`#fireJob`, `#organizationFail`), runtime.mjs (the revoke comment),
bus.mjs (OPEN, STATUS) and serve.mjs are unchanged apart from their position. The range-diff shows
only the channel-proof pin and the hunk context moved.
- Each M1-M3g replacement string still matches exactly once. M3f's target is the whole `OPEN` line,
since `, 'outcome-unknown-revoked'` also appears in the STATUS line.
- No 64-hex pin in packages/*/tests or scripts names a changed file except channel-proof.test.
- On the CLEAR (sage-3730): the touched files whole, then M1-M3g, then the counts here. Sage runs the
full gate on the replay at landing.
## 2026-10-23 r4 replay and the slot run (Honker, sage-3773 CLEAR)
- On the CLEAR, refactor was 546366f4a (row 320 landed at 094124b4e). None of the 16 manifest paths changed
between 93694c518 and 546366f4a. Upstream did change library src/index.mjs, which bus runtime.mjs imports,
and business src/index.mjs, which the cli imports, so the run was on a replay onto 546366f4a rather than
on r3's base. That replay is honker/row239-r4 0cae2dad5. The range-diff of r3 against r4 is four `=`
lines with equal patch-ids, so duckworth-0292's read of r3 applies to r4. r3 isn't rewritten.
- The run was on 0cae2dad5 with a clean tree, TMPDIR under /mnt/storage/scratch/tmp, 22:01:27Z-22:01:52Z.
- The touched files, run whole: business lifecycle-settings 12/12, bus fire-revokes-grants 24/24, bus
channel-proof 2/2, cli instances 15/15, cli transport 3/3, webui agents 9/9. That's 65/65 with 0 skipped,
and no first-run red.
- Mutants: 9 of 9 killed. Each was applied exactly once, and each made its named test fail as an assertion:
- M1 lifecycle: 3 fail (T1, T2, T3).
- M2: T1.
- M3a: T2 and T3.
- M3b fire-revokes-grants: T4.
- M3c instances: T5 CLI.
- M3d agents: T6.
- M3e: T4.
- M3f agents: T6.
- M3g transport: "busExit and refuseInsideAgent".
T4b passed under M3b and M3e. Every restore matched the HEAD blob by sha256, and the tree was clean
after the run.
- No full gate (sage-3730): Sage runs it on the replay at landing.
-159
View File
@@ -1,159 +0,0 @@
# Row 239 design: the revoke under the writer's lock, and a fire test
Brief: docs/plans/2026-10-20_agent-lifecycle.md, "Brief: row 194 follow-up, the revoke under the writer's
lock and a fire test" (blob 3b3b3b2b9), from Duckworth's row 194 b3 N1 and mos-1158. Base aca16404c (row
194 landed as fd6dd327c, row 281 as e479521d3). Builder Honker, reviewer Duckworth, gate owner Sage.
Draft 1, 2026-10-22.
## Today
- `instance-settings.mjs` `commit()` runs the checks, then compares the caller's revision with a fresh
`writer.read` (row 194 b3, sage-1634), then calls `beforeWrite` (the fire's or the delete's credential
revoke), then `writer.write`.
- `writer.write` (`organization.mjs`) takes the lock, runs `recover`, checks the revision again (`:188`),
validates, prepares, checks the bytes once more before publication (`:221`), renames and commits.
- So two windows are left.
- A write that lands between `commit()`'s read and the writer's lock still gets a revoke and then a
`revision-conflict`.
- Any refusal after the revoke ran (the `:221` check, or an I/O failure the bus reports as
`outcome-unknown`) uses the same text as one before it, so nothing says the tokens may be gone.
- Row 281 left this path alone. Its fire job (`broker.mjs` `#fireJob`) runs step G (grants), then step B,
which is this same `write('instance.fire')`, and reports a step B failure as `fire.failed` along with a
human notice that names the code. Its delete job only does step G. The delete itself still commits
through `lifecycleDelete` → `instances.set` op delete → `commit()`.
## What changes
### 1. `beforeWrite` runs inside `writer.write`, under the lock (organization.mjs)
`write(id, document, { revision, actor, detail, context, beforeWrite = null })`. The order under the lock:
1. `recover`, read, then the `:188` revision check. A refusal here revokes nothing.
2. Projects-protected and both validations. A refusal here revokes nothing either. I put `beforeWrite`
after these rather than straight after `:188`, so a document the writer would refuse anyway never
costs a revoke. Q1 asks Duckworth to confirm this.
3. `beforeWrite()`. If it throws (today `credential-revoke-failed`), the error propagates as it is and
nothing has been written, as now.
4. Prepare, the `:221` check, rename and commit, wrapped so that any error thrown after step 3 gets
`revoked = true` set on it before it is rethrown. That covers `revision-conflict` from `:221`, an
`organization-refused` from `log()`, a barrier throw, and an fs error.
`record()` and the other writers (`launch-settings`, `plugin-settings`, `credentialVerbs`) pass no
`beforeWrite` and behave as before.
### 2. commit() passes beforeWrite through (instance-settings.mjs)
`commit()` passes `beforeWrite` to `writer.write` and no longer calls it itself. The row 194 b3 early check
stays. It costs one read, keeps T21's refusal before any validation work, and is no longer what protects
the revoke. Its comment is rewritten to say so, and the "(BUILD.md follow-up)" sentence goes.
`instanceSettings({ ..., writer = organizationWriter({ dir, rolesDir }) })` takes an optional writer, so a
test can inject one (the brief's "injected writer"). `runtime.mjs` passes none.
### 3. The bus names a refusal after a revoke (broker.mjs `#organizationFail`)
The bus answers with a code only, and the CLI and Console look the text up by code. So the "may already be
revoked" signal has to be a code. Two new codes:
- `revision-conflict-revoked`: `e.revoked && e.code === 'revision-conflict'`.
- `outcome-unknown-revoked`: `e.revoked` with any other code. The write may or may not have landed, since a
failure after the rename is recovered as committed.
Both are checked before the existing mappings. An error without `revoked` maps exactly as today, so a
refusal before any revoke keeps today's code and text. Q2 asks about the names.
### 4. The texts
- CLI `instances.mjs` REASONS:
- `revision-conflict-revoked`: "the business file changed before the write, after the agent's tokens
were revoked; the tokens may already be revoked and the instance can't use them; read the instances
again and run the same command again, which is safe".
- `outcome-unknown-revoked`: "the write's outcome is unknown and the agent's tokens may already be
revoked; read the instances before trying again".
- Console `serve.mjs` AGENT_REASONS: the same two, in the Console's wording ("delete again").
- `#fireJob`'s `fire.failed` notice: when the code ends in `-revoked`, it adds "Its credential tokens may
already be revoked." The chief's synchronous fire (no grants) answers with the code, as every chief verb
does.
- `transport.mjs` EXIT: `outcome-unknown-revoked` exits 1, as `outcome-unknown` does. Console
`bus.mjs` STATUS: `revision-conflict-revoked` 409, `outcome-unknown-revoked` 503. A delete's code
reaches both as a closed request's `refusal`, so these two only matter for a direct verb answer.
## Tests
- **T1, the brief's fire test** (business `instance-settings.test.mjs`). The fire uses an injected writer
that wraps the real one. Its `write` makes one hand edit of the business file (a vars change on another
instance) and then calls the real `write`, so the edit lands after `commit()`'s early check and before
the lock's `:188` check. Expected:
- the fire refuses `revision-conflict` without `revoked`;
- the revoke spy has no calls;
- the business file's bytes are the hand edit's;
- changes.jsonl has no new prepared entry.
- **T2** (same file). A fire on a real writer built with `barrier: stage => stage === 'prepared' && edit()`.
The revoke runs, then `:221` refuses. Expected: `revision-conflict` with `revoked === true`, one revoke
call, the file's bytes are the edit's, and the last receipt is `aborted`.
- **T3** (same file). The same, with a `prepared` barrier that throws. Expected: the error has
`revoked === true`, one revoke call, and the file unchanged. A delete through the same writer gives the
same result (T3b).
- **T4** (bus `fire-revokes-grants.test.mjs`, its injected `f.org`). `f.org.fireError` is a
`revision-conflict` with `revoked: true`. Expected:
- the job's `fire.failed` carries `revision-conflict-revoked`, and the inbox message says "may already
be revoked";
- the same with no `grants` (the synchronous fire) refuses `revision-conflict-revoked`;
- a plain `revision-conflict` still maps to `revision-conflict` and the message lacks the sentence.
- T4b: `outcome-unknown-revoked` for an fs-style error with `revoked`.
- **T5** (cli `lifecycle.test.mjs` or `instances.test.mjs`, whichever already reads a closed delete's
refusal). A delete closed with `revision-conflict-revoked` prints the new text, and one closed with
`revision-conflict` prints today's.
- **T6** (webui `agents.test.mjs`). The same pair for the Console's reason.
- T21 (bus `lifecycle-revoke.test.mjs`) stays as it is, for delete.
## Mutants
| Mutant | Change | Killed by |
| --- | --- | --- |
| M1 | `beforeWrite` before the lock: `commit()` calls `beforeWrite()` itself and passes none to `write` | T1 (the revoke spy has a call) |
| M2 | The fire refusal dropped: the `:188` revision check removed | T1 (the write lands over the hand edit) |
| M3a | The mark dropped: no `revoked = true` in the catch | T2, T3, and T4 through the codes |
| M3b | `#organizationFail` ignores `revoked` | T4 |
| M3c | CLI text: the two REASONS entries removed | T5 |
| M3d | Console text: the two AGENT_REASONS entries removed | T6 |
| M3e | The notice: the `fire.failed` sentence removed | T4 |
M3a-M3e together are the brief's "drop the may already be revoked text".
## Files
- `packages/business/src/organization.mjs`
- `packages/business/src/instance-settings.mjs`
- `packages/business/tests/instance-settings.test.mjs`
- `packages/bus/src/broker.mjs`
- `packages/bus/tests/fire-revokes-grants.test.mjs`
- `packages/cli/src/instances.mjs`
- `packages/cli/src/transport.mjs`
- one cli test file
- `packages/webui/src/serve.mjs`
- `packages/webui/src/bus.mjs`
- `packages/webui/tests/agents.test.mjs`
- This packet.
## Gate
As the brief says:
- Duckworth approves the manifest.
- The bus, business, cli and webui suites and every `scripts/test-*.sh` are green on Sage's slot, with
the touched test files run whole.
- M1-M3e are killed.
Before the in-review move I grep the tests for the changed comment and error text, and for hash pins on the
changed files.
## Questions for Duckworth
- **Q1.** Should `beforeWrite` run after the writer's validations (my pick) or straight after `:188` (the
brief's literal place)? Running it later means a document the writer would refuse revokes nothing.
- **Q2.** Are two new codes, `revision-conflict-revoked` and `outcome-unknown-revoked`, the right way to
carry the signal? The alternative is to give `BusError` a detail field, which every CLI and Console
reader would then have to learn. The codes are additive and no existing code changes meaning.
- **Q3.** The brief's mutant "drop the fire refusal": I read it as removing the `:188` check (M2). If you
meant the fire test's own refusal, say so and I'll redefine it.
-276
View File
@@ -1,276 +0,0 @@
# Row 283 (#1788) C2-01d build notes (Honker)
Append-only. Design: DESIGN.md d2 16e809457, APPROVE webby-0172. Gate owner Sage, reviewer Webby.
## 2026-10-23 build on 45dca69f6 (sage-3577)
Branch honker/row283-build from origin/refactor 45dca69f6e4bce52834587f66f2ff33ba839ab8a. The old branch
honker/row283 (16e809457) held only the design, so its two commits were cherry-picked as 398314cf4
(d1) and 4f66dccde (d2). The DESIGN.md blob equals 16e809457's.
Commits:
- 331653b38: code, README and tests T1-T8.
- 28918824c: the survey.test:73 re-pin, alone (mos-1330 item 4).
- this file.
Nothing has run. No node, test or mutant ran during the build: Darkwing held the slot. Every count
below is a plan until Sage's CLEAR.
### Drift check (sage-3577)
`git diff --stat 94c33a43c 45dca69f6` (design base to build base) over the paths the tests read or
hash-pin:
- No change in packages/library, packages/business/src/business.mjs or
packages/business/tests/business.test.mjs.
- No test hash-pins a library file. bus library.test and library-sync.test import index.mjs, and
stack-copy.test only checks that index.mjs exists.
- webui library-cli.test changed one line and doesn't read scope. The `classify` in webui reads.mjs is
the business one, not the library's.
I redo the drift check on BASE..refactor before the in-review move.
### N3 (webby-0169): deepEquals of the mapping output outside packages/library
`git grep -n playground -- 'packages/*/tests/*' ':!packages/library/*'` and the files naming
`universes` give two files:
- business.test.mjs:20 compares `validateBusinessDocument(...).projects.books` with
`checkMapping(mappingEntry())`. That is output against output, so `supporting: []` shows up on both
sides. :27 compares only `.playground`.
- library-projects-protected.test.mjs compares file bytes (:31, :32) and change paths (:58), never the
mapping output.
No re-pin is needed outside library. Inside library, only the draft deepEqual at mapping.test:48 gains
`supporting: []`, as the design says.
### Q1 departure (sage-2923) and the :73 re-pin (mos-1330 item 4)
survey.mjs:65 counts `supporting: 0` and formatSurvey prints it. T8 is the only added test
(survey.test "C2-01d: with --mapping the survey counts supporting files").
mos-1330 item 4, packages/library/tests/survey.test.mjs:73, commit 28918824c:
- old: `'scopes canon 5, playground 1, unmapped 3; indexed 6',`
- new: `'scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6',`
N2: the pin gains only `supporting 0`. No other text on the line or in that test moved.
### Q2 tradeoff (webby-0169)
A supporting prefix that holds a playground prefix refuses `library-mapping-invalid`. That goes past
the brief, but it is the fail-closed reading, and it uses an existing code: classify checks supporting
first, so that playground prefix could never match. The cost is that she can't nest a playground under
a supporting folder, though she can nest the other way (a playground holding a supporting folder).
### Handoffs
- Q4 (webby-0169, sage-2930): the M12 delete rule ("A delete also needs the path to exist at the tip as
a 100644 blob", case2 DESIGN.md :308) is not in supportingPath. It binds row 288 (C2-02) via Gitea
#1793 comment 30738, with tests for a missing path and a non-100644 entry.
- A save with no `editor` refusing is C2-02's (DESIGN.md section 1).
### Choices the design left open
- The book loop is now `againstBooks(owners, list, code)` in mapping.mjs, called once per list. The
playground's messages and codes are byte-equal to before.
- A non-string path refuses `library-write-story-path` ("the path must be a string") as part of
check 1. A non-array `tree` is a TypeError (DESIGN.md section 3).
- Check 8 builds the tree's names as every path and every folder above it, keyed by
`normalize('NFC').toLowerCase()`. A name on the way refuses if its key holds any name other than
itself, so a tree that already holds case twins refuses a write to either (R7).
### Mutants (exact strings)
Each applies one replacement to the build tip. Run one at a time, and restore with `git checkout --`
on the one file before the next.
| Mutant | File | Replace | With | Killed by |
|---|---|---|---|---|
| M1 | src/scope.mjs | `const prefix = entry.supporting.find(p => inside(p, path));` | `const prefix = entry.supporting.find(p => inside(p, path) && !entry.playground.some(q => inside(q, path)));` | scope.test "supporting comes before playground", supporting.test "passes at book level" (both-lists path) |
| M2 | src/mapping.mjs | `if ((inside \|\| holding) && owner !== id)` | `if ((inside \|\| holding) && owner !== id && list !== 'supporting')` | mapping.test "each supporting prefix rule", business.test C2-04 |
| M3 | src/supporting.mjs | `for (const p of own) if (LINKS.has(modes.get(p)))` | `for (const p of own) if (false && LINKS.has(modes.get(p)))` | supporting.test "symlink or gitlink" |
| M4 | src/supporting.mjs | `const fold = name => name.normalize('NFC').toLowerCase();` | `const fold = name => name.normalize('NFC');` | supporting.test "case or Unicode form" |
| M4b | src/supporting.mjs | `const fold = name => name.normalize('NFC').toLowerCase();` | `const fold = name => name.toLowerCase();` | supporting.test "case or Unicode form" (NFD case) |
| M5 | src/supporting.mjs | `Buffer.byteLength(path, 'utf8') > MAX_BYTES` | `path.length > MAX_BYTES` | supporting.test "1025 bytes" |
| M6 | src/mapping.mjs | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f<>]/;` | `const IDENT_BAD = /[<>]/;` | mapping.test "editor is optional" |
| M6b | src/mapping.mjs | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f<>]/;` | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f]/;` | mapping.test "editor is optional" |
| M7 | src/supporting.mjs | `if (place.scope !== 'supporting' \|\| place.universe !== universe)` | `if (place.scope !== 'supporting')` | supporting.test "outside the universe's supporting folders" |
| M8 | src/mapping.mjs | `if (sid === pid && s !== p && holds(s, p))` | `if (false && sid === pid && s !== p && holds(s, p))` | mapping.test "each supporting prefix rule" (Workshop case) |
(The `\|` in the table is an escaped `|` in the source text.) Each mutant runs the one test file named,
plus business.test for M2, under `timeout -k 10 600`. A mutant passes the gate only when its run is red
on the named test and the restored file's sha256 equals the build tip's.
### Gate plan (on Sage's CLEAR)
1. The touched files whole, each under `timeout -k 10 600`: library mapping, scope, supporting and
survey tests, and business.test.mjs. Counts go below.
2. M1-M8 as above.
3. The full gate, under mos-1275 timeouts: `node --test packages/library/tests/` and
`node --test packages/business/tests/` each under `timeout -k 10 1800`, and every scripts/test-*.sh
under `timeout -k 10 300`. pipefail and tee, with skips named (test-task Docker groups).
4. Before in-review: grep packages/*/tests for a 64-hex pin on any changed file and for the changed
message text, then the drift check on BASE..refactor.
## 2026-10-23 Webby early look (webby-0227, static, not a verdict)
- E1: business.test.mjs is an existing test file with a 4-line addition after the playground refusal.
The in-review request lists it next to survey.test:73.
- E3 fixed: supportingPath now runs the string check before `JSON.stringify`, so a BigInt (or any
non-string) path refuses `library-write-story-path` instead of throwing a TypeError.
supporting.test's string case now covers 7, null, undefined, 1n and an array.
- E2 open: `editor` takes surrounding whitespace, and git trims it at commit, so the saved ident and
the commit ident can differ. DESIGN.md section 1 says "non-empty after trim". Refusing
`value.trim() !== value` would change that approved rule, so it went to Sage instead of into the
build.
## 2026-10-23 E2 departure (sage-3601)
A departure from DESIGN.md section 1 (which stays unedited). The design says each `editor` value is
"non-empty after trim". Sage ruled that surrounding whitespace refuses too, because git trims an
ident at commit, so a padded value would save one name and commit another. mapping.mjs now adds
`if (value.trim() !== value) bad(`editor.${key} must not start or end with whitespace`)` after the
IDENT_BAD check. `trim()` covers Unicode whitespace, so U+00A0 and U+3000 refuse as well. Inner
whitespace is kept ("Her Middle Name" passes).
T3 gains a case per key for leading, trailing and both-ends spaces, U+00A0 and U+3000, plus one inner
whitespace pass.
M6 grows a third part:
| Mutant | File | Replace | With | Killed by |
|---|---|---|---|---|
| M6c | src/mapping.mjs | `if (value.trim() !== value) bad(` | `if (false && value.trim() !== value) bad(` | mapping.test "editor is optional" (padded cases) |
Fixture grep: `git grep -nE "\beditor\b|\bauthor\b" -- 'packages/library/tests/*' 'packages/business/tests/*'`,
leaving out mapping.test.mjs, finds nothing. packages/business/tests has no fixtures directory. The
library fixture mapping.json has no `editor`. The positive control (`vaultPrefix` in mapping.json)
matches once. No fixture maps an editor or author with surrounding whitespace.
## 2026-10-23 E4 (webby-0230, sage-3606), same departure as E2
Claimed: `queue move 283 in-progress`, op honker-283-start, rev 3642, 19:10:49Z (briefed→in-progress).
Git's ident.c `crud()` strips any byte <= 0x20 and `, : ; < > " \ '` from both ends of an ident. I
checked that with git 2.55.0: `Jane,` `"Jane"` `'Jane'` `Jane;` `Jane:` `Jane\` and `,Jane` all
commit as `Jane`, while `Jane.` and `Ja,ne` are kept. mapping.mjs now takes the set from crud()
rather than from that list: `IDENT_CRUD_END = /^[\u0000-\u0020,:;<>"\\']|[\u0000-\u0020,:;<>"\\']$/`.
The E2 check became `if (value.trim() !== value || IDENT_CRUD_END.test(value))`, which refuses
library-mapping-invalid with "editor.<key> must not start or end with whitespace or any of , : ; " ' \".
`trim()` still catches Unicode whitespace (U+00A0, U+3000), which crud() leaves alone.
Inner characters: IDENT_BAD (`/[\u0000-\u001f\u007f-\u009f<>]/`) already refuses `<`, `>`, newline,
tab and every other control anywhere in the value. T3 now pins that at the start, the end and the
middle. Inner `, : ; " ' \` are kept, since git keeps them, and T3 pins that too.
Tests:
- T3 has one case per character in [space, U+00A0, U+3000, `,`, `:`, `;`, `"`, `'`, `\`], at the start
and at the end, for each key. `<`, `>`, newline and tab are pinned at the start, end and middle.
Four accepted names have inner crud characters.
- New round-trip test, "an accepted editor round-trips through a real git commit unchanged". It
commits in a scratch repo with editor `Doe, Jane Q.` / `[email protected].` as author and
committer, with GIT_AUTHOR_* and GIT_COMMITTER_* stripped from the env, and asserts `git log`
shows exactly the saved values. I preflighted that commit with git 2.55.0 by hand: both values came
back unchanged.
M6c now replaces the whole end check (this supersedes the earlier M6c row):
| Mutant | File | Replace | With | Killed by |
|---|---|---|---|---|
| M6c | src/mapping.mjs | `if (value.trim() !== value \|\| IDENT_CRUD_END.test(value))` | `if (false)` | mapping.test "editor is optional" (end cases) |
| M6d | src/mapping.mjs | `if (value.trim() !== value \|\| IDENT_CRUD_END.test(value))` | `if (value.trim() !== value)` | mapping.test "editor is optional" (`,` `:` `;` `"` `'` `\` cases) |
## 2026-10-23 E5 (webby-0236, not blocking), fixed
The round-trip test stripped only GIT_AUTHOR_* and GIT_COMMITTER_*. With GIT_DIR, GIT_WORK_TREE or
GIT_INDEX_FILE set (inside a hook, say), GIT_DIR beats `-C root` and the commit would land in the
outer repo, and the test read global and system config. It now deletes GIT_DIR, GIT_WORK_TREE,
GIT_COMMON_DIR and GIT_INDEX_FILE (as queue/tests/helpers.mjs gitEnv does) and sets
GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1 (as ledger.test.mjs does). The `-c user.*`
ident still applies. Test only; no mutant changes.
## 2026-10-23 sage-2925: optional editor.user (sage-3649)
sage-2925 (recorded in ludwig/row286-r2 agents/ludwig/work/queue-286/BUILD.md, Q4) ruled that row 283
adds an optional `editor.user`, her Gitea login for the `library-human` slot, which row 288 (C2-02)
passes to `humanCredential(context, {user})`. It reached this row after the DESIGN.md d2 approval
(ludwig-0068, bubba-0009, sage-3649), so it departs from DESIGN.md section 1 ("exactly the keys
`name` and `email`"). DESIGN.md stays unedited.
- mapping.mjs: `only(entry.editor, ['name', 'email', 'user'], 'editor')`. When present, `user` must be
a string matching USER (`/^[A-Za-z0-9_.-]{1,100}$/`, the entry `user` check), else
library-mapping-invalid "editor.user must be a plain account name". The output carries `user` only
when given. An absent `editor.user` refusing library-human-missing is 288's (bubba-0009).
- README: the `editor` line names `user` and the end-character rule from E2/E4.
- Tests: T3 (mapping.test "editor is optional") gains one accepted case (`her.login-1`, kept in the
output) and refused cases for '', an inner space, '@', 101 characters, 7 and null. They go in
mapping.test with the other editor cases, not in the new supporting.test, which tests write paths.
- No test outside mapping.test reads `editor` or pins mapping.mjs or README (git grep).
| Mutant | File | Replace | With | Killed by |
|---|---|---|---|---|
| M6e | src/mapping.mjs | `if (Object.hasOwn(entry.editor, 'user') && (typeof entry.editor.user !== 'string' \|\| !USER.test(entry.editor.user)))` | `if (false)` | mapping.test "editor is optional" (editor.user refusals) |
| M6f | src/mapping.mjs | `...(Object.hasOwn(entry.editor, 'user') ? { user: entry.editor.user } : {})` | `...({})` | mapping.test "editor is optional" (`her.login-1` kept) |
## 2026-10-23 T2 run (sage-3695 CLEAR, lane A, library and business only)
Node v26.8.1. refactor was e2b873390 (226 landed); `git diff --stat 45dca69f6 e2b873390 -- packages/library
packages/business` is empty. TMPDIR was a mktemp -d under /mnt/storage/scratch/tmp, with pipefail.
Singles and mutants ran under `timeout -k 10 600`, suites under `timeout -k 10 1800`.
First-run red (mos-1272, placed): the first base run on 46bad7ea3 at 20:01:09Z was red in
supporting.test "malformed paths refuse by class, in order". 5 of 6 passed, with `TypeError: Do not know
how to serialize a BigInt`. The cause was the test helper, not the product: `refuses()` labelled each
assertion with `JSON.stringify(path)`, and the 1n case added for E3 can't be stringified. In c18ad3e12
the label uses `util.inspect(path)`, a 2-line test-only change. The product's 1n refusal was never in
question: supportingPath type-checks before it stringifies (E3). The whole run below is on c18ad3e12.
Base, the touched files whole (20:01:43Z), all rc=0:
- library mapping.test 16/16, scope.test 6/6, supporting.test 6/6, survey.test 7/7;
- business business.test 21/21;
- 0 skipped, 0 cancelled, 0 todo.
Mutants (20:01:44Z). Each was applied by exact string with count 1, then restored with `git checkout --`.
Every restored sha256 equals HEAD's blob, and the tree was clean after.
| Mutant | Run | Result | Failing test |
|---|---|---|---|
| M1 | scope.test | rc=1, 5/6 | supporting comes before playground; a folder in both lists is supporting… |
| M1 | supporting.test | rc=1, 5/6 | a path passes at book level, universe level, in a draft and in a folder in both lists |
| M2 | mapping.test | rc=1, 15/16 | each supporting prefix rule refuses with its code |
| M2 | business.test | rc=1, 20/21 | C2-04: library mappings load without roots, remain frozen, and retain mapping refusals |
| M3 | supporting.test | rc=1, 5/6 | a symlink or gitlink on the way, or at the path itself, refuses |
| M4 | supporting.test | rc=1, 5/6 | a name that differs from a tree name only in case or Unicode form refuses… |
| M4b | supporting.test | rc=1, 5/6 | the same case/Unicode test |
| M5 | supporting.test | rc=1, 5/6 | a path of exactly 1024 bytes passes, and 1025 bytes… refuses |
| M6 | mapping.test | rc=1, 15/16 | editor is optional, kept when given, and refuses anything but a plain name and email |
| M6b | mapping.test | rc=1, 15/16 | the same editor test |
| M6c | mapping.test | rc=1, 15/16 | the same editor test |
| M6d | mapping.test | rc=1, 15/16 | the same editor test |
| M6e | mapping.test | rc=1, 15/16 | the same editor test |
| M6f | mapping.test | rc=1, 15/16 | the same editor test |
| M7 | supporting.test | rc=1, 5/6 | a path outside the universe's supporting folders refuses |
| M8 | mapping.test | rc=1, 15/16 | each supporting prefix rule refuses with its code |
14 of 14 mutants killed, each on its named test only.
Suites (20:01:47Z):
- `packages/library`: `node --test tests/*.test.mjs` rc=0, 98/98, 0 skipped.
- `packages/business`: `node --test tests/` rc=0, 231/231, 0 skipped.
Not run here: every scripts/test-*.sh and the other packages. Those are Sage's 283 gate, after Webby's
APPROVE.
## 2026-10-23 G1: mos-1528 OK for the mapping.test draft deepEqual (sage-3713)
This entry completes the N3 line above (BUILD.md:42, "only the draft deepEqual at mapping.test:48 gains
`supporting: []`"). BUILD.md is append-only, so :42 stays as written. Mos OK'd the change of that old
assertion in mos-1528. It is now at packages/library/tests/mapping.test.mjs:52, and commit 331653b38
changed it. Everything else in that commit's hunk is new test lines, not changed old ones.
- Old, mapping.test.mjs:48 at 45dca69f6:
`assert.deepEqual(m.universes.idea, { kind: 'draft', name: 'New World', books: [], playground: ['Drafts/New World/'] });`
- New, mapping.test.mjs:52 at b2a1f4092:
`assert.deepEqual(m.universes.idea, { kind: 'draft', name: 'New World', books: [], playground: ['Drafts/New World/'], supporting: [] });`
The line gains only `, supporting: []`; nothing else in it moved. The business.test and scope.test hunks
add tests only (sage-3713: no OK needed). The other old-test change in this row is survey.test:73
(mos-1330 item 4, 28918824c). Doc only.
-191
View File
@@ -1,191 +0,0 @@
# Row 283 (#1788) C2-01d design, draft 2 (Honker)
Brief: docs/plans/2026-10-20_case2-rows.md "C2-01d" at refactor 94c33a43c. Sources: the design at
agents/gosalyn/work/case2/DESIGN.md b68d79663, the ruling after r11 (:207), the M12 paragraph (:300),
1.1 (:755) and 1.2 (:817). Base 94c33a43c. Reviewer Webby, gate owner Sage. Nothing is built yet. The
build waits for 282 to land (sage-2906).
## 1. Mapping (packages/library/src/mapping.mjs)
`supporting`:
- Every universe kind takes an optional `supporting` list: plain `['name','books','playground','supporting']`,
hybrid adds it to its seven keys, and a draft gets `['kind','name','playground','supporting']`.
- Each value is checked as a playground prefix is: a string ending in `/` whose body passes `folder()`.
- The output carries `supporting` on every universe, `[]` when absent, the way a draft carries
`books: []`. classify then needs no `?? []`. The business writer writes the raw document
(organization.mjs:195), not this output, so no business file changes shape.
- The prefix rules, across every entry (ruling after r11):
- A supporting prefix holding another supporting prefix, or one listed twice, refuses
`library-mapping-invalid`.
- A prefix equal to one of its own entry's book folders, or holding one, refuses `library-mapping-invalid`.
- A prefix inside or holding another entry's book folder refuses `library-supporting-other-universe`.
- Level comes from where the prefix sits, as for the playground: inside one of its own entry's books
is book level, and outside every book is universe level. A draft's prefixes are all universe level.
- Between the two lists (my choice, Q2):
- A supporting prefix may equal a playground prefix of the same entry. That folder is writable.
- A playground prefix may hold a supporting prefix of the same entry. The inner folder is supporting
and the rest stays playground.
- A supporting prefix that holds a playground prefix refuses `library-mapping-invalid`. classify
checks supporting first, so that playground prefix could never match.
- A supporting prefix equal to, holding or inside a playground prefix of another entry refuses
`library-mapping-invalid`. Otherwise one path would have two universes.
- The book loop at mapping.mjs:128-140 becomes a helper that both lists call with their own code and
label. The playground's messages and codes stay byte-equal, so no existing test text moves.
`editor`:
- The entry takes an optional `editor`. When present, it is an object with exactly the keys `name` and
`email` (via `only()`). Each value is a string that is non-empty after trim and has no control
character (`[\u0000-\u001f\u007f-\u009f]`), `<` or `>` (webby-0169 R1: git drops them from an ident
and commits under a different name, so C2-02 would never see a failure). Anything else refuses
`library-mapping-invalid`.
- The output carries `editor: { name, email }` only when present, as `user` and `vaultPrefix` do. A save
with no `editor` refusing is C2-02's job.
## 2. Scope (scope.mjs)
- `classify` gets a supporting loop before the playground loop (design 1.2). It returns `place(...)`
with `{ scope: 'supporting', universe, book }`. The book is the book folder holding the prefix, or
null at universe level and in a draft. The type and folder come from the first folder under the
prefix, as for the playground.
- The `.md` check stays first, so a `.canvas` or `.base` under a supporting prefix stays unmapped.
- The comment above `classify` names the three prefix scopes and the order.
## 3. supportingPath (new supporting.mjs)
`supportingPath(mapping, universe, path, tree)` returns `path` or throws
`LibraryError('library-write-story-path', ...)`. It is pure and reads no git.
- `tree` is the array `listTree` returns (reader.mjs:26, `{ mode, type, oid, size, path }`), so the
C2-02 broker passes its listing as it is (Q3). A non-array `tree` throws a TypeError, because that is
a caller bug, not a path class.
- The checks run in this order, each with its own message naming the class:
1. The path must be a string of at most 1,024 UTF-8 bytes (`Buffer.byteLength`).
2. It must have no control character, using the same control class as `editor` (not `<` or `>`).
3. It must have no backslash.
4. It must not be absolute (a leading `/`).
5. It must have no empty, `.` or `..` segment. That also catches `//` and a trailing `/`.
6. `classify(mapping, path)` must give scope `supporting` and that same `universe`. This one check
covers an unknown universe, an unmapped path, a `.canvas`, `.base` or attachment, a canon or
story path, a playground path that isn't also supporting, and another universe's prefix. The scope
rule then has one source, and M1 below hits both.
7. Every proper prefix of the path (`a`, `a/b`, ...) and the path itself must not be in `tree` with
mode `120000` (symlink) or `160000` (gitlink).
8. No prefix of the path, and not the path itself, may equal a tree path or a tree directory
(derived from the entries' paths) after `normalize('NFC').toLowerCase()` on both sides, unless
it equals that tree path exactly. NFC as well as case, because APFS treats NFC and NFD forms of
a name as one file (webby-0169 N1).
Directories are checked too, because the folders under a supporting prefix are hers to name. A
path in a tree that holds both `a.md` and `A.md` refuses, which matches R7.
- The refusal messages quote the path with JSON, except for the control-character class, whose message
leaves the path out.
- The delete rule ("A delete also needs the path to exist at the tip as a 100644 blob", M12) isn't in
the brief's supportingPath list. It stays with C2-02's delete (Q4).
The C2-02 brief doesn't name it (only design :308), so BUILD.md carries it to C2-02 under Handoffs
(webby-0169 Q4).
- index.mjs exports `supportingPath`.
## 4. README (packages/library/README.md)
- Mapping: `supporting` and its rules, the cross-list rules, and `editor`.
- Refusals: a `library-supporting-other-universe` row, and the new cases in the
`library-mapping-invalid` row.
- Scope: supporting first, then playground, then canon.
- A new section, "Agent write paths", for supportingPath and its classes.
## 5. Callers of classify outside files owned (Q1 for Sage)
- survey.mjs:65 builds `{ canon: 0, playground: 0, unmapped: 0 }` and increments
`scopes[classify(...).scope]`. A supporting file adds `supporting: NaN`, and formatSurvey (:92) never
prints it. I propose a departure: add `supporting: 0` and print it, so the line becomes
`scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6`. That re-pins survey.test:73 and
adds a count test. Without it, the survey reports garbage once she marks a folder. The departure
covers two files, packages/library/src/survey.mjs and packages/library/tests/survey.test.mjs.
- No change in the rest, each for a reason:
- reader.mjs:183 skips only unmapped, so supporting files are indexed like playground files.
- R5 (:207) flags only canon to playground, as design 1.3 R5 says. A canon link to a supporting file
raises nothing.
- render.mjs:23 gives supporting a null badge. The supporting badge belongs to C2-10b (design :1415).
- mirror.mjs:246 refuses only unmapped. Reading supporting files by scope is C2-05c's.
- No bus or webui source filters on scope (git grep at 94c33a43c).
- M9 already protects the whole `projects` key from agent edits (library-projects-protected.test). No
test is added there.
## 6. Tests
- mapping.test:
- T1: `supporting` at book level, at universe level, in a hybrid and in a draft. The output carries
it, with `[]` when absent. The draft `deepEqual` at :48 gains `supporting: []`.
- T2: one test per prefix rule.
- These refuse `library-mapping-invalid`: no trailing `/`, a bad folder, one holding another, one
listed twice, one equal to its own book, one holding its own book, one holding a playground
prefix, and one overlapping another entry's playground.
- These refuse `library-supporting-other-universe`: one inside another universe's book, and one
holding another universe's book.
- An equal same-entry playground passes.
- T3: `editor` passes, and absent passes. It refuses `library-mapping-invalid` for a non-object, an
array, an extra key, a missing key, an empty or blank value, a non-string, and a control character,
`<` or `>` in either value.
- scope.test, T4:
- Supporting at book level, universe level and in a draft each give their universe and book.
- A folder in both lists is `supporting`, and a playground-only folder is `playground`.
- A supporting `.canvas` or `.base` is unmapped.
- supporting.test (new):
- T5 passes at book level, universe level, in a draft, in a both-lists folder, and at exactly 1,024 bytes.
- T6 has one refusal per class against a fixture tree with a symlink, a gitlink and a case variant.
The classes are:
- unmapped, `.canvas`, `.base`, an attachment, canon, playground-only, another universe's
supporting prefix, and an unknown universe;
- absolute, `..`, `.`, `//`, a trailing `/`, a backslash, a C0 control and DEL;
- a symlink folder, a gitlink folder, and a symlink at the path itself;
- a case-variant file, a case-variant folder, and an NFD-form variant of an NFC tree name;
- 1,025 bytes made with a multi-byte character, so bytes and characters differ.
- Each asserts the code `library-write-story-path`.
- business.test: T7 adds one assertion that `validateBusinessDocument` refuses
`library-supporting-other-universe`.
- survey.test (only if Q1 is approved): T8 counts one supporting file, and :73 is re-pinned.
## 7. Mutants (exact strings go in BUILD.md)
| Mutant | Change | Killed by |
|---|---|---|
| M1 | classify checks playground before supporting (the brief's mutant) | T4 both-lists, T5 both-lists |
| M2 | the other-universe fail for supporting is dropped | T2 |
| M3 | the symlink and gitlink check is dropped | T6 |
| M4 | the case check is dropped | T6 |
| M4b | check 8 drops `normalize('NFC')` | T6 NFD variant |
| M5 | the byte length uses `path.length` | T6 1,025 bytes |
| M6 | the `editor` control-character check is dropped | T3 |
| M6b | the `editor` `<` and `>` check is dropped | T3 |
| M7 | supportingPath skips the universe match | T6 other universe |
| M8 | the supporting-holds-playground check is dropped | T2 |
## 8. Gate
- Webby approves the manifest.
- The library and business suites and every scripts/test-*.sh are green on Sage's gate.
- Timeouts (mos-1275): each package `timeout -k 10 1800`, each script `timeout -k 10 300`, each single
file and mutant `timeout -k 10 600`. These come from my row 256 gate at 10:34Z.
- Before in-review, grep packages/*/tests for 64-hex pins on the changed files and for the changed
message text, then drift-check BASE..refactor.
## 9. Questions
- Q1 (Sage): approve the survey.mjs and survey.test.mjs departure in section 5?
- Q2 (Webby): the cross-list rules in section 1?
- Q3 (Webby): `tree` as the `listTree` array?
- Q4 (Webby): the delete's 100644 check stays with C2-02?
- Q5 (Webby): `editor` refuses only blank values and control characters, as the brief says. `<` and `>`
would break a git ident. I'd leave them to C2-02's commit step unless you want them refused here.
## 10. Draft 2 (webby-0169)
- R1 folded: `editor` refuses `<` and `>` too, with T3 cases and mutant M6b.
- Q2 accepted. BUILD.md records the tradeoff: refusing a supporting prefix that holds a playground
prefix goes past the brief, but it is the fail-closed reading and uses an existing code. The cost
is that she can't nest a playground under a supporting folder, though she can nest the other way.
- Q3 accepted. Q4: carried to C2-02 under Handoffs in BUILD.md, and Sage was asked to add it to C2-02's brief.
- N1 folded into check 8 (NFC and case), with an NFD case in T6 and mutant M4b.
- N2: the survey.test:73 pin gains only `supporting`, and the count test is the only addition
(mos-1330, its own commit).
- N3: before the build, re-grep tests outside packages/library for a `deepEqual` of the mapping output
on the build base.
-8
View File
@@ -1,8 +0,0 @@
# Lena
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 296 (FED R7, handed over from Quackerjack).
No launcher in this directory. The seat runs as a T3 thread (thread db27faca) and works in its own worktree. It has no
credentials of its own; it pushes only its own `lena/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-92
View File
@@ -1,92 +0,0 @@
# Row 298 (#1804, Q228): the chief-of-staff grant scope
Owner Megavolt, reviewer Dewey. Assigned in sage-3584 (mos-1489), 2026-10-23 18:53Z.
Base: origin/refactor 45dca69f6. Branch: megavolt/row298.
Brief: docs/plans/2026-10-23_mvp-readiness-rows.md, "Brief: the chief-of-staff grant scope (Q228)".
Decision: DECISIONS Q228 (jason-master-restart DECISIONS.md:2278). It replaces Mos's Q190 reading.
## What exists at 45dca69f6 (broker.mjs)
- Raise: `#raiseGrant` (:2558) is chief-only (`grant-chief-only`) and binds a grant body per method:
gitea `#giteaGrantBinding` (:2665), host `#hostGrantBinding` (:2599), or host-enroll `enrollBinding`.
The decision goes through `#dispatch(..., 'decision.raise', ...)`, routed to the human.
- Approval: `#approvedGrant` (:1525) returns the binding only for a credential.mint routed to the
human and closed with `approvalChoice` by the business's human, via 'cli', or via 'console' with
the broker's own human.input (row 252).
- Start: decision.resolve (:5653-5659) runs `#approvedGrant` after the close and, once the transaction
commits, starts `#runEnrollment`, `#runHostGrant` or `#startGitea` with setImmediate.
- Spend and records: each executor calls `#consumeGrant` and writes the 236 records (grant.call,
grant.result, grant.step, grant.applied or grant.failed, gitea_grants or host_grants). Revoke
goes through grants.revoke and hosts.revoke, as before.
- Human-only settings: `#human(s)` (cli, outsideAgent) plus `#mayHuman`, as in veto.set (:4713).
The organization file is checked whole by business.mjs `checkLaunch`, and a bad file refuses
every read.
## Proposed design (waits on Sage's rulings, Q1-Q6 below)
D1. Storage. The scope gets its own key, `launch.grantScope`, in the organization file. The
whole-file check doesn't refuse on it. A separate reader, `grantScope(document)`, returns the
parsed scope or, when the key is missing or bad, the empty scope `{}`, plus a `fallback` flag.
A bad value can't break every organization read, and it falls back to "everything needs
Approve", which is the fail-closed direction. (Q1)
D2. Shape. `{ gitea?: { agents: [id], projects: [id] }, host?: { agents: [id], hosts: [id],
commands: [[argv prefix]] } }`. A request is in scope only if its method has an entry, its agent
is listed, and every target is listed: each gitea project, the host, and each command prefix
equal to a listed prefix. host-enroll is never in scope, because it adds a host to the registry.
(Q2)
D3. Writer. A new verb, `grants.scope.set { revision, scope }`, CLI-only for the human: `#human(s)`,
then `#mayHuman`, with the verb added to LIFECYCLE_HUMAN_VERBS. The tester, the reader and every
agent are refused. organization.set's ORGANIZATION_KEYS don't include it, so the chief's tool
never reaches it, the same way instances.set and veto.set stay human-only. The write validates
strictly and refuses a bad value; only the read falls back. Receipt: `grants.scope` with the old
and new sha256. `grants.scope` reads the value for the human, the tester and the chief. (Q3)
D4. In-scope raise. Inside the raise transaction, `#raiseGrant` checks the binding against the scope
and, if it's in scope, closes the new decision at once: op resolved, choice approvalChoice, by
`grant-scope`, via `scope`. It writes a `grant.scope.used` event carrying the scope sha256 and
revision. `#approvedGrant` accepts via 'scope' only when that event exists for the close's seq
and its sha256 matches the scope as read in the same transaction. After the commit, the same
setImmediate starts the same executor, so 236's spend, records and revoke are unchanged. The
decision is closed before any inbox read, so no inbox item ever shows. (Q4)
D5. Out-of-scope raise: unchanged. The decision stays open, routed to the human, for the Console
and CLI Approve.
D6. Executor recheck. At the spend, `#consumeGrant` reruns the scope check for a via-'scope'
approval. If the scope changed between raise and run and the grant is now out of scope, it
fails with `grant.failed scope-changed` and the grant is spent. (Q5)
## Open questions for Sage
Q1. Storage: `launch.grantScope` in the organization file with a lenient reader (D1), or a separate
file under the business directory?
Q2. Shape: is per-method agent, project, host and command lists right? Should host-enroll stay
out of scope always?
Q3. "A scope change needs Approve": is the human's CLI verb itself the Approve (D3)? Or must the
chief also be able to raise a scope change that Jason approves in the inbox (a new decision
action, more code)? I recommend D3 alone. Should Console get a scope page in this row? I
propose no.
Q4. Recording an in-scope grant as a broker close via 'scope' with a scope event (D4), versus a
decision that is never raised (a new table). D4 keeps 236's records and revoke unchanged.
Q5. Scope narrowed between raise and run: fail scope-changed (D6), or honor the raise-time check?
Q6. Chief enrollment ("Jason's Approve stays for the chief of staff's enrollment"): I read this
as the chief's own instance and credentials, which the human's instances.set and the
credential lifecycle already cover. So nothing new is needed in this row. Agree?
## Tests (access by brief; all on scratch stores, fake Gitea, fake host)
T1 agent, chief and tester callers of grants.scope.set are refused, and organization.set with
grantScope is refused. T2 a bad or missing grantScope falls back to empty and the grant waits.
T3 an in-scope gitea request issues with no open decision and no inbox item, and writes the 236
records. T4 the same for a host request. T5 out of scope by agent, by project, or by command
waits for Approve. T6 host-enroll always waits. T7 a scope change by the human's CLI writes a
receipt; a scope change by any other path is refused. T8 an in-scope grant is logged and revoked
with grants.revoke or hosts.revoke. T9 scope narrowed before run gives scope-changed.
## Mutants (brief)
M1 the scope check skipped (every chief raise auto-closes): killed by T5 and T6.
M2 the fallback skipped (a bad value is used as-is or throws): killed by T2.
M3 the `#human` check removed from grants.scope.set: killed by T1.
## Holds
Darkwing holds the slot: no push, no claim, no queue write, no node and no test run until Sage
says so. Q188: no agent creates a grant; tests use scratch stores only.
+177
View File
@@ -0,0 +1,177 @@
# Row 225 (C2-10b) Console chat: replies, citations, write notices, status line
Owner: Morgana, from sage-3582 (18:53Z 10-23). Base: origin/refactor 45dca69f6. Branch: morgana/row225.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-10b. Design: agents/gosalyn/work/case2/DESIGN.md 7.4,
5.2 (the reply schema and the check result) and the write notice and `library.revert` (about line 286).
## Ruling on the dependency gap (sage-3591)
225 builds against the design contracts. `renderReply` stays pure, and the caller supplies the answers
it can't know. The revert route proxies to the bus verb and shows the refusal code until the verb
exists. Landing order: 225 lands after 217 (C2-06), not after 288 (C2-02). Sage adds `after 217` to
row 225. Before 288 lands, the Revert button gets the bus's refusal and shows its code, as the brief
allows.
## A1. The check result I assume (to 217, Launchpad)
The broker stores one check result with each agent-to-human message the guard passes. The Console reads
it on the message row that `/api/chat/threads/<id>/messages` returns, under the key `check`:
```json
{
"check": {
"citations": [
{ "scope": "canon", "agent": null },
{ "scope": "supporting", "agent": "acme/scribe" },
{ "scope": "web", "agent": null }
]
}
}
```
- `check` is `null` or absent on a message the guard didn't check: a human's message, a free-text reply,
a notice, or a message from before 217.
- `check.citations` has one entry per entry of the body's `citations`, in the same order.
- `scope` is one of `canon`, `playground`, `supporting` and `web`. A url cite is `web`.
- `agent` is the `Mosaic-Agent` trailer value, `<business>/<instance>`, from
`git log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>`, or `null` when the
file's last commit at `commit` carries none. A url cite has `agent: null`. The Console shows the
agent-written label when `agent` is a nonempty string, and names the agent.
The Console trusts nothing in the body for scope or label. A cite with no matching check entry renders
with no badge and no label. It doesn't fall back to a scope it worked out itself.
If 217 uses other names, I change mine to theirs before either of us pushes (sage-3591 item 1).
## A2. The revert verb I assume (to 288, Honker)
- Verb: `library.revert`. A human verb (design M4): from an agent session it refuses `human-required`,
like `library.status` (`broker.mjs:5133`).
- Args: `{ "project": <business project name>, "commit": <40 lowercase hex> }`. The design writes
`library.revert {commit}`. `project` is added to match the other library verbs, and like theirs it is the
key of `business.projects` (`mirror.mjs:31`), which is the chat project's `repo` link, not its UUID. Both are required, and no other key
is allowed (`invalid-request`), as the other library verbs check with `keys(args, ...)`.
- Result: `{ "project", "path", "reverted": <the commit given>, "commit": <the new commit> }`.
- Refusal codes the Console shows by name:
- `library-revert-not-agent`: `commit` has no `Mosaic-Agent` trailer for this business, or isn't
reachable from the served commit.
- `stale-base`: a later commit changed that path.
- `human-required`, `invalid-request`, `project-not-configured`.
- Before 288 lands, the bus refuses the verb (expected `unknown-verb`, `broker.mjs:6578`), and the Console
shows that code.
- HTTP status in the Console (`packages/webui/src/bus.mjs` `busStatus`): `stale-base` and
`library-revert-not-agent` map to 409. Every other code keeps the existing mapping.
## A3. The status line
`GET /api/library/status?project=<id>` proxies `library.status {project}` through the existing
`libraryStatus` option. `cli.mjs:110` already passes that option, but `startServer` doesn't read it.
The status line shows:
- the served commit (7 characters) and its age;
- `library-diverged` when the sync state names it;
- the fetch error code;
- the reply limits in force, and the keys that fell back;
- the count of R-problems, linking to the list.
It reads the fields `readSyncState` returns at the base (`packages/library/src/mirror.mjs:232`). It shows
any field 217 adds (the limits) only when present.
## A4. "Changed since" (ruled sage-3596)
Sage ruled that 217 supplies `last`. On a `thread.messages` row that carries `check`, each path cite's
entry gets `last`, the file's last commit at the served tip, read when the message is read. When `last`
is absent the Console marks nothing changed, and a test pins that.
One more field is needed, raised in morgana-0200. The cited `commit` is a library commit, not the
file's own last commit, so a cite made at a commit that didn't touch the file would differ from `last`
while the file is unchanged. I asked 217 to also store `file`, the file's last commit at the cited
commit. That's `%H` from the same `git log -1 <commit> -- <path>` call that reads the trailer, so it
costs no extra git call. The Console marks a cite changed when both `file` and `last` are 40-hex and
differ. The cited commit is reachable from the served one (5.2), so `file` is an ancestor of `last`,
and differing means newer. A missing or malformed `file` or `last` marks nothing.
The entry the Console reads, in full: `{ scope, agent, file?, last? }`.
## A5. `no-cited-answer` retires
`renderItems` and its `NOTICES` (`no-cited-answer`, `no-answer`, `turn-failed`) go. 217 sends the
runner's fixed texts as plain bodies (design 5.2, "Runner texts"), and renderReply shows those as free
text. Design 7.4 names no other notice than `library-write`, so nothing is lost (sage-3596 item 3).
## Files
Both deviations below were accepted in sage-3596 and are named in the manifest for Dewey. The brief owns these:
- `packages/library/src/render.mjs`: `renderReply` replaces `renderItems`.
- `packages/library/src/index.mjs`: the export.
- `packages/library/README.md`: the section.
- `packages/library/tests/render.test.mjs`.
It also owns `bus.js`, its stylesheet, `serve.mjs` (only the two routes) and new webui tests. Two
deviations, both recorded here:
- The chat view's message list is `packages/webui/src/public/chat.js:85` (`window.MosaicChat`), not
`bus.js`. The reply, notice and Revert rendering goes in `chat.js` and `chat.css`. The status line
goes in the chat thread view, so it is also in `chat.js`.
- `packages/webui/src/console-bus.mjs` and `cli.mjs` each get one `libraryRevert` line, beside the
existing `libraryStatus` wiring.
## Overlap with row 220 (C2-10a, Researcher, 3ef6da90d)
220 changes one line each in the webui tests discord.test.mjs, fixture.mjs, history-fixture.mjs,
relaunch.test.mjs and return-flow.test.mjs. Its other paths are in control-board, conversation, discord
and business. 225 doesn't touch those five files or those packages. Its webui tests are new files.
## Handoff to Fethry (mos-1501, Q245)
The row moved to Fethry (Codex, fethry/row225). Everything below is **untested**: no suite or single file
has run on it, since a run needs Sage's CLEAR. Each changed file passes `node --check`.
Done in the handoff commit:
- `render.mjs` `renderReply(body, {check, changed, name})`; index export; README section;
`render.test.mjs` rewritten. `renderItems` is gone from every package.
- `serve.mjs`: `/render.mjs` static entry (owner and user file maps), `GET /api/library/status`
(one `project` query, answer must name that project), `POST /api/library/revert` (save's boundary,
strict `{project, commit}`, refusal shows the code, lost or unusable answer is `outcome-unknown`,
never retried). Both are nulled for a granted user, and `userRoute` keeps them owner-only. The tester
listener refuses `libraryRevert`.
- `bus.mjs`: `library-revert-not-agent: 409`. That re-pins `packages/webui/src/bus.mjs` in
`packages/bus/tests/channel-proof.test.mjs`, with a comment line, as row 254 did. It's a third deviation,
in another package's test.
- `console-bus.mjs` and `cli.mjs`: `libraryRevert`, beside `libraryStatus`.
- `chat.js`: dynamic `import('/render.mjs')` (CSP is `script-src 'self'`); body text until it loads.
Cites with a scope badge, the agent-written label, "changed since" from `file`/`last`, links only for
https with `rel="noopener noreferrer"`. A notice gets a Revert button behind `confirm()`. The library
status line goes under the thread heading.
- `chat.css`: cite, badge, notice and status styles.
- New tests: `packages/webui/tests/library-routes.test.mjs` and
`packages/webui/tests/chat-replies-browser.test.mjs`. 220's five files are untouched.
Decisions made in the build:
- The library name is the chat project's `repo`. A project with no repo reads no status and its
Revert does nothing.
- These codes hide the status line: `project-not-declared`, `library-mapping-invalid`,
`project-not-configured`, `not-configured` and `owner-only`.
- Otherwise every code-repo thread, and every granted user, would show "unavailable".
- Cost: a broken library mapping also hides the line. `library.status` from the CLI still shows it.
- "Age" is the time since `lastFetch` ("fetched 5 min ago"), not the served commit's age, which the
status doesn't carry.
- The limits fields are assumed to be `replyLimits {requireCitation, citationsPerReply}` and
`replyLimitsFallback [keys]`, and are shown only when present. **Not agreed with 217.** Confirm with
Beakley and rename.
- A notice body's shape is all the renderer checks. An agent in a business without a library could
send a body that looks like a notice. The page would show it, but `library.revert` still refuses it.
Left:
1. Get CLEAR and run the webui and library suites, `channel-proof.test.mjs`, and the two new files whole.
The browser test's selectors and waits have never run.
2. Agree the limits field names with 217 (Beakley).
3. Granted-user R6 G5 (`granted-user.test.mjs:252`) lists owner verbs by hand. Add `library.revert`
once 288 lands.
4. BUILD.md: the mutant list and the mos-1275 timeout plan.
5. The manifest names the three deviations: chat.js and chat.css in place of bus.js, the console-bus and
cli lines, and the channel-proof re-pin.
-237
View File
@@ -1,237 +0,0 @@
# Row 216 build notes (C2-05a, lookup index and queries, #1717)
Phase A only: code, tests and this file. No test, suite or mutant has been run. `node --check` passed on every changed `.mjs`.
- Branch: `researcher/row216`
- Base: `d372102882d6858fa77af4aee453f866c886ad5c` (origin/refactor)
- Design: `agents/gosalyn/work/case2/DESIGN.md` on origin/gosalyn/case2-design, section 3 (table at lines 625-631, bullets 633-641), 1.2-1.4, B12, D2
- Brief: `docs/plans/2026-10-20_case2-rows.md`, C2-05a at lines 320-357
## Files
| Path | Status |
|---|---|
| `packages/library/src/index-build.mjs` | new |
| `packages/library/src/query.mjs` | new |
| `packages/library/src/index.mjs` | edit: 2 export lines added |
| `packages/library/tests/query-fixture.mjs` | new, test helper |
| `packages/library/tests/query.test.mjs` | new |
| `packages/library/tests/index-build.test.mjs` | new |
| `agents/researcher/work/queue-216/BUILD.md` | new (this file) |
No existing test or source file changed beyond `index.mjs`.
## Design rows and where each is tested
| Design row | Code | Tests (query.test.mjs unless noted) |
|---|---|---|
| `library_find` | `find` | find is bound to its universe; every result carries its scope; long list pages; large items page under the cap |
| `library_get` | `get` | hybrid gets a source file; get lists visible links both ways; get pages its lines |
| `library_search` | `search` | search returns scoped line hits; item that cannot fit alone is refused |
| `library_manuscript` | `manuscript` | one chapter at most 400 lines; refused without chapter; hybrid reads a source chapter |
| `library_universe` | `universeInfo` | universe info reports a hybrid's sources, constraints, notes; unknown universe refused |
| Hybrid semantics (1.4) | `sees` | a hybrid sees its sources canon minus exclusions, no playground |
| Unmapped never appears | index build + `sees` | unmapped files never appear (query.test); index holds mapped files only (index-build.test) |
| Per-commit cache (3) | `indexFor` | same commit, path, mapping return one index; different path, mapping or commit get their own; eight-entry bound; failed read not kept; unchecked mapping refused (index-build.test) |
| Rebuild time (3, C2-05a gate) | `indexFor` | 3,000-file rebuild, time reported with `t.diagnostic`, no threshold asserted |
| REPLY_CAP parity | `QUERY_REPLY_CAP` | equals `REPLY_CAP` from harness tools.mjs |
## Choices where the design table is silent or differs
These are departures or gaps. Each is built as stated, so Sage can rule before Phase B. A ruling that changes one of them is a small edit.
1. **Paging arguments (gap a).** The table has no offset. `find` and `search` take an optional `offset` (a position in the filtered list, default 0). A reply carries `next`: the position of the next item, or `null`.
2. **`library_get` has no universe (gap b).** Built with a required `universe`. Without it the reply could not be isolated. A missing file and a file the universe cannot see both return `null`, so a caller cannot tell them apart. `get` also takes an optional `from` line.
3. **`get` over the cap (gap c).** Lines page by `from` with `next`. `links_out` and `links_in` come back whole on every page. If the frontmatter and links alone exceed the cap, the call is refused with `query-item-too-large`. A hub note with many backlinks may be refused. This needs a ruling: link paging, or accept the refusal.
4. **`manuscript` without a chapter (gap d).** Refused with `query-chapter-required`. The design names no list form. A chapter name that matches two visible files is refused with `query-chapter-ambiguous`.
5. **`search` excerpt (gap e).** The excerpt is the whole line, not a truncation. A line that alone exceeds the cap is refused. Truncating it would cut an item.
6. **`exclude` form (gap f).** `checkMapping` accepts only a folder path for `exclude` (`folder()`), so an exclusion is a path prefix. There is no type or source form to build. Confirm.
7. **Pin constraints.** `pin` does not hide any file. It is reported by `universeInfo` only. Confirm.
8. **Playground through `get`.** `scope` applies to lists. `get` by an explicit path returns a playground file of the caller's own universe. Confirm.
9. **Links.** A link into a file the universe cannot see is dropped from `links_out` and `links_in`, in line with N1. Links from a visible file that point into another universe are kept in the index (the reader keeps them under R6), and the query layer drops them for universes that cannot see them.
10. **`find` matching.** Case-insensitive substring over the front matter `name` and the file name. An empty `query` matches every file in scope.
11. **Cache key.** `repo`, `commit` and a sha256 of the canonical mapping. Keying by commit alone would return the wrong scopes after a mapping change. The cache holds eight entries, least recently used out. Failed reads are not kept.
12. **`universeInfo`.** Reads only the checked mapping and the served commit. It builds no index.
13. **Reply commit.** Every reply carries the index commit, which is the served commit passed in.
## Provisional refusal codes (pending Sage approval)
The brief says new refusal codes need Sage's approval first. These are provisional and must not be treated as settled:
- `query-argument-invalid`
- `query-universe-unknown`
- `query-chapter-required`
- `query-chapter-ambiguous`
- `query-item-too-large`
Reused, existing codes: `library-mapping-invalid`, plus the reader's `library-git`, `library-commit` through `indexFor`.
## Grep results (grep only, not a test run)
- Test references to the changed paths: `packages/library/tests/*.mjs` import `../src/index.mjs` and `../src/mapping.mjs`. `packages/bus/tests/library.test.mjs` and `library-sync.test.mjs` import the library index as a namespace. Neither enumerates its exports, so the two new export lines break no assertion.
- `packages/cli/tests/stack-copy.test.mjs:52` checks that `packages/library/src/index.mjs` exists, and it still does.
- 64-hex pins: none in `packages/library/tests`, none on `index.mjs`, `query.mjs` or `index-build.mjs`.
- `scripts/test-*.sh`: no reference to the new modules.
## Flag for Sage
- `packages/library/tests/helpers.mjs` `repo()` builds its fixtures under `tmpdir()`, which is `/tmp`. The launcher rules forbid that. The new tests do not use it; they use `tests/query-fixture.mjs`, which builds under `/mnt/storage/scratch/tmp`. The existing helper should be fixed in a separate row, not here.
## Phase B commands (not run)
Run only after Sage sends "slot clear". Set the scratch TMPDIR first, per the launcher rule.
```
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-aa3cc98787bc5152c/packages/library
export TMPDIR=/mnt/storage/scratch/tmp
node --test tests/query.test.mjs tests/index-build.test.mjs
node --test tests/*.test.mjs
```
Also run `packages/bus/tests/library.test.mjs` and `library-sync.test.mjs`, since they import the index.
## Status
Not yet verified. Nothing has been tested and nothing is committed. The working tree holds the Phase A changes. AGENTS.md invariant 8 allows a commit only after the applicable suites are green, so the commit waits for Phase B. The dispatch asked for a local commit in Phase A; Sage to confirm that the commit waits for Phase B.
## Round 2 (2026-10-22, sage-2708, sage-2716): rulings applied, rebased onto aca16404c
Added after the text above; nothing above is edited. "Nothing is committed", "Provisional refusal codes",
`query-chapter-required` and the tmpdir flag above were true when written and are superseded here.
- Branch `researcher/row216-r2`, one local commit on origin/refactor `aca16404c`. Row 268 (C2-01c) is in
that base. Row 253 has not landed; the work rebases onto 253's tip before any gate. Not pushed, not run.
- Rulings: `agents/sage/work/queue-216/RULINGS.md` at da9555ca2, rulings 1-13 and the tmpdir flag.
- Drift check, d37210288..aca16404c against my paths: the library files that changed are `mapping.mjs`,
`scope.mjs`, `README.md` and the mapping and scope tests. None is a file I add or edit. `query.mjs` reads
the checked mapping through them, so the whole library package runs in phase B.
### What changed from phase A
| Ruling | Change in `query.mjs` | Test |
|---|---|---|
| 3 `get` over the cap | `links_out` and `links_in` page together with `links_from`; every reply carries `links_next`, `next` and the front matter. A page with lines left carries at least one line. Links take at most half the cap while lines remain. `query-item-too-large` is left for front matter alone or one line alone (or one link alone) | hub note with 900 backlinks pages and adds up; front matter alone and a line alone refused; bad `links_from` |
| 4 `manuscript` no chapter | `chapter` optional: the first visible chapter in path order. Every reply carries `next_chapter`. `query-chapter-required` removed. Ambiguity still refuses | first chapter and `next_chapter`; ambiguous name refused |
| 5 `search` long line | hit comes back `{path, line, excerpt: null, excerpt_too_long: true, scope}`, other hits still return | marked hit and unmarked hits in one reply |
| 6 `exclude` | path-prefix reading only. The test names `EXCLUSION` and `EXCLUDED_PATHS` in `query-fixture.mjs`, so C2-15 (row 267) swaps two constants | exclusion hides from the hybrid and not from the source |
| 8 hybrid `get` | unchanged code; test added | hybrid gets a source's playground file as `null`; the source gets it with `scope: "playground"` |
| PRD 1.14 (row 268) | `universeInfo` reports `kind` from the mapping (`draft` included) | four tests below |
PRD 1.14 tests, one each: a lookup naming a `book` leaves `book: null` files out (find and search);
`library_manuscript` never reads a `book: null` file; a draft universe has no canon, so its lookups return
files only with `scope: "playground"`; `library_universe` on a draft returns kind, name and prefixes.
The levels fixture is `WITH_LEVELS` in `query.test.mjs`: harbor gets `Notes/Harbor/` as a universe-level
playground prefix, and `idea` is a draft over `Drafts/New World/`.
`query-fixture.mjs` builds under `tmpdir()`, as `helpers.mjs` does (ruling "tmpdir flag"). The earlier
flag about `helpers.mjs` building under `/tmp` is closed by that ruling: the gate sets `TMPDIR`.
Refusal codes now: `query-argument-invalid`, `query-universe-unknown`, `query-chapter-ambiguous`,
`query-item-too-large`. All four approved in the rulings.
### Grep results (grep only, not a test run)
- `git grep` over `packages/*/tests/*` and `scripts/*` for `query.mjs` and `index-build`: no hit outside
`packages/library/tests/query*.test.mjs` and `index-build.test.mjs`.
- 64-hex pins naming a library path: none. Positive control: the same pathspec finds the sha256 lines in
`packages/cli/tests/case1-hostcheck.test.mjs` and the `packages/library/src/index.mjs` existence check at
`packages/cli/tests/stack-copy.test.mjs:52`.
- Tests that read `packages/library/src` as a directory: `packages/cli/tests/stack-release-fixture.mjs:309`
copies `packages/library/src` whole into a release fixture. It carries no file list, so two new files break
no assertion. `stack-release.test.mjs` is not in the gate unless Sage wants it.
- Tests that import the library index as a namespace: `packages/bus/tests/library.test.mjs` and
`library-sync.test.mjs`. Neither enumerates exports, so the two export lines add no break. Both run
whole in phase B.
### Run limits (fixed before any run)
Every node file runs as its own `setsid -w timeout -k 10 LIMIT node --test FILE`; nothing runs unbounded.
- Node test files: LIMIT 900 s. The library test files have no recorded time. 900 s is above the longest
recorded whole-package run I know of (cli, 591 s, row 281 BUILD.md "Runs" step 3), so it holds the 3x rule
for any single file. The first run records each file's time, and a file past 300 s gets a limit re-derived
from it before the next run. `query.test.mjs` builds a 900-file repo and a 3,000-file index rebuild is in
`index-build.test.mjs`; both are the files most likely to need it.
- No shell script is in this row's gate, so no script limit applies.
Any timeout goes to Sage, then Mos, unplaced (mos-1275).
### Phase B (not run; Sage's CLEAR first, then a rebase onto 253's tip)
```
export TMPDIR=/mnt/storage/scratch/tmp
cd <worktree>/packages/library
for f in tests/*.test.mjs; do setsid -w timeout -k 10 900 node --test "$f"; done
cd ../bus
for f in tests/library.test.mjs tests/library-sync.test.mjs; do setsid -w timeout -k 10 900 node --test "$f"; done
cd ../cli
setsid -w timeout -k 10 900 node --test tests/stack-copy.test.mjs
```
Mutants I propose (my list, not read off the brief; each is meant to be killed by the named test, and none
has been thrown; the kill claims are unverified until CLEAR):
- M-a: `sees()` returns true for a file of another universe: killed by "find is bound to its universe".
- M-b: the playground filter dropped: killed by "playground appears only when asked for".
- M-c: the hybrid reads a source's playground: killed by "a hybrid gets a source playground file as null".
- M-d: `links_next` always null: killed by the hub paging test.
- M-e: the book filter lets `book: null` through: killed by the PRD 1.14 book test.
### Status
Built, not run. `node --check` has not been run on this round's files (no node before the CLEAR); the
phase A pass above covered the earlier text only. The commit is local and waits for no gate under Sage's
sage-2716 instruction, which allows git and docs work only; push and claim wait for Sage's go.
## Round 3: rebase onto 45dca69f6, drift check, hash-pin grep (2026-10-23T18:53:43Z, sage-3576)
Appended; nothing above is edited. Git only; nothing run, nothing pushed. Branch researcher/row216-r4 holds the
one row 216 commit on 45dca69f6 (origin/refactor, 10-23). Earlier base: 48e450ce8 (row 253).
- Rebase: clean. `git range-diff 48e450ce8..038089e5f 45dca69f6..researcher/row216-r4` shows the commit as `=`.
- Landed in 48e450ce8..45dca69f6: 1175 files. Path overlap with this row's 7 paths: none. packages/library: no
landed file. packages/harness/src/tools.mjs (imported by query-fixture.mjs or the tests): not landed.
- Hash-pin grep (the item 216 owed): 64-hex in packages/library: none. Positive control: 364 files in
packages/*/tests and scripts match 64-hex. Tests and scripts that name library files by path:
bus/tests/library.test.mjs and library-sync.test.mjs and bus/src/runtime.mjs import library/src/index.mjs;
business/src/business.mjs and its test import library/src/mapping.mjs (unchanged by this row);
cli/tests/stack-copy.test.mjs, stack-release-fixture.mjs:310 and stack-release.test.mjs:1991 copy
packages/library/src as a tree; scripts/stack-copy.sh:37 copies it; scripts/mosaic:41 runs survey.mjs. None pins
a hash of index.mjs, index-build.mjs or query.mjs.
- bus/src/runtime.mjs landed changes after 48e450ce8 and uses libraryModule by named members (ensureMirror, SLOT,
readLibraryFile, libraryStatus, sync, LibraryError, SYNC_INTERVAL_MS) only; the landed diff touches none of them,
and this row's two new index.mjs exports (query and index build) are not iterated by any caller.
- Gate additions for phase B: run bus library.test.mjs and library-sync.test.mjs whole (they import index.mjs), and
the cli stack-copy and stack-release tests that copy packages/library/src.
## Round 4: phase B on 4e5d28ab2, two test fixes for r5 (2026-10-23T20:20:51Z, sage-3702, mos-1528)
Appended; nothing above is edited.
Phase B receipts on 4e5d28ab2 (researcher-r216-kit/out-b1): library whole 114 pass, 2 fail; bus library.test and
library-sync.test 12 pass; cli 266 pass. The 3,000-file rebuild took 3807 ms (the test, with fixture setup, 4070 ms;
`t.diagnostic` in index-build.test.mjs). No row 216 mutant was required or thrown in that round.
The 2 library reds are test faults, not product faults. Both are placed as first-run reds in files this row adds
(mos-1272); Mos ruled both fixes in mos-1528 and Sage relayed it in sage-3712.
- R4: `packages/library/tests/index-build.test.mjs`, test "a different repo path, mapping or commit gets its own
index". Old: `writeFileSync(join(repo, 'Book One/Places/New.md'), …)` with no `Book One/Places` folder in FILES, so
the write failed with ENOENT. New: one `mkdirSync(join(repo, 'Book One/Places'), { recursive: true })` before the
write (import line gains `mkdirSync`). No assertion changes.
- R5: `packages/library/tests/query.test.mjs`, test "a hybrid sees its sources canon, minus exclusions, and no
playground". The assertion sorts the actual paths and compared them to an unsorted literal, so the literal had
the wrong order (Characters before Chapters; Second World Characters before Chapters). New: only the expected
literal is reordered into sorted order. The `.sort()` on the actual side stays, and the assertion still checks
order. The set of seven paths is unchanged.
- Product code: no edit.
Reruns owed after the r5 commit (run on Sage's CLEAR, after the 283 gate): index-build.test.mjs whole,
query.test.mjs whole, library whole, and the mutants those two files kill (M-a to M-e above). Results are appended
below as a new entry.
Run limit note: stack-release.test.mjs ran 644 s in the cli run. A per-file limit of 3 x 644 = 1932 s covers it.
Sage's gates run cli as one whole-package `node --test` under 2100 s.
Carry notes from Filbert's static check (doc only, no change in r5): N1 and N2 stay recorded as he gave them. The
`After` list in this file still names row 268; row 268 landed at c27207f6e.
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more