Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b43156fd95 | ||
|
|
17e421cf4c | ||
|
|
bbc38067f2 | ||
|
|
6418b1b123 | ||
|
|
4a16ebaa4a | ||
|
|
e0c6142f3a | ||
|
|
42ba0f5bc0 | ||
|
|
68c08b4e76 | ||
|
|
76f84465fd | ||
|
|
1bd214e382 | ||
|
|
56c1845505 | ||
|
|
72a3125a6e | ||
|
|
6a5e125113 | ||
|
|
529e121379 | ||
|
|
9042754c47 | ||
|
|
c98cd41d8d | ||
|
|
94d9e90a6a | ||
|
|
b617b80f68 | ||
|
|
93200bb446 | ||
|
|
d16958bcfd | ||
|
|
48f1627be2 | ||
|
|
17f056787f | ||
|
|
200dd396c4 | ||
|
|
537902d261 | ||
|
|
f58049de02 | ||
|
|
33f49f7d5a |
No files matched your search
@@ -5469,3 +5469,26 @@ bus bypass refusals. Prior old-test draft regression fixed under sage-3918;
|
||||
missing root oauth4webapi corrected by pinned npm ci under sage-3924. Evidence:
|
||||
agents/poe/work/queue-208/BUILD.md. Independent reviews and Sage's global gate
|
||||
remain; no live state, schema or bus policy change.
|
||||
|
||||
2026-10-24 | Goldie | Row265 / #1770 before, recorded during authorized verification | C2-09b has no project room verbs. On goldie/row265 at ec86f2c84, add only room.open/send/read, pure exported leading-tag parser, README verb rows and new bus tests. Sage-3921 fixes EOF-tag and message-only timestamp readings; tester list/helpers/schema stay unchanged. Claim rev3814; Sage-3932 CLEAR for serialized new files, whole bus and twelve refusal mutants. No old-test edits authorized; stop rules apply.
|
||||
|
||||
2026-10-24 | Goldie | Row265 / #1770 first slot STOP (sage-3932) | New parser14/14, rooms18/18 green; serialized bus stops at old project-runtime.test.mjs:14 import-closure pin, partial619 ok/1 not ok. New pure address.mjs import is absent from expected imports. Static trace also needs address.mjs in seat RUNNER_FILES. No old-test/product correction, no mutant/restored run, no green commit/push; old-pin and out-of-owned-files mount change sent to Sage for ruling. Audit prose missed the direct source pin despite raw grep hit; correction recorded in BUILD.md.
|
||||
|
||||
2026-10-24 | Goldie | Row265 / #1770 verified (mos-1567, sage-3942) | Approved correction adds solely address import pin + Row265 comment to project-runtime.test and one pure-source RUNNER_FILES mount; new block checks parser has no imports. Serialized fresh-short-TMP timeout-k10: unchanged sandbox10/10, parser15/15, rooms18/18, full bus957/957, twelve named refusal mutants killed (each Node1, driver0), restored parser15/15 + rooms18/18. All positives zero fail/skip/cancel; every mutation restored full broker SHA71b32ee4. Old first-red evidence retained, no other old-test change. Slot returned goldie-0034; Boyd packet explicitly includes mount-list review, candidate push next.
|
||||
2026-10-24T07:18:53Z | Sage | Row 327 (#1825, Bradford, Poe and Dewey) LANDED 68c08b4e7. Before: refactor at 94d9e90a6; the push guard was a scratch script with no reviewed-binary check. After: scripts/push-check.sh and scripts/test-push-check.sh on refactor, 3 commits cherry-picked onto 6a5e12511 with blobs equal to the round 3 tip d858c9f8b, plus doc commits 76f84465f and 68c08b4e7. Binaries are found by numstat or a NUL in the first 8000 committed bytes, regardless of attributes; each needs its sha256 and path in an approved latest-round candidate; an unconfirmed queue tail exits 2. Reviews: Poe c31525 rev 3882, Dewey c31526 rev 3883. Gate: 1st run 06:57Z RED, not the candidate: canonical node_modules was empty from 05:45:15Z (sage-4023). Rerun with its own root npm ci GREEN 07:15:49Z: 17 package suites 0 failures (cli 986, plugins 119, webui 418), 11 scripts exit 0, push-check 6 mutants KILLED; skips are the queue canonical-root check and the row 244 live provider cases. The landed push-check.sh passed its own push (NO_BINARY, NO_ATTRIBUTION, PUSH_READY). mos-1575: gates install their own node_modules; canonical restored with npm ci (128 packages).
|
||||
|
||||
## 2026-10-23 — Lena, row 296 / #1802 FED R7 start
|
||||
|
||||
Before: R4 and R6 landed in base 45dca69f6; no scratch two-instance acceptance script.
|
||||
Plan: preserve Quackerjack handoff c6ab9bdc9, add isolated script/CLI test harness with real
|
||||
Console proof in transient user services, map assertions and two mutants for Filbert.
|
||||
Untested: all Node/tests and shared gate wait for Sage CLEAR; no production or live state changes.
|
||||
|
||||
## 2026-10-24 — Lena, row 296 / #1802 scoped acceptance verified
|
||||
|
||||
After: lena/row296-r2 rebased onto e0c6142f3 with both append logs preserved and row source
|
||||
unchanged. Own root/webui npm ci (128/2 packages), canonical dependencies untouched. Sage-4035
|
||||
slot: plain acceptance 1/1, killed-driver cleanup expected exit1 with cleanup verified, M1/M2
|
||||
both killed by named assertions with product hashes restored, whole new file intentionally
|
||||
skips outside its script. No unexpected first-run red, no old test change, no full/shared gate
|
||||
claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r2/.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Bradford
|
||||
|
||||
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1572. Owns the push check row:
|
||||
`scripts/push-check.sh`, which passes a binary only when an approved review candidate lists its path and sha256
|
||||
(docs/plans/2026-10-24_push-guard-binary-manifest.md, #1825).
|
||||
|
||||
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 `bradford/*` branches through the repository credential helper (mos-0981), never `refactor`,
|
||||
`main` or another seat's branch. Poe gives the first Codex review and Dewey the Opus 5.5 landing review (mos-1542,
|
||||
mos-1571). Registered by Sage on 2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
|
||||
@@ -0,0 +1,162 @@
|
||||
# Row 327 — reviewed binary push check (#1825)
|
||||
|
||||
2026-10-24 Bradford, branch bradford/row327, base b617b80f6.
|
||||
|
||||
## Plan and boundaries
|
||||
|
||||
Implement the scratch check's ordered tokens in scripts/push-check.sh, using an
|
||||
embedded Node program for byte-safe git paths and SHA-256 blob hashing. Bash is
|
||||
the public entry point; Node is already a repository requirement. Git commands
|
||||
use explicit resolved object IDs, disable external diff/textconv, and inspect
|
||||
committed bytes. No queue writes, hook changes, binary evidence, or live state.
|
||||
Fetch is opt-in. Read reviewed rows with the queue package rows() API rooted in
|
||||
the caller's repository; this preserves canonical/witness/replay checks.
|
||||
|
||||
Only the latest round counts; any changes receipt blocks that round. A matching
|
||||
approval authorizes exactly the manifest path/hash, or a path changed from the
|
||||
commit candidate's first parent with matching committed blob hash and mode.
|
||||
Deleted binaries need no candidate. Multiple --row values form a union.
|
||||
|
||||
Add scratch-only integration coverage in scripts/test-push-check.sh. Reuse queue
|
||||
test fixture helpers to bootstrap real scratch queues, then use queue verbs to
|
||||
open rounds and record receipts. All git writes, bare remotes and queues remain
|
||||
under TMPDIR. Test fail-closed ordering and five required mutants, generated as
|
||||
text copies in scratch. No old tests are modified. Add the TOOLS entry.
|
||||
|
||||
Every run needs Sage CLEAR; run timeout -k 10. Record plain-text results here or
|
||||
in this packet. After green checks, commit and push only bradford/row327 through
|
||||
the repository helper, send Sage and Poe tip/base/path sha256 manifest, and move
|
||||
327 in-review. Sage owns request posting and landing; Dewey reviews once there.
|
||||
|
||||
## Recovery and choices
|
||||
|
||||
Initial worktree queue next refused linked-worktree access as designed. Queue
|
||||
claim is performed from the canonical checkout; production reads in the guard
|
||||
retain the same refusal rather than invent a worktree override.
|
||||
|
||||
Requested Sage ruling on AGENTS log appends outside the brief's owned paths.
|
||||
|
||||
Sage sage-3989: lead handles BUILD-LOG and SESSIONS at landing. Session entry:
|
||||
2026-10-24 Bradford — row 327 / #1825 — implementation and scratch verification
|
||||
in this packet; outcome to be appended after acceptance.
|
||||
|
||||
Implementation note: commit candidates use an explicit first-parent diff (empty
|
||||
tree for a root commit), with rename detection disabled, like verify-commit.
|
||||
The changes-veto case has both an approval and a changes receipt; a veto mutant
|
||||
would otherwise still fail for lack of approval and provide false confidence.
|
||||
A receipt for another candidate cannot be produced through queue verbs: the
|
||||
suite corrupts only a scratch queue and expects the production reader to refuse.
|
||||
Diff color, external diff and textconv are disabled so scans inspect plain git
|
||||
output. Paths are read from NUL-delimited records (including spaces/tabs).
|
||||
|
||||
## Verification (Sage CLEAR sage-3991)
|
||||
|
||||
First new-suite run red: my whitespace-path fixture attempted a manifest path
|
||||
that the queue intentionally forbids. Corrected the fixture to a commit
|
||||
candidate, which can contain that git path. Production queue code and old tests
|
||||
remain unchanged. Raw first-run output is test-run-1.txt.
|
||||
|
||||
Second run green: PASS push-check 22 checks; KILLED skip-hash,
|
||||
accept-changes, earlier-round, working-tree, no-row. test-run-2.txt holds the
|
||||
plain-text receipt. bash -n both scripts and git diff --check passed. Runs used
|
||||
TMPDIR recorded in tmpdir.txt and timeout -k 10 240; all generated binary bytes
|
||||
were in disposable scratch repositories, never in this checkout or evidence.
|
||||
The slot was used for only the authorized new suite and syntax/diff checks.
|
||||
No claim of the lead's full scripts/test-*.sh landing gate is made here.
|
||||
|
||||
Final inspection after the first green run found a path-decoding concern: lossy
|
||||
UTF-8 decoding can conflate distinct git filenames. Hardened NUL-delimited path
|
||||
decoding to reject invalid UTF-8, and quote newline-containing paths in refusal
|
||||
output to preserve one STOP line. Added scratch cases for both. Requested a new
|
||||
CLEAR (bradford-0005) because the previous slot had already been released.
|
||||
|
||||
Final run (sage-3994 CLEAR) green on final code: PASS push-check 24 checks,
|
||||
all five mutants KILLED again, bash -n both scripts and git diff --check passed.
|
||||
Receipt: test-run-3.txt. The guard rejects non-UTF-8 git paths as environment
|
||||
errors rather than comparing replacement characters; newline paths remain
|
||||
checkable through commit candidates and refusals quote them on one line.
|
||||
|
||||
Acceptance for the builder stage: ordered tokens and stop behavior, explicit
|
||||
base/fetch, exact blob manifest/commit checks, latest-round/veto checks, deletion,
|
||||
log/FF/secrets/attribution refusals, and all required mutants are covered. Queue
|
||||
reads use rows() with production validation; tests use only scratch queues.
|
||||
Candidate-mismatched receipts are impossible through the queue API and are
|
||||
covered by a corrupt scratch queue refusing its read. The lead's full-suite gate,
|
||||
Poe first review, Dewey landing review, and adoption for Sage's next push remain
|
||||
landing stages. No hooks, role policy, credentials or other seats' paths changed.
|
||||
|
||||
## Poe first review R1 (poe-0010) — changes
|
||||
|
||||
cceea407e has a blocking subdirectory bypass: ls-tree without --full-tree can
|
||||
omit an existing binary outside the caller cwd, making it look deleted.
|
||||
Accepted the finding. Git now anchors all invocations at the repository root,
|
||||
ls-tree uses --full-tree, and diff/diff-tree use --no-relative. EXPECTED_LOG still
|
||||
resolves from the original caller cwd. Added outside-cwd and nested-binary cases
|
||||
under diff.relative=true: unreviewed stops, reviewed manifest/commit passes.
|
||||
No old test changed. New run requires Sage CLEAR before execution.
|
||||
|
||||
R1 verification under sage-3999 CLEAR: test-run-4.txt records PASS 29 checks
|
||||
and KILLED all five required mutants. Syntax and diff whitespace checks passed.
|
||||
A second bounded run used the new suite against cceea407e's script copied under
|
||||
TMPDIR: r1-regression-red.txt records the outside-cwd unreviewed binary reaching
|
||||
NO_BINARY/PUSH_READY (actual exit 0, expected 1). Thus the new regression detects
|
||||
the reviewed bug, while the revised guard refuses it. No code changed between
|
||||
the green run and this old-candidate reproduction. All runs used timeout -k 10.
|
||||
|
||||
Holding claimant queue writes per sage-3999 until Boyd finishes row 309's record.
|
||||
Last acknowledged row 327 state is in-progress (bradford-0010, revision 3868).
|
||||
|
||||
## Dewey landing review B1 (dewey-0956) — changes
|
||||
|
||||
Read the invited review body at /mnt/storage/scratch/tmp/dew327vWXloCT/body.md.
|
||||
B1 identifies a brief-level gap: numstat follows candidate/host git attributes,
|
||||
so *.bin diff makes a NUL-containing blob disappear from the binary-only set.
|
||||
The production script indeed filters numstat before reading any blob, consistent
|
||||
with that data flow. Proposed correction: enumerate every changed numstat path,
|
||||
skip absent/non-blob entries at SHA, classify binary when numstat says binary OR
|
||||
committed blob has NUL among first 8000 bytes. Read with cat-file, never worktree.
|
||||
Test committed .gitattributes, info/attributes, and core.attributesFile overrides
|
||||
in scratch; reject with no row, allow exact reviewed bytes; add NUL-scan mutant.
|
||||
Sage owns the brief amendment and next CLEAR/queue sequencing.
|
||||
|
||||
N1 acknowledged: candidate-equality check is defense in depth; invalid receipt
|
||||
cannot pass queue replay, so the corrupt-queue case does not isolate that check.
|
||||
N2/N3 recorded as described in review, without optional secrets/reader changes.
|
||||
N4 planned hardening: refuse unconfirmed durable tail notes from rows() with
|
||||
exit 2, with a real scratch witness-prefix case rather than a test-only reader.
|
||||
No test run or queue write started for B1/N4 before Sage CLEAR.
|
||||
|
||||
Sage sage-4013/4015 authorizes B1 and N4 within the brief; amendment committed on
|
||||
refactor at 9042754c4 (read with git show, no branch change). Fix and tests written
|
||||
before requesting CLEAR as directed. Tests cover all three attribute sources,
|
||||
approved manifest success under each override, exact 7999/8000 offset boundary,
|
||||
and a real prefix-witness rollback to yield an unconfirmed approval tail.
|
||||
Added only the requested sixth mutant, skip-nul-scan; the original five remain.
|
||||
No new production configuration knobs, no secrets-diff change, no queue writes.
|
||||
Will demonstrate red against the prior candidate using the new tests, then green
|
||||
against revised code, under the approved slot. Full landing gate remains Sage's.
|
||||
|
||||
After Dewey's round-2 CHANGES record (comment 31522, rev3876), Sage sage-4016
|
||||
released only the claimant return to in-progress. Acknowledged:
|
||||
`ok bradford-0017 rev 3877 row 327 in-review→in-progress`.
|
||||
No other queue write before the next in-review candidate move.
|
||||
Test slot remains pending Sage CLEAR after Gyro's build.
|
||||
|
||||
## Amendment 1 verification (sage-4017 CLEAR)
|
||||
|
||||
b1-regression-red.txt: new tests against 704910ca3 reproduce attributes-hidden
|
||||
asset.bin reaching NO_BINARY/PUSH_READY with exit0 instead of exit1.
|
||||
n4-regression-red.txt: revised code with only the N4 check removed lets the real
|
||||
scratch unconfirmed approval tail reach BINARY_REVIEWED/PUSH_READY, exit0 rather
|
||||
than exit2. These are expected negative controls; neither changed committed code.
|
||||
|
||||
Final full new-suite run is green: test-run-5.txt records PASS 38 checks and
|
||||
KILLED skip-hash, accept-changes, earlier-round, working-tree, no-row, skip-nul-scan.
|
||||
All runs used timeout -k 10 240 with the same scratch TMPDIR; no old tests were
|
||||
changed or run. bash -n both scripts and git diff --check also passed. No source
|
||||
changes followed this green run. Sage's full landing gate is still separate.
|
||||
|
||||
Scope in the final delta: classify all changed present blobs using numstat OR
|
||||
first-8000-byte NUL; fail closed on queue unconfirmed-tail notes; regression cases
|
||||
and requested sixth mutant; TOOLS entry and this plain-text packet. B1's brief
|
||||
amendment is Sage's 9042754c4; this branch does not edit the brief or queue files.
|
||||
@@ -0,0 +1,27 @@
|
||||
node:internal/modules/run_main:107
|
||||
triggerUncaughtException(
|
||||
^
|
||||
|
||||
AssertionError [ERR_ASSERTION]: LOG_OK
|
||||
FF_OK
|
||||
NO_BINARY
|
||||
SECRETS_CLEAN
|
||||
NO_ATTRIBUTION
|
||||
PUSH_READY ee394f31d1e52751716e2413049f41b2f3072e65..76acc948149e718091fcd3f3807257e752a6616e
|
||||
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
at Object.check (file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:44:12)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:100:7
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
Node.js v26.8.1
|
||||
@@ -0,0 +1,27 @@
|
||||
node:internal/modules/run_main:107
|
||||
triggerUncaughtException(
|
||||
^
|
||||
|
||||
AssertionError [ERR_ASSERTION]: LOG_OK
|
||||
FF_OK
|
||||
BINARY_REVIEWED 1
|
||||
SECRETS_CLEAN
|
||||
NO_ATTRIBUTION
|
||||
PUSH_READY c1a8a1e9b8c3988874a8f7a35afab7c06aea94a5..325cd3671a7bf70b9a3aee60f4446bc93594a12a
|
||||
|
||||
|
||||
|
||||
0 !== 2
|
||||
|
||||
at Object.check (file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:44:12)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:121:7
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 2,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
Node.js v26.8.1
|
||||
@@ -0,0 +1,27 @@
|
||||
node:internal/modules/run_main:107
|
||||
triggerUncaughtException(
|
||||
^
|
||||
|
||||
AssertionError [ERR_ASSERTION]: LOG_OK
|
||||
FF_OK
|
||||
NO_BINARY
|
||||
SECRETS_CLEAN
|
||||
NO_ATTRIBUTION
|
||||
PUSH_READY 896bdf0ef56596c67d2dc427275743c46026e832..ad9f9c86112fadafef4d4854f46bd9994fe2fe5d
|
||||
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
at Object.check (file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:44:12)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:130:7
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
Node.js v26.8.1
|
||||
@@ -0,0 +1,21 @@
|
||||
node:internal/modules/run_main:107
|
||||
triggerUncaughtException(
|
||||
^
|
||||
|
||||
AssertionError [ERR_ASSERTION]: queue: candidate manifest path must be a repo-relative path: "space tab\tasset.bin"
|
||||
|
||||
|
||||
2 !== 1
|
||||
|
||||
at Object.round (file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:61:12)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/bradford-row327/[eval1]:116:7
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 2,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
Node.js v26.8.1
|
||||
@@ -0,0 +1,6 @@
|
||||
PASS push-check 22 checks
|
||||
KILLED skip-hash
|
||||
KILLED accept-changes
|
||||
KILLED earlier-round
|
||||
KILLED working-tree
|
||||
KILLED no-row
|
||||
@@ -0,0 +1,6 @@
|
||||
PASS push-check 24 checks
|
||||
KILLED skip-hash
|
||||
KILLED accept-changes
|
||||
KILLED earlier-round
|
||||
KILLED working-tree
|
||||
KILLED no-row
|
||||
@@ -0,0 +1,6 @@
|
||||
PASS push-check 29 checks
|
||||
KILLED skip-hash
|
||||
KILLED accept-changes
|
||||
KILLED earlier-round
|
||||
KILLED working-tree
|
||||
KILLED no-row
|
||||
@@ -0,0 +1,7 @@
|
||||
PASS push-check 38 checks
|
||||
KILLED skip-hash
|
||||
KILLED accept-changes
|
||||
KILLED earlier-round
|
||||
KILLED working-tree
|
||||
KILLED no-row
|
||||
KILLED skip-nul-scan
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/YPrPwR
|
||||
@@ -0,0 +1,78 @@
|
||||
# Row 208 (193d) second review: poe/row208 4a4bef6c1 (duckworth-0302)
|
||||
|
||||
Request: sage-3934 (sage-3931 request, c31492 on #1706). Candidate: 4a4bef6c1 on ec86f2c84, one
|
||||
commit. Design: e2d161e2e:agents/morgana/work/queue-193/DESIGN.md section 8, sha256 dd7fcb41
|
||||
(matches). Scope under mos-1542: the guards only. The question is whether a viewer can widen
|
||||
authority.
|
||||
Method: static. Git reads, plus the mutant summary Sage sent by path. No node, no tests.
|
||||
|
||||
## Verdict: APPROVE (static)
|
||||
|
||||
The diff has no bus or schema change. Every write is still decided by the broker as it stood at the
|
||||
base. The webui adds routes and hints, and no authority.
|
||||
|
||||
- **chat-http.mjs.**
|
||||
- share and unshare are POST only. They come after the same write fences as every other write:
|
||||
send and named mode, the SSO session or auth header, Origin, and sec-fetch-site.
|
||||
- The body must be exactly `{to}`: a string of 1-200 characters with no CR, LF or NUL. Extra keys
|
||||
(owner, verb) refuse 400, and the test covers that.
|
||||
- shares is GET only and takes no query.
|
||||
- The reply must echo thread, to and op exactly. Anything else is 503 outcome-unknown.
|
||||
- The new STATUS codes map share-not-covered to 403, share-exists to 409 and share-not-found to 404.
|
||||
- `?shared=` accepts only `true`.
|
||||
- **chat.mjs and the adapter.**
|
||||
- The three verbs go over `createChannelCaller` to the webui adapter (index.mjs:12-14). The adapter
|
||||
is an allowlist passthrough to the session's own `call`.
|
||||
- A granted user's chat is rebound per session (serve.mjs:701), so share calls carry the user's
|
||||
principal, never the owner's.
|
||||
- The test fixture runs the real adapter and the real Broker.
|
||||
- **The broker decides each new path, unchanged from the base.**
|
||||
- thread.share, thread.unshare and thread.shares are in USER_VERBS and not in VIEWER_VERBS, so
|
||||
userLevel gives collaborator. A viewer gets access-refused at #grantedThread (broker.mjs:950).
|
||||
- Each of the three goes through `#thread`'s owner check (broker.mjs:909-913). A sharee gets
|
||||
thread-not-owned and can't reshare.
|
||||
- share-not-covered applies to a `user:` target (broker.mjs:6559).
|
||||
- Only thread.messages takes `#threadRead`, so a share gives read access and nothing else.
|
||||
- **The grant-hint predicate.**
|
||||
- access.mjs `chatGrants` re-runs `lookup`, so the subject must still sign in as session.user with a
|
||||
live grant. It then keeps only that user's grants in this business that cover at collaborator,
|
||||
which is the same `covers` call as the broker's `#granted`.
|
||||
- It returns only `{projects, instances}`. No ids, receipts, subjects or other users escape. Any
|
||||
failure gives [].
|
||||
- The client's `scopeWrites` requires the project and the instance to match within one grant
|
||||
(`some(g => project && instance)`). It never pairs a project from one grant with an instance from
|
||||
another, so mixed grants can't combine into a scope that no single grant has.
|
||||
- `includes('*')` on the client is equivalent to covers' `[0] === '*'`, because checkScope
|
||||
(business access.mjs:97-103) refuses `*` unless it is alone.
|
||||
- The hint is never carried over: chatGrants starts at [] and is re-read from /api/config on each
|
||||
user render. It is in /api/config only when `granted`.
|
||||
- **No client-only hiding stands in for a broker refusal.** Each hidden control maps to a refusal:
|
||||
|
||||
| Hidden control | Who | Broker refusal |
|
||||
| --- | --- | --- |
|
||||
| composer and resume | viewer | thread.send/open: access-refused |
|
||||
| composer and resume | sharee | thread.send/open: thread-not-owned |
|
||||
| share form | viewer | access-refused |
|
||||
| share form | sharee | thread-not-owned |
|
||||
| create thread | out of scope | collaborator check at create |
|
||||
| create project | user | project.create isn't in USER_VERBS |
|
||||
| session controls, clear, cancel | user | session.* isn't in USER_VERBS (tester-only) |
|
||||
|
||||
Test :136 POSTs create, send, open and share from the page with the UI bypassed, and checks four
|
||||
403 access-refused responses, unchanged table counts, and that each verb reached the bus. Test :19
|
||||
adds unshare and the shares read over HTTP. Test :154 covers the sharee's thread-not-owned on send,
|
||||
open and share.
|
||||
- **Mutants, as reported (sage-3920 CLEAR, /mnt/storage/scratch/tmp/8UlAhS/poe-208-mutants.json, not
|
||||
rerun).**
|
||||
- There are four served-copy mutants: composer, open, share and create. Each one's anchor must exist,
|
||||
or the fixture throws.
|
||||
- Each mutant fails the UI-absence test with ERR_ASSERTION, and the bypass test passes under each
|
||||
one, so the broker still refuses. That meets design section 8: "With each UI check removed, the
|
||||
bus still refuses."
|
||||
- The full webui suite passed 418/0 and plugins 119/0 (sage-3925), as reported.
|
||||
|
||||
## Notes
|
||||
|
||||
- N1 (outside guard scope). user.js:16 re-renders every 5 s while the tab is visible. Each render
|
||||
re-reads /api/config, projects, threads and the shared list. This doesn't affect authority, but it
|
||||
is steady load per open user tab.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Glomgold
|
||||
|
||||
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1571. Owns row 309, O2 W1: the reviewed local
|
||||
thesaurus data (docs/plans/2026-10-21_q195-writing-editor.md, "W1 reviewed local thesaurus data").
|
||||
|
||||
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 `glomgold/*` branches through the repository credential helper (mos-0981), never `refactor`,
|
||||
`main` or another seat's branch. Claude seats orchestrate and give the Opus 5.5 landing review (mos-1571). Registered by
|
||||
Sage on 2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
|
||||
@@ -0,0 +1,134 @@
|
||||
# Row 265 / #1770 — project rooms
|
||||
|
||||
Current result: verified under sage-3942; slot returned in goldie-0034. Full bus 957/957, sandbox 10/10, parser 15/15, rooms 18/18, twelve mutants killed, restored files green. Review/push packet below includes mos-1567 closure scope.
|
||||
|
||||
Goldie, 2026-10-24. Branch goldie/row265, base ec86f2c847f6154a0677c6ed23c87dabc2c665d0. Brief docs/plans/2026-10-20_case2-rows.md § C2-09b rooms at blob3a5ed91a4fb3462df67e6939ae6f18290ac9b453; design agents/gosalyn/work/case2/DESIGN.md §6. Sage-3915 transfers ownership from Rocko to Goldie and review from Dewey to Boyd. No old test edit authorized. All tests/mutants wait for Sage CLEAR; queued behind Poe, Bentina and Sage's326 gate.
|
||||
|
||||
## Design and clarified readings
|
||||
|
||||
A room is a bus project. Members are all loaded business instances whose definitions are in THREAD_DEFINITIONS, sorted by key. Canonical room threads have those keys as names and belong to the requesting human. open checks all name/instance/owner collisions before creating anything; it delegates missing inserts to existing thread.create dispatch, including default session metadata. send/read require the complete canonical set. No schema or migration change.
|
||||
|
||||
Sage-3921 settles two brief readings: the single captured timestamp applies to the room.send MESSAGE rows; existing route/delivery/event helpers keep their own timestamps in the same dispatch transaction. They are unchanged. A final EOF-terminated @name is a tag, so bare @continuity is invalid-request rather than a broadcast. Leading whitespace is accepted. Pure parseAddress returns {tags,body}, where body is the unaddressed remainder; room.send stores the ORIGINAL input unchanged. Export it from the bus index and expose a separate reusable test table for C2-10c parity.
|
||||
|
||||
send resolves tags against current #named results and member instance keys, deduplicates recipients, and refuses multiple member matches. It checks each target's current default-session fence and messaging rule, then inserts one normal INFO message, route and human.input per target under the existing request transaction. All message rows share one at. No tag broadcasts; tags after the first non-tag token remain plain text.
|
||||
|
||||
read includes all sessions of the caller's canonical threads, globally ordered by messages.seq. Each entry retains first-message fields plus thread/session/instance and adds ids (every grouped message id) and to (each instance once). Only consecutive human rows with identical at/body/class group, and a repeated instance starts a new entry, so same-millisecond identical sends remain separate. Paging limit counts merged entries (THREAD_PAGE defaults/max); clients continue after the last ids element. Cursor must belong to a room thread. Bounded raw scan limit*memberCount+1 completes requested groups without reading unbounded history. Sage-3921 accepts these routine choices as proposed in goldie-0030.
|
||||
|
||||
## Scope / steps
|
||||
|
||||
1. Read current thread.create/send/messages, transaction wrapper, naming and messaging helpers; audit source readers/pins. Claim265 via queue after next. Done/read, claim recorded at rev 3814 with op goldie-0265-start-1 (same-op replay confirmed).
|
||||
2. Add parser + index export, only three room cases in broker, README verb rows and new bus tests/fixtures. Do not modify existing thread/messaging/ACL/helper behavior.
|
||||
3. New tests cover all brief cases, atomicity across message/route/input rows, ownership/projects, permissions, paging/grouping and session fences. No suites or test runs before CLEAR.
|
||||
4. Record exact refusal→test→mutant matrix here before requesting the slot; one targeted killed mutant per named refusal, restoration byte-checks. New files whole then bus suite under CLEAR, serial timeout-k10, fresh31-byte mktemp TMPDIR plus UTC/load/busy header.
|
||||
5. Before in-review, grep packages/*/tests for broker.mjs/TESTER_VERBS and changed-file pins. Push own branch and hand tip/base/manifest directly to Sage/Boyd. Sage owns request posting/landing.
|
||||
|
||||
No Console/Discord/CLI room surface or cross-agent reply visibility added. No tester verb, role, schema or secret change. No host action or credential access.
|
||||
|
||||
## New tests and refusal / mutant matrix
|
||||
|
||||
Only new test files and a new reusable vector fixture are added. No pre-existing test changes. Parser table: address.test.mjs consumes address-cases.mjs, including final EOF tags, leading whitespace, tags after the first word, repetition and blank/tag-only remainder. Broker cases: rooms.test.mjs covers sorted/reused canonical threads, other-instance and other-owner collisions, all three verbs' human/tester gates, missing projects/member set, partial/foreign/wrong-instance rooms, key and current-name addressing, ambiguity/same-member name+key, unknown/non-member/old-name tags, tags-only bodies, one/many/broadcast targets, original bytes, shared message timestamp, routes/deliveries/human.input, denied last recipient, clearing last session, all-session merged reads, agent reply metadata, merged paging/default/max, foreign/noncanonical/absent cursors, and same-at repeated sends. Synthetic historical rows append valid rows; they do not rewrite evidence or clocks. The same-at batches repeat the first recipient at each boundary, proving identical sends remain distinct.
|
||||
|
||||
Each named refusal has one mutation of its enforcement, run against the entire new rooms.test.mjs file. The driver mutants.py stores the exact original→mutant diff in logs/Mxx-*.diff, logs UTC/load/busy/TMPDIR, accepts only Node exit 1 with the named target test failing, and restores/verifies full broker bytes and SHA256 in finally. No mutation is retained. Generic pre-existing request/schema/argument policies are exercised through existing code; the matrix names the refusals added or deliberately inherited by these room cases.
|
||||
|
||||
| Mutant | Refusal | Guard relaxed | Required failing new test |
|
||||
| --- | --- | --- | --- |
|
||||
| M01-human | human-required | remove common room human check | room verbs require the human |
|
||||
| M02-tester | tester-refused | add the three room verbs to TESTER_VERBS | room verbs require the human (explicit absence pin) |
|
||||
| M03-project | project-not-found | remove room project lookup check | room verbs refuse missing projects |
|
||||
| M04-members | thread-instance-missing | allow empty member set | room verbs refuse missing projects |
|
||||
| M05-collision | room-thread-name-taken | remove canonical owner/instance collision check | room.open preflights instance collisions |
|
||||
| M06-open | room-not-open | remove missing canonical thread check | send/read refuse a missing canonical member |
|
||||
| M07-body | invalid-request | allow empty parsed remainder | unknown or non-member addressees and tags-only bodies |
|
||||
| M08-unknown | unknown-addressee | allow zero member matches | unknown or non-member addressees and tags-only bodies |
|
||||
| M09-ambiguous | addressee-ambiguous | allow multiple member matches | a legacy name colliding with another member key |
|
||||
| M10-messaging | message-sender-refused | omit room target messaging check | messaging denial on the last target |
|
||||
| M11-cursor | message-not-found | unknown room cursor falls back to seq zero | room.read groups one send, pages |
|
||||
| M12-fence | session-clearing | omit room target session fence | a clearing last member fences the whole send |
|
||||
|
||||
For M02, the existing human check remains a second barrier: the mutant deliberately relaxes only the tester allowlist; the new explicit absence assertion kills it without loosening or running the old pin. Existing helpers keep their full session-state behavior. No helper or schema mutation ships.
|
||||
|
||||
## Reader / hash audit
|
||||
|
||||
`rg -n 'broker\.mjs|TESTER_VERBS' packages/*/tests --glob '!fixtures/*'` produced reader-grep.txt (133 hits at audit time). These are imports except the concrete source read at bus/tests/limit-checks.test.mjs:323-326 (forbidden limit-check/token-limit imports) and the tester set assertions at bus/tests/tester-writes.test.mjs:18-32. The latter stays byte-for-byte unchanged. bus/tests/viewer.test.mjs:116 builds its coverage set from exported verb lists; rooms do not widen those lists. No old test needs an edit.
|
||||
|
||||
Pin check: recursively inspect packages/*/tests plus scripts/test-*.sh for changed-file mentions (broker.mjs, address.mjs, packages/bus/README.md, packages/bus/src/index.mjs) with literal 64-hex values within eight lines: pin-context-grep.txt records no hits. A direct same-line hash/digest/sha256 grep finds registrySha256 imports only, which hash host registry data rather than broker bytes. The independent channel-proof.test.mjs baselines cover proof/transport files, none changed here. Positive control: pin-positive-control.txt records actual schema.sql / SCHEMA_SHA256 / createHash references in bus/tests/schema.test.mjs and bus/src/store.mjs. Schema bytes are unchanged.
|
||||
|
||||
## Requested serialized slot plan (not run)
|
||||
|
||||
At each run: fresh `TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)`, date -u, uptime, and `bash /mnt/storage/scratch/sage-busy-now.sh 2>/dev/null | tail -1` in the log. No concurrent test runs. Ignored root node_modules symlink targets the canonical checkout; it will not be committed.
|
||||
|
||||
1. `timeout -k 10 120 node --test packages/bus/tests/address.test.mjs` whole.
|
||||
2. `timeout -k 10 120 node --test packages/bus/tests/rooms.test.mjs` whole.
|
||||
3. `timeout -k 10 600 node --test --test-concurrency=1 packages/bus/tests/*.test.mjs` bus suite whole.
|
||||
4. `python3 agents/goldie/work/queue-265/mutants.py`: M01 through M12 serially, each runs `timeout -k 10 120 node --test packages/bus/tests/rooms.test.mjs` whole, fresh TMPDIR/header; stop on a live mutant or wrong failure. Record each diff and restoration digest.
|
||||
5. Restored address.test.mjs whole and rooms.test.mjs whole separately, same commands as 1/2 and fresh directories/header each.
|
||||
|
||||
Estimate 5–10 minutes; bus timeout ceiling 10 minutes, individual new/mutant ceilings 2 minutes each. On first new-test red, record/report it. On any old-test red or need for old-test change, stop and ask Sage. Static node --check for new modules/tests and git diff --check passed; no tests or mutants run yet.
|
||||
|
||||
## First slot STOP — sage-3932
|
||||
|
||||
2026-10-24T04:27Z: stopped on an old-test red. No old test changed or rerun, no mutant executed, no product adjustment after the failure, no commit/push claiming verification.
|
||||
|
||||
| Step | Exit | Counts / disposition | Log |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 address whole | 0 | 14 pass, 0 fail/skip/cancel; 58.559 ms | logs/01-address.log |
|
||||
| 2 rooms whole | 0 | 18 pass, 0 fail/skip/cancel; 1067.524 ms | logs/02-rooms.log |
|
||||
| 3 bus whole | 1 (stopped) | partial 619 top-level ok, 1 not ok; not a completed-suite result | logs/03-bus.log |
|
||||
| 4 twelve mutants | not run | stop rule | none |
|
||||
| 5 restored files | not run | stop rule; no mutations were applied | none |
|
||||
|
||||
All three run headers use UTC/date, uptime, helper CLEAR and distinct 31-byte scratch TMPDIRs. Bus began04:25:22Z at load2.80/2.91/2.61, TMPDIR /mnt/storage/scratch/tmp/LaZAaK. Parser used /mnt/storage/scratch/tmp/rqcTH7; rooms has its own header. Explicit TAP reporter was selected for rooms/bus and the mutant driver so failure detection/counts are stable across Node26's default spec reporter; this changes reporting only.
|
||||
|
||||
The old red is project-runtime.test.mjs:12, assertion:14, test620, “Broker keeps the sandbox client import closure: only the listed imports, no lazy project import”. Actual imports include the new `import { parseAddress } from './address.mjs';`; the pinned array omits it. ERR_ASSERTION deepStrictEqual; individual duration2.469251ms. The full expected/actual/stack is in 03-bus.log. No other red was observed before stopping.
|
||||
|
||||
Reader-audit correction: reader-grep.txt:100 already contains project-runtime.test.mjs:13, a direct broker source read. The earlier prose incorrectly described the import-closure assertion as a normal import; I missed this old source pin during inspection. This is not a hash pin. Follow-up static trace finds seat/src/sandbox.mjs:50-60 RUNNER_FILES includes broker and landing but not address; seat/tests/sandbox.test.mjs:45-53 compares this mount list to the recursively imported source closure. A parser dependency therefore also needs the one new RUNNER_FILES path. Both an old-test pin change and this out-of-owned-files product change require Sage/Mos approval before edits. Recommended bounded change: add only the parser import to the project-runtime expected imports, add only packages/bus/src/address.mjs beside broker in RUNNER_FILES, and leave the generic sandbox closure test unchanged.
|
||||
|
||||
Broker bytes remain SHA256 71b32ee4afa322793aad81231441ee7badc3e5aff65ff008c6cc0f7f63b72781. git diff --check still passes. Slot returned in goldie-0032, pending ruling/new CLEAR; no reruns.
|
||||
|
||||
## mos-1567 approved closure correction / second slot request
|
||||
|
||||
Sage-3940 relays Mos's mos-1567 and widened row scope (sage-3939). Applied exactly the approved pre-existing-file edits: project-runtime.test.mjs adds two lines at17-18, one Row265 comment and the parser import in the pinned array, no other line changed (numstat2 added/0 deleted); seat/src/sandbox.mjs adds only `packages/bus/src/address.mjs` immediately after broker in RUNNER_FILES. seat/tests/sandbox.test.mjs stays byte-for-byte unchanged. The parser remains a separate pure module. A NEW block in address.test.mjs asserts `doesNotMatch(/\bimport\b/)` on its source, with a node:fs import in that new test file; no existing assertion is moved or weakened.
|
||||
|
||||
Review packet must explicitly call out the RUNNER_FILES mount-list entry for Boyd's review. This is a read-only source-file mount needed by the broker's static dependency, with no new module dependency or role/verb authority. The first-run import pin failure remains recorded above and in logs/03-bus.log; new logs will have rerun-prefixed names, preserving first-slot bytes.
|
||||
|
||||
Requested rerun order, awaiting CLEAR after Bentina: first `timeout -k 10 180 node --test --test-reporter=tap packages/seat/tests/sandbox.test.mjs` whole, then the original five steps again from step1: address whole (now15 cases), rooms whole, bus whole with concurrency1 (includes project-runtime whole), M01–M12 whole-room-file mutants, then restored address and rooms files separately. Explicit TAP reporter is used throughout for stable logs/kill matching; timeouts otherwise120 per new/mutant run and600 for bus. Each run still gets fresh short mktemp TMPDIR, date/uptime/busy header, serialized with the same stop rule. Estimate5–10minutes. Only static node --check and git diff --check run since the STOP; all pass. No new test run yet.
|
||||
|
||||
## Verified result — sage-3942 CLEAR, slot returned goldie-0034
|
||||
|
||||
All rerun positives exit 0 with zero failures, skips or cancellations. Full bus suite passed 957/957, including the approved project-runtime import pin. The unchanged sandbox test passed 10/10, including its recursive mount/import-closure assertion. New parser cases passed 15/15, rooms 18/18, and both passed again after restoration. No tests run after slot return.
|
||||
|
||||
| Run | Exit | Pass / fail | Duration ms | UTC / load / helper | Log |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| rerun-00-sandbox | 0 | 10 / 0 | 5526.027699 | Sat Oct 24 04:31:35 UTC 2026; 23:31:35 up 10 days, 14:51, 2 users, load average: 2.83, 2.91, 2.73; CLEAR | [log](logs/rerun-00-sandbox.log.gz) |
|
||||
| rerun-01-address | 0 | 15 / 0 | 54.634817 | Sat Oct 24 04:31:45 UTC 2026; 23:31:45 up 10 days, 14:51, 2 users, load average: 2.54, 2.84, 2.71; CLEAR | [log](logs/rerun-01-address.log.gz) |
|
||||
| rerun-02-rooms | 0 | 18 / 0 | 921.788987 | Sat Oct 24 04:31:52 UTC 2026; 23:31:52 up 10 days, 14:51, 2 users, load average: 2.42, 2.81, 2.70; CLEAR | [log](logs/rerun-02-rooms.log.gz) |
|
||||
| rerun-03-bus | 0 | 957 / 0 | 118397.418425 | Sat Oct 24 04:32:01 UTC 2026; 23:32:01 up 10 days, 14:51, 2 users, load average: 2.28, 2.77, 2.68; CLEAR | [log](logs/rerun-03-bus.log.gz) |
|
||||
| rerun-05-restored-address | 0 | 15 / 0 | 54.587943 | Sat Oct 24 04:35:02 UTC 2026; 23:35:02 up 10 days, 14:54, 2 users, load average: 3.10, 3.07, 2.83; CLEAR | [log](logs/rerun-05-restored-address.log.gz) |
|
||||
| rerun-05-restored-rooms | 0 | 18 / 0 | 991.877991 | Sat Oct 24 04:35:11 UTC 2026; 23:35:11 up 10 days, 14:55, 2 users, load average: 3.17, 3.09, 2.84; CLEAR | [log](logs/rerun-05-restored-rooms.log.gz) |
|
||||
|
||||
Mutant driver exit 0; all twelve target tests killed their guard relaxation with Node exit 1. Every mutant log records exact bytes restored to broker SHA256 `71b32ee4afa322793aad81231441ee7badc3e5aff65ff008c6cc0f7f63b72781`. Driver receipt: [rerun-04-mutants-driver.log](logs/rerun-04-mutants-driver.log.gz).
|
||||
|
||||
| Mutant | Node exit | Pass / fail | Exact diff | Raw log |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| M01-human | 1, killed | 16 / 2 | [diff](logs/M01-human.diff) | [log](logs/M01-human.log.gz) |
|
||||
| M02-tester | 1, killed | 17 / 1 | [diff](logs/M02-tester.diff) | [log](logs/M02-tester.log.gz) |
|
||||
| M03-project | 1, killed | 17 / 1 | [diff](logs/M03-project.diff) | [log](logs/M03-project.log.gz) |
|
||||
| M04-members | 1, killed | 17 / 1 | [diff](logs/M04-members.diff) | [log](logs/M04-members.log.gz) |
|
||||
| M05-collision | 1, killed | 14 / 4 | [diff](logs/M05-collision.diff) | [log](logs/M05-collision.log.gz) |
|
||||
| M06-open | 1, killed | 17 / 1 | [diff](logs/M06-open.diff) | [log](logs/M06-open.log.gz) |
|
||||
| M07-body | 1, killed | 17 / 1 | [diff](logs/M07-body.diff) | [log](logs/M07-body.log.gz) |
|
||||
| M08-unknown | 1, killed | 16 / 2 | [diff](logs/M08-unknown.diff) | [log](logs/M08-unknown.log.gz) |
|
||||
| M09-ambiguous | 1, killed | 17 / 1 | [diff](logs/M09-ambiguous.diff) | [log](logs/M09-ambiguous.log.gz) |
|
||||
| M10-messaging | 1, killed | 17 / 1 | [diff](logs/M10-messaging.diff) | [log](logs/M10-messaging.log.gz) |
|
||||
| M11-cursor | 1, killed | 17 / 1 | [diff](logs/M11-cursor.diff) | [log](logs/M11-cursor.log.gz) |
|
||||
| M12-fence | 1, killed | 17 / 1 | [diff](logs/M12-fence.diff) | [log](logs/M12-fence.log.gz) |
|
||||
|
||||
## Final review scope and packet
|
||||
|
||||
Boyd reviews the three room cases, pure parser/export, README rows, new parser/room tests, and **the added read-only RUNNER_FILES mount for packages/bus/src/address.mjs**. mos-1567 authorizes that one sandbox source line and the sole old-test delta: project-runtime.test.mjs:17-18 adds exactly the Row265 comment and parser import pin (numstat 2 added / 0 deleted). Byte comparison against base after removing those additions proves all other old-test bytes unchanged. Removing the single RUNNER_FILES addition likewise restores sandbox.mjs to base. sandbox.test.mjs has no byte change. New address.test.mjs block proves the mounted parser source has no import word.
|
||||
|
||||
The widened changed-file hash-context audit includes sandbox.mjs: [pin-context-with-sandbox.txt](pin-context-with-sandbox.txt), zero 64-hex-near-reference hits. The original reader and positive-control receipts remain alongside it; the missed source pin and first STOP remain recorded, not concealed. No role/schema/tester allowlist or helper change ships. BUILD-LOG and SESSIONS only append tracking entries.
|
||||
|
||||
Base: `ec86f2c847f6154a0677c6ed23c87dabc2c665d0`. Branch: `goldie/row265`. MANIFEST.sha256 lists every changed source/test/tracking/evidence file except itself, with repository-relative paths, and is verified before commit/push. Raw first-red logs are retained separately from green reruns.
|
||||
|
||||
Evidence packaging: logs are stored losslessly as `.log.gz`; decompress with `gzip -cd`. Decompression was byte-compared with each raw run log before packing. This preserves the original failure/Node whitespace while keeping the committed diff whitespace-clean. The run driver recreates raw `.log` files when used. Mutant busy-check headers identify only their own Python driver PID3686656 as BUSY; positive runs report CLEAR. The driver held the assigned slot throughout, with serialized child tests.
|
||||
@@ -0,0 +1,51 @@
|
||||
996e9d0c1e7a8436691ce5d5d89b4eb940b07aafb3386f63a9847d203f43d998 BUILD-LOG.md
|
||||
26b5821895a152ddac66a26f16585c6c48ec71d99ca7e7c2b9a4d5a71943c5b4 agents/goldie/work/queue-265/BUILD.md
|
||||
1289c0f9f4e857590490a0972b5300cc8fb1a86e3ef79fe63e47472709e982f6 agents/goldie/work/queue-265/logs/01-address.log.gz
|
||||
bc0ac95454ca4a4478641832e68caeb82bfe8b1fdc9abc0b80f1126ef42b7be5 agents/goldie/work/queue-265/logs/02-rooms.log.gz
|
||||
459a45bd772576847338a438b27f4f578a2a660be744e1404fd239d192c6957c agents/goldie/work/queue-265/logs/03-bus.log.gz
|
||||
c30cecb149e98d64f285e81a82d07a0a89dad5ea223f9031b543230615663bb5 agents/goldie/work/queue-265/logs/M01-human.diff
|
||||
a75e02d91ff46fbfaf627beea943183b769e417bcb572bf11f778f0fca7fcf29 agents/goldie/work/queue-265/logs/M01-human.log.gz
|
||||
b78b1c5b205272411710f56a981acf58dc09c46333369924b98c4c8657dc949c agents/goldie/work/queue-265/logs/M02-tester.diff
|
||||
6a680dcb1db461cce3a3a2e066aa1245e4f84aa6754354058009bc0f6ebf4185 agents/goldie/work/queue-265/logs/M02-tester.log.gz
|
||||
02a6cfab404405099fc39eb83413614c67a0c4c3f87cd23c3849a35d1055ddb0 agents/goldie/work/queue-265/logs/M03-project.diff
|
||||
8b7cd0c568510e49b61fa14fce7b2680025b773eda884e0a84163d240a124e39 agents/goldie/work/queue-265/logs/M03-project.log.gz
|
||||
18b2cb43f2799301cf934e4c327bc1a03e08ff8d11ff590cb9677c86dfd43ff0 agents/goldie/work/queue-265/logs/M04-members.diff
|
||||
195f9ec57c9494552566d2f8b87f3b17a210b811f8b556dd65db89814e237816 agents/goldie/work/queue-265/logs/M04-members.log.gz
|
||||
a773e214c76acf5285ac4ca17cdd90dd552ddc79157abe75ef6de7f67a0b6a70 agents/goldie/work/queue-265/logs/M05-collision.diff
|
||||
1148cda3f6aaa7f6aeaab2f95c932114ddeac5bfd9318a6b77a4148079d486ed agents/goldie/work/queue-265/logs/M05-collision.log.gz
|
||||
56400065c9cdb6305763b2bff8c0e06e9ae1223fa89b4467634a9951622d3fbe agents/goldie/work/queue-265/logs/M06-open.diff
|
||||
b8f9bd9a5c488e604d0afe3feda4a2602359a94a49c7fdbd2f0f6a421b0ca1ce agents/goldie/work/queue-265/logs/M06-open.log.gz
|
||||
2c1b745a15602cdcaf194131e4d6e0f8fa349e9712ce2e8cb6e695699e4ec19d agents/goldie/work/queue-265/logs/M07-body.diff
|
||||
4857461e7a2ceb01a6446acb4708a732949a0d8d1a85fa2f6d10bedde6b9e11b agents/goldie/work/queue-265/logs/M07-body.log.gz
|
||||
e3480e69d016caea6c1fbcdfa022b18214a10e8deca4409d0dc2f72261d2ee92 agents/goldie/work/queue-265/logs/M08-unknown.diff
|
||||
3d0f347f21df3c388a4f39679d4694aadc9684ae8b8ad2d30bf0bb316e48dcb0 agents/goldie/work/queue-265/logs/M08-unknown.log.gz
|
||||
62420ed20affcab256facee05414b8b760a4c79a60f60be0c06c88abb61ec6ae agents/goldie/work/queue-265/logs/M09-ambiguous.diff
|
||||
14a18d61344df592e885c46ace34e0c59914f4fbe591fa99eeb3d81fa8ef8bf1 agents/goldie/work/queue-265/logs/M09-ambiguous.log.gz
|
||||
0671bf77394f82df92252275171fd9edea2e5b2e7143387840d03bda544676bf agents/goldie/work/queue-265/logs/M10-messaging.diff
|
||||
4202215d40999e0d1cc98ebfb5445f0c8fd4688e6ca968e85d225723076ca8a7 agents/goldie/work/queue-265/logs/M10-messaging.log.gz
|
||||
042598ecb551e761d37af898f69e17ec137d2f39687dbf81ce5e1eeb98a38202 agents/goldie/work/queue-265/logs/M11-cursor.diff
|
||||
deff3d0514c379bd3d9c6f6bbc60db59b9dce2baaafd6fa611711c98cc8365ce agents/goldie/work/queue-265/logs/M11-cursor.log.gz
|
||||
780e70fbcb2328006c8d9ace75aa62fcd0d935b4d758b50b0f5c5d5c55604122 agents/goldie/work/queue-265/logs/M12-fence.diff
|
||||
b629b41a39c028b70d4b847c351d248d6d70e8175d6d27f563b0ed8817f89642 agents/goldie/work/queue-265/logs/M12-fence.log.gz
|
||||
1b8c3cec31b44ff018a1bc74fef95996bcbdd21d340e2d2605154ba645648040 agents/goldie/work/queue-265/logs/rerun-00-sandbox.log.gz
|
||||
d18c5a9432de06ebec36c937da7dfb1a3e79dc397b33ff624058af351eee24dd agents/goldie/work/queue-265/logs/rerun-01-address.log.gz
|
||||
d10b83ccec56e47d8c7c74d8304fc4fc87b8ea92a50098380cd02b8012c32d09 agents/goldie/work/queue-265/logs/rerun-02-rooms.log.gz
|
||||
6ff5955fe5c85f51c1cf4daeb8eddd61ef1a30e170221f99bfb08da8304d7e4a agents/goldie/work/queue-265/logs/rerun-03-bus.log.gz
|
||||
eeba0182f06cee0c06cd15ecf7ef43aaeffd7149b2202cc0653078c20bb46198 agents/goldie/work/queue-265/logs/rerun-04-mutants-driver.log.gz
|
||||
5fe9fe35af60ef951c5c68b040aa9bb6067d9c746e143bb5782eb428acd4e4fe agents/goldie/work/queue-265/logs/rerun-05-restored-address.log.gz
|
||||
2b3f3305ff5f6926a0269bd6c88d5f2e4a89727eefcf99625bebb51a70abb649 agents/goldie/work/queue-265/logs/rerun-05-restored-rooms.log.gz
|
||||
f11ae40d923affad1936fe51d05981ac201a1051bc9f877d75fc1d544c6e38f1 agents/goldie/work/queue-265/mutants.py
|
||||
e9cdf16670a16a14f3dd722a69c63a1785e4deb3608ee05d519b0ec8c1a9ca0b agents/goldie/work/queue-265/pin-context-grep.txt
|
||||
491ba3bb25e918049249178f3c6a67b2b4f56a197f0a67059acb2420cd03b73c agents/goldie/work/queue-265/pin-context-with-sandbox.txt
|
||||
784fb42bff187c0eebcf7756942ec3cf56f8faa7b54ce4ad96181b81fe46f0e9 agents/goldie/work/queue-265/pin-positive-control.txt
|
||||
766ead519eefbde958bbdbf77bf858b10dd1e3d9dee8ab5d356fe821ce7602fe agents/goldie/work/queue-265/reader-grep.txt
|
||||
6c3c44eb943f33d5bb547f542c77da4d6f04e738056466bc491c2f6fd5f67946 docs/SESSIONS.md
|
||||
1ba33af1f42c88a5346d25fe915a44ee70a27fbc6b9ab880db0866cbdad5d45d packages/bus/README.md
|
||||
232a1c77c2f17011c53b55fc6052f70985598210769d41d7c2c0b535328aad23 packages/bus/src/address.mjs
|
||||
71b32ee4afa322793aad81231441ee7badc3e5aff65ff008c6cc0f7f63b72781 packages/bus/src/broker.mjs
|
||||
ed57d48d2af4a225259e0a44c86a30b12792ec9f725dd9c89251569b7dc89e5a packages/bus/src/index.mjs
|
||||
a6725ffe30d44981a9b9d3a3c319f7e96a46ec7bd74f0c8d869d9a798eca1a81 packages/bus/tests/address-cases.mjs
|
||||
fae431bc3743b224f69da33c4cebe376b47257d67f52d25d3ab0cb59f224a607 packages/bus/tests/address.test.mjs
|
||||
8594e94384c1c8b7234cfa8bbc17456cff86d9adef936cd1059a0d5a60e4546b packages/bus/tests/project-runtime.test.mjs
|
||||
892c3709393fa8962710bc07565d1097bbd79ea1fbfda34af5f9385db34ed0fa packages/bus/tests/rooms.test.mjs
|
||||
83019fa6b39ad9fd79b84627856b4dec82683d8261b8cba9b9ac840e0c489268 packages/seat/src/sandbox.mjs
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M01-human
|
||||
@@ -6423,7 +6423,6 @@
|
||||
case 'room.open':
|
||||
case 'room.send':
|
||||
case 'room.read': {
|
||||
- this.#human(s);
|
||||
const opening = verb === 'room.open';
|
||||
const sending = verb === 'room.send';
|
||||
keys(a, opening ? ['project'] : sending ? ['project', 'body'] : ['project', 'after', 'limit'],
|
||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
--- broker.original
|
||||
+++ M02-tester
|
||||
@@ -122,7 +122,8 @@
|
||||
// Row 175 (#1673, Q173): the test system's reads and two writes, name.set and project.create,
|
||||
// each recorded with `tester` as its actor. The organization branch keeps its own list below.
|
||||
// Row 177 (#1675, Q173): the tester's own threads, owner `tester`, and a stop of their runs.
|
||||
-export const TESTER_VERBS = Object.freeze(['message.send', 'message.receive', 'message.read', 'inbox', 'agents', 'tasks', 'trail', 'decisions', 'messages', 'replies',
|
||||
+export const TESTER_VERBS = Object.freeze([
|
||||
+ 'room.open', 'room.send', 'room.read','message.send', 'message.receive', 'message.read', 'inbox', 'agents', 'tasks', 'trail', 'decisions', 'messages', 'replies',
|
||||
'usage', 'names', 'limits', 'plugins', 'instances', 'project.list', 'thread.list', 'name.set', 'project.create', 'credentials',
|
||||
'thread.create', 'thread.open', 'thread.send', 'thread.messages', 'assistant.stop',
|
||||
// Row 194 (#1692, D4.1): launch and terminate, and the read of the requests they make.
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M03-project
|
||||
@@ -6429,7 +6429,6 @@
|
||||
keys(a, opening ? ['project'] : sending ? ['project', 'body'] : ['project', 'after', 'limit'],
|
||||
sending ? ['project', 'body'] : ['project']);
|
||||
const project = identifier(a.project);
|
||||
- if (!this.#store.get('SELECT 1 FROM projects WHERE id=? AND business=?', project, s.business)) fail('project-not-found');
|
||||
const members = Object.keys(b.roles).filter((instance) => this.#threaded(s.business, instance)).sort();
|
||||
if (!members.length) fail('thread-instance-missing');
|
||||
const owner = this.#owner(s);
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M04-members
|
||||
@@ -6431,7 +6431,6 @@
|
||||
const project = identifier(a.project);
|
||||
if (!this.#store.get('SELECT 1 FROM projects WHERE id=? AND business=?', project, s.business)) fail('project-not-found');
|
||||
const members = Object.keys(b.roles).filter((instance) => this.#threaded(s.business, instance)).sort();
|
||||
- if (!members.length) fail('thread-instance-missing');
|
||||
const owner = this.#owner(s);
|
||||
// Preflight all collisions/missing threads before open creates or send writes anything.
|
||||
const threads = members.map((instance) => {
|
||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
--- broker.original
|
||||
+++ M05-collision
|
||||
@@ -6436,8 +6436,6 @@
|
||||
// Preflight all collisions/missing threads before open creates or send writes anything.
|
||||
const threads = members.map((instance) => {
|
||||
const t = this.#store.get('SELECT * FROM threads WHERE project=? AND name=?', project, instance);
|
||||
- if (t && (t.instance !== instance || t.owner !== owner))
|
||||
- fail(opening ? 'room-thread-name-taken' : 'room-not-open');
|
||||
if (!t && !opening) fail('room-not-open');
|
||||
return t ?? { instance, name: instance };
|
||||
});
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M06-open
|
||||
@@ -6438,7 +6438,6 @@
|
||||
const t = this.#store.get('SELECT * FROM threads WHERE project=? AND name=?', project, instance);
|
||||
if (t && (t.instance !== instance || t.owner !== owner))
|
||||
fail(opening ? 'room-thread-name-taken' : 'room-not-open');
|
||||
- if (!t && !opening) fail('room-not-open');
|
||||
return t ?? { instance, name: instance };
|
||||
});
|
||||
if (opening) {
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M07-body
|
||||
@@ -6450,7 +6450,6 @@
|
||||
if (sending) {
|
||||
string(a.body, 32768);
|
||||
const addressed = parseAddress(a.body);
|
||||
- if (!addressed.body.trim()) fail('invalid-request');
|
||||
const selected = new Set();
|
||||
for (const tag of addressed.tags) {
|
||||
const candidates = new Set([this.#named(s.business, tag), tag].filter((instance) => members.includes(instance)));
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M08-unknown
|
||||
@@ -6454,7 +6454,6 @@
|
||||
const selected = new Set();
|
||||
for (const tag of addressed.tags) {
|
||||
const candidates = new Set([this.#named(s.business, tag), tag].filter((instance) => members.includes(instance)));
|
||||
- if (!candidates.size) fail('unknown-addressee');
|
||||
if (candidates.size > 1) fail('addressee-ambiguous');
|
||||
selected.add([...candidates][0]);
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M09-ambiguous
|
||||
@@ -6455,7 +6455,6 @@
|
||||
for (const tag of addressed.tags) {
|
||||
const candidates = new Set([this.#named(s.business, tag), tag].filter((instance) => members.includes(instance)));
|
||||
if (!candidates.size) fail('unknown-addressee');
|
||||
- if (candidates.size > 1) fail('addressee-ambiguous');
|
||||
selected.add([...candidates][0]);
|
||||
}
|
||||
const targets = threads.filter((t) => !addressed.tags.length || selected.has(t.instance));
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M10-messaging
|
||||
@@ -6462,7 +6462,6 @@
|
||||
const sessions = targets.map((t) => {
|
||||
const session = this.#defaultSession(t.id);
|
||||
this.#sessionFence(session);
|
||||
- this.#checkMessaging(s.business, 'human', t.instance);
|
||||
return session;
|
||||
});
|
||||
const at = this.#now();
|
||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
--- broker.original
|
||||
+++ M11-cursor
|
||||
@@ -6488,7 +6488,7 @@
|
||||
`SELECT m.seq FROM messages m JOIN message_threads mt ON mt.message=m.id WHERE m.id=? AND m.business=? AND mt.thread IN (${placeholders})`,
|
||||
identifier(a.after), s.business, ...tids,
|
||||
)?.seq;
|
||||
- if (after === undefined) fail('message-not-found');
|
||||
+ if (after === undefined) after = 0;
|
||||
}
|
||||
// At most one row per instance joins a group; one lookahead row completes the last page.
|
||||
const rows = this.#store.all(
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
--- broker.original
|
||||
+++ M12-fence
|
||||
@@ -6461,7 +6461,6 @@
|
||||
const targets = threads.filter((t) => !addressed.tags.length || selected.has(t.instance));
|
||||
const sessions = targets.map((t) => {
|
||||
const session = this.#defaultSession(t.id);
|
||||
- this.#sessionFence(session);
|
||||
this.#checkMessaging(s.business, 'human', t.instance);
|
||||
return session;
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Slot-only runner. Every mutation restores and verifies the complete broker bytes."""
|
||||
from pathlib import Path
|
||||
import hashlib, os, re, subprocess, sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
SOURCE = ROOT / 'packages/bus/src/broker.mjs'
|
||||
OUT = Path(__file__).resolve().parent / 'logs'
|
||||
MUTANTS = [
|
||||
('M01-human', 'human-required', 'room verbs require the human', ' this.#human(s);\n', ''),
|
||||
('M02-tester', 'tester-refused', 'room verbs require the human', None, None),
|
||||
('M03-project', 'project-not-found', 'room verbs refuse missing projects', " if (!this.#store.get('SELECT 1 FROM projects WHERE id=? AND business=?', project, s.business)) fail('project-not-found');\n", ''),
|
||||
('M04-members', 'thread-instance-missing', 'room verbs refuse missing projects', " if (!members.length) fail('thread-instance-missing');\n", ''),
|
||||
('M05-collision', 'room-thread-name-taken', 'room.open preflights instance collisions', " if (t && (t.instance !== instance || t.owner !== owner))\n fail(opening ? 'room-thread-name-taken' : 'room-not-open');\n", ''),
|
||||
('M06-open', 'room-not-open', 'send/read refuse a missing canonical member', " if (!t && !opening) fail('room-not-open');\n", ''),
|
||||
('M07-body', 'invalid-request', 'unknown or non-member addressees and tags-only bodies', " if (!addressed.body.trim()) fail('invalid-request');\n", ''),
|
||||
('M08-unknown', 'unknown-addressee', 'unknown or non-member addressees and tags-only bodies', " if (!candidates.size) fail('unknown-addressee');\n", ''),
|
||||
('M09-ambiguous', 'addressee-ambiguous', 'a legacy name colliding with another member key', " if (candidates.size > 1) fail('addressee-ambiguous');\n", ''),
|
||||
('M10-messaging', 'message-sender-refused', 'messaging denial on the last target', " this.#checkMessaging(s.business, 'human', t.instance);\n", ''),
|
||||
('M11-cursor', 'message-not-found', 'room.read groups one send, pages', " if (after === undefined) fail('message-not-found');\n", ' if (after === undefined) after = 0;\n'),
|
||||
('M12-fence', 'session-clearing', 'a clearing last member fences the whole send', ' this.#sessionFence(session);\n', ''),
|
||||
]
|
||||
|
||||
def sha(data): return hashlib.sha256(data).hexdigest()
|
||||
|
||||
def main():
|
||||
OUT.mkdir(exist_ok=True)
|
||||
original = SOURCE.read_bytes()
|
||||
text = original.decode()
|
||||
start = text.index(" case 'room.open':")
|
||||
end = text.index(" // A thread belongs", start)
|
||||
block = text[start:end]
|
||||
for name, refusal, expected, old, new in MUTANTS:
|
||||
if old is None:
|
||||
needle = 'export const TESTER_VERBS = Object.freeze(['
|
||||
assert text.count(needle) == 1
|
||||
changed = text.replace(needle, needle + "\n 'room.open', 'room.send', 'room.read',", 1)
|
||||
else:
|
||||
assert block.count(old) == 1, (name, block.count(old))
|
||||
changed = text[:start] + block.replace(old, new, 1) + text[end:]
|
||||
# Save conventional original→mutant diff without relying on shell interpolation.
|
||||
import difflib
|
||||
(OUT / f'{name}.diff').write_text(''.join(difflib.unified_diff(text.splitlines(True), changed.splitlines(True), fromfile='broker.original', tofile=name)))
|
||||
tmp = subprocess.check_output(['mktemp', '-d', '/mnt/storage/scratch/tmp/XXXXXX'], text=True).strip()
|
||||
env = {**os.environ, 'TMPDIR': tmp}
|
||||
log = OUT / f'{name}.log'
|
||||
try:
|
||||
SOURCE.write_text(changed)
|
||||
with log.open('w') as stream:
|
||||
for command in [['date', '-u'], ['uptime'], ['bash', '-c', 'bash /mnt/storage/scratch/sage-busy-now.sh 2>/dev/null | tail -1']]:
|
||||
subprocess.run(command, cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, check=True)
|
||||
stream.write(f'TMPDIR={tmp}\nmutant={name} refusal={refusal} original_sha256={sha(original)}\n')
|
||||
stream.flush()
|
||||
result = subprocess.run(['timeout', '-k', '10', '120', 'node', '--test', '--test-reporter=tap', 'packages/bus/tests/rooms.test.mjs'],
|
||||
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT)
|
||||
output = log.read_text()
|
||||
killed = result.returncode == 1 and re.search(r'(?m)^not ok \d+ - ' + re.escape(expected), output)
|
||||
print(f'{name}: exit={result.returncode} target_killed={bool(killed)} log={log}', flush=True)
|
||||
if not killed: return 1
|
||||
finally:
|
||||
SOURCE.write_bytes(original)
|
||||
assert SOURCE.read_bytes() == original
|
||||
assert sha(SOURCE.read_bytes()) == sha(original)
|
||||
with log.open('a') as stream: stream.write(f'RESTORED sha256={sha(original)}\n')
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__': sys.exit(main())
|
||||
@@ -0,0 +1 @@
|
||||
No changed-file mention has a literal 64-hex digest within eight lines.
|
||||
@@ -0,0 +1 @@
|
||||
No changed-product-file mention has a literal 64-hex digest within eight lines.
|
||||
@@ -0,0 +1,6 @@
|
||||
packages/bus/src/store.mjs:15:const ddl = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8');
|
||||
packages/bus/src/store.mjs:16:export const SCHEMA_SHA256 = 'a2ba3a46f12e56139ecae9e55227d00847842aedec50c1d92aaae28844ca48e7';
|
||||
packages/bus/src/store.mjs:106:const hex = (value) => createHash('sha256').update(value).digest('hex');
|
||||
packages/bus/src/store.mjs:107:if (hex(ddl) !== SCHEMA_SHA256) throw Error('schema-source-mismatch');
|
||||
packages/bus/tests/schema.test.mjs:17: db.exec(readFileSync(new URL('../schema.sql', import.meta.url), 'utf8'));
|
||||
packages/bus/tests/schema.test.mjs:90: const hex = (s) => createHash('sha256').update(s).digest('hex');
|
||||
@@ -0,0 +1,133 @@
|
||||
packages/bus/tests/session-compactions.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/console-decide.test.mjs:14:import { Broker, CONSOLE_VERBS } from '../src/broker.mjs';
|
||||
packages/harness/tests/runner.test.mjs:14:import { Broker, BusError } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/project-files.test.mjs:8:import { PROJECT_FILE_VERBS } from '../src/broker.mjs';
|
||||
packages/cli/tests/lifecycle.test.mjs:19:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/lifecycle-verbs.test.mjs:11:import { Broker, LIFECYCLE_REQUEST_MS, TESTER_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/lifecycle-verbs.test.mjs:74: for (const verb of ['instance.launch', 'instance.end', 'instance.request']) assert.ok(TESTER_VERBS.includes(verb), verb);
|
||||
packages/bus/tests/lifecycle-verbs.test.mjs:75: assert.equal(TESTER_VERBS.includes('instance.regenerate'), false);
|
||||
packages/bus/tests/tasks.test.mjs:9:import { Broker, BusError, TASK_VERBS } from '../src/broker.mjs';
|
||||
packages/harness/tests/tools.test.mjs:4:import { ORGANIZATION_KEYS as BROKER_ORGANIZATION_KEYS, TASK_PAGE, TASK_PAGE_MAX } from "../../bus/src/broker.mjs";
|
||||
packages/seat/tests/session.test.mjs:11:import { Broker } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/decision-route.test.mjs:12:import { Broker } from '../src/broker.mjs';
|
||||
packages/cli/tests/thread-launch.test.mjs:19:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/seat/tests/sandbox.test.mjs:15:import { Broker } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/host-run.test.mjs:16:import { Broker, ORGANIZATION_KEYS } from '../src/broker.mjs';
|
||||
packages/bus/tests/host-run.test.mjs:181: // organization.set reaches the writer as limits.set (broker.mjs:1379).
|
||||
packages/cli/tests/helpers.mjs:10:import { BusError, Broker } from "../../bus/src/broker.mjs";
|
||||
packages/tasks/tests/fields.test.mjs:10:import { TASK_VERBS } from '../../bus/src/broker.mjs';
|
||||
packages/tasks/tests/fields.test.mjs:226: // decision back to that role (broker.mjs, decision.raise).
|
||||
packages/webui/tests/instances.test.mjs:15:import { BusError } from '../../bus/src/broker.mjs';
|
||||
packages/cli/tests/tester-threads.test.mjs:14:import { BusError, Broker } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/staff-request.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/tasks/tests/world.mjs:5:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/cli/tests/processing-cli.test.mjs:7:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/grant-host.test.mjs:10:import { Broker, registrySha256 } from '../src/broker.mjs';
|
||||
packages/business/tests/workflow-roles.test.mjs:115: // Q169 (broker.mjs END_PROTECTED, #hardWait): revoking the assistant is a hard wait as well.
|
||||
packages/business/tests/workflow-roles.test.mjs:136: // sage-0709: only recordLand spends a decision (broker.mjs #spent). checkLand refuses decision-consumed after
|
||||
packages/cli/tests/thread-revoke.test.mjs:18:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/tester-writes.test.mjs:10:import { Broker, TESTER_VERBS, TESTER_ORGANIZATION_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/tester-writes.test.mjs:18:test('row 175: TESTER_VERBS and the organization branch tester set are pinned', () => {
|
||||
packages/bus/tests/tester-writes.test.mjs:19: assert.deepEqual([...TESTER_VERBS], [
|
||||
packages/bus/tests/tester-writes.test.mjs:32: assert.ok(Object.isFrozen(TESTER_VERBS) && Object.isFrozen(TESTER_ORGANIZATION_VERBS));
|
||||
packages/cli/tests/stack-release.test.mjs:1993: for(const [f,text] of [['packages/cli/src/cli.mjs',id===A?'// fixture host from before row 163\nprocess.exit(1);\n':script(rHost)],['packages/webui/src/cli.mjs',script(rConsole)],['packages/bus/src/store.mjs',rStore(v,froms)],['packages/bus/src/migrate-schema.mjs',script(rMigrate)],['packages/bus/src/broker.mjs','export class BusError extends Error { constructor(code) { super(code); this.code = code; } }\n']]){mkdirSync(dirname(join(d,f)),{recursive:true});writeFileSync(join(d,f),text)}
|
||||
packages/cli/tests/session-cli.test.mjs:94: const { Broker } = await import('../../bus/src/broker.mjs');
|
||||
packages/cli/tests/session-launch.test.mjs:21:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/cli/tests/revoke-window.test.mjs:19:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/chief-of-staff.test.mjs:10:import { Broker, ACTIONS } from '../src/broker.mjs';
|
||||
packages/cli/tests/instances.test.mjs:13:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/cli/tests/thread-cli.test.mjs:14:import { BusError, Broker } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/processing.test.mjs:6:import { Broker } from '../src/broker.mjs';
|
||||
packages/cli/tests/idle-stop-race.test.mjs:17:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/cli/tests/tester-reads.test.mjs:12:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/cli/tests/session-clear.test.mjs:18:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/names.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/cli/tests/compactions-launch.test.mjs:24:import { BusError } from "../../bus/src/broker.mjs";
|
||||
packages/cli/tests/tester.test.mjs:7:import { BusError, Broker } from "../../bus/src/broker.mjs";
|
||||
packages/bus/tests/reload.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/agents.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/end-launch.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/webui/tests/decide-browser.test.mjs:14:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/credential-verbs.test.mjs:11:import { Broker, CREDENTIAL_VERBS } from '../src/broker.mjs';
|
||||
packages/webui/tests/send-browser.test.mjs:13:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/launch-notice.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/messages.test.mjs:3:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/workflow-roles.test.mjs:11:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/library-sync.test.mjs:11:import { Broker, LIBRARY_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/credential-status.test.mjs:12:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/gitea-grant-docker.test.mjs:15:import { Broker, GRANT_EVIDENCE } from '../src/broker.mjs';
|
||||
packages/bus/tests/thread-migration.test.mjs:11:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/comment-cursors.test.mjs:7:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/thread-claims.test.mjs:11:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/state.test.mjs:7:import { Broker, TASK_PAGE } from '../src/broker.mjs';
|
||||
packages/bus/tests/access-revoke.test.mjs:14:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/plugins-verb.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/transport.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/publish.test.mjs:14:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/publish.test.mjs:221: const { ACTIONS, HARD_WAITS } = await import('../src/broker.mjs');
|
||||
packages/bus/tests/tester-threads.test.mjs:11:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/tester-threads.test.mjs:284: // The row #specNotice (broker.mjs) writes for a human save of the spec in project stack. The save
|
||||
packages/bus/tests/approval-binding.test.mjs:7:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/fire-revokes-grants.test.mjs:15:import { Broker, GRANT_EVIDENCE, registrySha256 } from '../src/broker.mjs';
|
||||
packages/bus/tests/daily-quota-runtime.test.mjs:10:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/limit-checks.test.mjs:323: for (const file of ['../src/runtime.mjs', '../src/broker.mjs', '../src/process.mjs']) {
|
||||
packages/bus/tests/spec-fixtures.mjs:6:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/decisions.test.mjs:9:import { Broker, RECENT } from '../src/broker.mjs';
|
||||
packages/bus/tests/peer-transport.test.mjs:9:import { BusError } from '../src/broker.mjs';
|
||||
packages/bus/tests/peer-transport.test.mjs:47: const {TASK_VERBS,BusError}=await import('../src/broker.mjs');
|
||||
packages/bus/tests/messaging.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/rooms.test.mjs:8:import { Broker, THREAD_PAGE, THREAD_PAGE_MAX, TESTER_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/rooms.test.mjs:80: assert.ok(!TESTER_VERBS.includes(verb));
|
||||
packages/bus/tests/web.test.mjs:8:import { Broker, BusError, WEB_UNTRUSTED } from '../src/broker.mjs';
|
||||
packages/bus/tests/replies.test.mjs:11:import { Broker, REPLIES } from '../src/broker.mjs';
|
||||
packages/bus/tests/thread-shares.test.mjs:14:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/message-citation.test.mjs:6:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/limits.test.mjs:7:import {Broker} from '../src/broker.mjs';
|
||||
packages/bus/tests/host-fixtures.mjs:6:import { registrySha256, writeGrantEvent } from '../src/broker.mjs';
|
||||
packages/bus/tests/gitea-grant.test.mjs:12:import { Broker, GRANT_EVIDENCE, writeGrantEvent } from '../src/broker.mjs';
|
||||
packages/bus/tests/csuite-messaging.test.mjs:12:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/decision-close.test.mjs:9:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/grant-scope.test.mjs:14:import { Broker, GRANT_EVIDENCE, writeGrantEvent } from '../src/broker.mjs';
|
||||
packages/bus/tests/project-await.test.mjs:7:import { Broker, BusError } from '../src/broker.mjs';
|
||||
packages/bus/tests/launcher-released.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/limit-stop.test.mjs:12:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/message-filter.test.mjs:12:import { Broker, REPLIES } from '../src/broker.mjs';
|
||||
packages/bus/tests/land.test.mjs:18:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/single-use.test.mjs:7:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/session-clear.test.mjs:10:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/grant-migration.test.mjs:10:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/usage-broker.test.mjs:7:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/project-runtime.test.mjs:13: const source = readFileSync(new URL('../src/broker.mjs', import.meta.url), 'utf8');
|
||||
packages/bus/tests/task-read.test.mjs:7:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/library.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/instances-verb.test.mjs:11:import { Broker, LIFECYCLE_HUMAN_VERBS, ORGANIZATION_KEYS, TESTER_ORGANIZATION_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/routes-verb.test.mjs:13:import { Broker, ORGANIZATION_KEYS } from '../src/broker.mjs';
|
||||
packages/bus/tests/broker.test.mjs:10:const { Broker } = await import('../src/broker.mjs').catch((e) => {
|
||||
packages/bus/tests/grant-enroll.test.mjs:10:import { Broker, BusError, writeGrantEvent, registrySha256 } from '../src/broker.mjs';
|
||||
packages/bus/tests/hard-waits.test.mjs:7:import { Broker, ACTIONS, HARD_WAITS } from '../src/broker.mjs';
|
||||
packages/bus/tests/requirements.test.mjs:10:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/grant-executor.test.mjs:15:import { Broker, registrySha256, writeGrantEvent } from '../src/broker.mjs';
|
||||
packages/bus/tests/sessions.test.mjs:10:import { Broker } from '../src/broker.mjs';
|
||||
packages/bus/tests/user-principal.test.mjs:14: Broker, ACTIONS, USER_VERBS, TESTER_VERBS, TASK_VERBS, WEB_VERBS, HOST_VERBS, HOSTS_VERBS, CREDENTIAL_VERBS,
|
||||
packages/bus/tests/user-principal.test.mjs:16:} from '../src/broker.mjs';
|
||||
packages/bus/tests/user-principal.test.mjs:137: ...ACTIONS, ...TESTER_VERBS, ...TESTER_ORGANIZATION_VERBS, ...PROJECT_FILE_VERBS, ...LIBRARY_VERBS, ...CREDENTIAL_VERBS, ...HOSTS_VERBS,
|
||||
packages/bus/tests/tester.test.mjs:10:import { Broker, TASK_VERBS } from '../src/broker.mjs';
|
||||
packages/bus/tests/threads.test.mjs:9:import { Broker, THREAD_PAGE } from '../src/broker.mjs';
|
||||
packages/webui/tests/routes-browser.test.mjs:15:import {BusError} from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/creative.test.mjs:8:import { Broker } from '../src/broker.mjs';
|
||||
packages/webui/tests/granted-user.test.mjs:16:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/task-page.test.mjs:7:import { Broker, TASK_PAGE, TASK_PAGE_MAX } from '../src/broker.mjs';
|
||||
packages/webui/tests/user-option-swap.test.mjs:14:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/webui/tests/board-held.test.mjs:20:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/webui/tests/chat-sessions-access.test.mjs:10:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/review.test.mjs:14:import { Broker } from '../src/broker.mjs';
|
||||
packages/webui/tests/sso-browser.test.mjs:14:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/revoke-window.test.mjs:13:import { Broker } from '../src/broker.mjs';
|
||||
packages/webui/tests/bus.test.mjs:13:import { Broker } from '../../bus/src/broker.mjs';
|
||||
packages/bus/tests/viewer.test.mjs:13: Broker, ACTIONS, USER_VERBS, VIEWER_VERBS, BOARD_VERBS, TESTER_VERBS, TESTER_ORGANIZATION_VERBS, PROJECT_FILE_VERBS,
|
||||
packages/bus/tests/viewer.test.mjs:14:} from '../src/broker.mjs';
|
||||
packages/bus/tests/viewer.test.mjs:116: const others = new Set([...ACTIONS, ...TESTER_VERBS, ...TESTER_ORGANIZATION_VERBS, ...PROJECT_FILE_VERBS, ...BOARD_VERBS, 'no.such.verb']);
|
||||
packages/webui/tests/tester-fixture.mjs:12:import { Broker, BusError } from '../../bus/src/broker.mjs';
|
||||
packages/webui/tests/routes.test.mjs:12:import { BusError } from '../../bus/src/broker.mjs';
|
||||
packages/webui/tests/chat.test.mjs:191: const { Broker } = await import('../../bus/src/broker.mjs');
|
||||
packages/webui/tests/bus-browser.test.mjs:11:import { Broker } from '../../bus/src/broker.mjs';
|
||||
@@ -0,0 +1,56 @@
|
||||
# Row 326 / #1822 — five run-sheet gaps
|
||||
|
||||
Goldie, 2026-10-24. Branch goldie/row-run-sheet in /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row-run-sheet. Base local refactor4ec279dc43922b338a94718a2afdaba87ef89c15, explicitly assigned by sage-3893. Brief docs/plans/2026-10-24_run-sheet-doc-brief.md at4ec279dc4, blobce3e24fa, section Brief: five run-sheet gaps. Reviewer Gladstone; Sage posts review and owns landing/full gate.
|
||||
|
||||
## Before / after and choices
|
||||
|
||||
Before: the sheet omitted proof bytes/newline handling, treated render refusal without distinguishing metadata/content, omitted missing-project warning consequences and a pre-install key check, offered no first-install retry form, and omitted the bus-relative backup checksum form.
|
||||
|
||||
After: the sheet covers all five cases in their existing sections6/9. Numbered section headings are unchanged. Only this BUILD.md and docs/guides/host-run-sheet.md are edited, per the brief's explicit two-file boundary. Session/build-phase registration and the manifest are recorded here rather than adding changes to SESSIONS.md, BUILD-LOG.md or a third manifest file. No host action or credential read. No suite or product command executed, and no CLEAR needed for this doc-only row (decision137).
|
||||
|
||||
| Gap / claim | Source read and how it supports the text |
|
||||
| --- | --- |
|
||||
| Proof format, optional LF | webui/src/serve.mjs:257-267 opens no-follow, checks regular/uid/mode600-or400, strips exactly one trailing LF at263, then requires32+ ASCII base64url chars at264. Table states the allowed alphabet and one optional trailing LF. Extra newline/CR is outside that alphabet. |
|
||||
| Render versus start | case2-unit.sh:397-408 explicitly stats/realpaths the proof and never opens it. webui/src/serve.mjs:267 throws the content error; webui/src/cli.mjs:138-140 catches and sets exit2. Table now distinguishes metadata refusal at render from bad bytes at Console start. |
|
||||
| Missing project warning | business/src/cli.mjs:74-78 reports absent plus the no-project-variables warning, then continues. case2-install.sh:217-221 captures validate output and prints business-validates on success. Text makes that incomplete install possible and tells operators to stop on the missing-file warning, per Duckworth row323 N2. |
|
||||
| Pre-install key check | business/src/cli.mjs:71-82 checks every file-backed project, skips kind library, and requires loaded id==business key at80. Read-only inline Node uses only builtins and section2's pinned node, as mosaic. It reads the exact intended business input and files under each project's root, prints a match, and exits nonzero with STOP for read/JSON/id failure. Expected .53 output comes from .mosaic/project.json:3 and case1-business.mosaic-stack.json:10-11. Fixing business rather than project id is mos-1563. |
|
||||
| Why check before install | case2-install.sh:178 makes the copy before validation at217. New check is explicitly before case2-install.sh and needs no existing Mosaic config, npm modules or installed wrapper. |
|
||||
| Refused first-install residue | case2-install.sh:172/174 creates/bootstraps the config; bootstrap.sh:13 dispatches to mosaic-config.mjs, whose412-424 creates config.json or validates an existing one without rewriting it. case2-install.sh:200-211 may create businesses/file and library slots. case2-persona-import.mjs:300-308 may create library personas. Validation cleanup at case2-install.sh:217-219 removes only this run's new business file, retaining dirs. Copy may already exist under the path at install:80/178; stack-copy.sh:68 reuses an existing copy. |
|
||||
| Retry paths / refs | Mos-1565/ops-0291, as pinned in the brief, names exactly root checkout, mosaic clone and default config dir to move aside. UTC-suffixed mv is root-only documentation; no delete, chown or chmod. Destination collision/move failure prints STOP. Retry starts section6's first-install block again. The existing upgrade fetch targets only refs/remotes/origin/refactor; stack-copy.sh:44-48 requires reachability from both it and refs/heads/refactor. |
|
||||
| Credential re-placement | Existing sheet section9 names claude-session.env, library proof and library-gitea with owner/mode. Moving config aside preserves them but the new config needs Jason to re-place any already-filled files, under mos-1565. Existing per-agent slots still require credentials issue/Approve, never hand placement. No value or command to read a credential added. |
|
||||
| Backup hand check | stack-release.sh:475-482 creates activation/bus and writes SHA256SUMS from inside bus. restore at502 and rehearsal at2244 cd bus and check ../SHA256SUMS. New hand command does the same as mosaic, without --quiet so the expected bus.sqlite:OK is visible; ops-0292 records why checking in activation itself fails. |
|
||||
|
||||
## Read-only grep gate
|
||||
|
||||
Executed after editing, with no test/suite run:
|
||||
|
||||
```
|
||||
git grep -n host-run-sheet -- 'packages/*/tests/*' 'scripts/*'
|
||||
# no output, exit1
|
||||
|
||||
git grep -n case1-business.mosaic-stack.json -- 'packages/*/tests/*' 'scripts/*'
|
||||
packages/business/tests/case1-business-file.test.mjs:13:const DOCUMENT = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
|
||||
packages/business/tests/case1-project-key.test.mjs:9:const CASE1 = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
|
||||
# exit0: positive control, same command form, both tests read this document
|
||||
|
||||
git grep -n -E '(host-run-sheet.*[0-9a-f]{64}|[0-9a-f]{64}.*host-run-sheet)' -- 'packages/*/tests/*'
|
||||
# no output, exit1: no 64-hex test pin beside the sheet name
|
||||
```
|
||||
|
||||
An earlier positive control with case2-install.sh also found the real installer spawn in case1-business-file.test.mjs:39 and the CLI installer tests. The sheet-name search finds no reference at all in tests/scripts, stronger than just finding no same-line pin. No read/pin blocker.
|
||||
|
||||
Static syntax only: extracted the three new shell snippets, replaced only the version placeholder for parsing, and ran bash-n3/3. Extracted Node heredoc parsed with node --check --input-type=module. No snippet body executed. git diff --check passes. Source citations checked against this base; no new em dash and section numbering unchanged.
|
||||
|
||||
## Session / claimant receipt
|
||||
|
||||
2026-10-24 | Goldie a13dd6d5-5e73-4b7d-8318-02abeff24940 | Row326/#1822 doc-only build | Five gaps documented with source lines and grep control; no suites/host/credential action. Claim goldie-0326-start-1 rev3793, briefed→in-progress. In-review follows the pushed candidate; expected tokenless pre-send failure will be handed to Sage.
|
||||
|
||||
## Inline manifest
|
||||
|
||||
Only the sheet needs a content pin; BUILD.md is this receipt in the same candidate commit. An inline manifest honors the brief's two-file limit:
|
||||
|
||||
```
|
||||
e4c14516edff3bfbc81ea553a5a6ccebb6154942473540659d4681c2d56c9711 docs/guides/host-run-sheet.md
|
||||
```
|
||||
|
||||
Reviewer validates the five source chains and operational order. Sage's landing gate owns suites. No code row265 work started; its brief comes separately.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Gyro
|
||||
|
||||
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1571, as the W2 brief names. Owns row 310, O2 W2:
|
||||
the WordCraft source import and reproducible restricted web build (docs/plans/2026-10-21_q195-writing-editor.md,
|
||||
"W2 WordCraft source import and reproducible restricted web build").
|
||||
|
||||
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 `gyro/*` branches through the repository credential helper (mos-0981), never `refactor`,
|
||||
`main` or another seat's branch. Claude seats orchestrate and give the Opus 5.5 landing review (mos-1571). Registered by
|
||||
Sage on 2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
|
||||
@@ -0,0 +1,323 @@
|
||||
# Row 296: FED R7, scratch two-instance acceptance (#1802)
|
||||
|
||||
## Start
|
||||
|
||||
- Started 2026-10-23 18:53:27Z, on Sage's sage-3583 (mos-1489).
|
||||
- Base: origin/refactor 45dca69f6 (pinned; local copy used, no fetch: fetch runs the credential node script, which
|
||||
is held during Darkwing's re-pin window).
|
||||
- Branch: quackerjack/row296 in worktree quackerjack-fed-r5-row296.
|
||||
- Brief: docs/plans/2026-10-23_mvp-readiness-rows.md at 45dca69f6, section "Brief: FED R7, scratch two-instance
|
||||
acceptance" (line 77).
|
||||
- Constraints from Sage: scratch roots only (mktemp -d under /mnt/storage/scratch/tmp), never ~/.mosaic-dev or
|
||||
the live bus. No push, claim, queue write, node or test run until CLEAR.
|
||||
|
||||
## What the brief asks for (summary)
|
||||
|
||||
- One script test under scripts/ with its harness in packages/*/tests/, as row 190's tests are.
|
||||
- Two configs, two data roots, two Consoles, one fake OP. Consoles start outside agent ancestry in a transient
|
||||
user unit. They never use the live stack's units, ports or sockets. The script removes its units on exit (trap).
|
||||
- Test (design section 7 R7 gate): Jason's test identity, granted collaborator on one hub project, signs in,
|
||||
makes a thread, sends to the granted instance, gets the reply in the thread, is refused on another project and
|
||||
on board reads, is revoked, is refused again, and the run ends. The factory shows nothing of the hub.
|
||||
- Mutants: grant project list widened to `*`; revocation claim end skipped.
|
||||
- Gate: Filbert approves the manifest; the script test and every scripts/test-*.sh green on Sage's gate; no unit,
|
||||
port or socket of the script outlives the run. R8 (live window) stays row 209.
|
||||
|
||||
## Open reads before design
|
||||
|
||||
- R4 and R6 candidates (the brief says it builds on their combined candidate). Confirm what they expose: the
|
||||
hub sign-in, grant and revocation paths, and the Console's board-read refusal.
|
||||
- Row 190's test harness (several instances on one uid): how it starts units and cleans up.
|
||||
- Transient user unit support on this host (systemd-run --user), and how the existing tests detect it.
|
||||
|
||||
## Status
|
||||
|
||||
- 18:53Z: worktree created, brief read. No code yet.
|
||||
|
||||
## Handoff 2026-10-23 19:10:09Z (Mos ruling Q245, mos-1503)
|
||||
|
||||
Row 296 moves to Lena (Codex seat, thread db27faca-85fb-4ca0-9fc7-78d740d3fa3a, branch lena/row296). Quackerjack
|
||||
stops here: no builds, no claim, no queue writes. This design is the handoff; Lena continues it.
|
||||
|
||||
Base: 45dca69f6. R4 landed as 2efd127ce (ancestor of the base, checked with merge-base --is-ancestor). R6 landed as
|
||||
d3059c2c3 (row 294). R7 is claimable once Darkwing's window ends (Sage, sage-3592).
|
||||
|
||||
What I verified by reading (not by running):
|
||||
- packages/cli/src/access.mjs:65 reads `system.federation?.peers ?? []` (the R6 seam).
|
||||
- packages/bus/src/user-cli.mjs exists at base (the user-cli transport).
|
||||
- Row 190's tests (packages/cli/tests/stack-instances.test.mjs, stack-release.test.mjs T56, stack-release-fixture.mjs)
|
||||
drive stand-in units and fixtures. They do not start real transient user units. Not a model for R7's Consoles.
|
||||
- Real transient-unit launch pattern: packages/conversation/src/cohort.mjs (`scopeAvailable()` and `ScopeLauncher`,
|
||||
systemd-run --user --scope -p Delegate=yes --unit=...; MANAGER_ENV = XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS).
|
||||
- Test-only spawn of systemd-run in packages/conversation/tests is the nearest precedent for start/stop cleanup.
|
||||
|
||||
What I did NOT verify (so treat as open): the hub sign-in, grant, revocation and board-read paths in R4/R6 code; the
|
||||
Console startup flags; whether the fake OP can be a local stand-in; the trap-based unit cleanup.
|
||||
|
||||
Design (proposal, not yet checked against code):
|
||||
1. scripts/test-federation-acceptance.sh: the entry point. Creates a mktemp -d scratch root under
|
||||
/mnt/storage/scratch/tmp (never ~/.mosaic-dev, never the live bus). trap removes every unit it started and the
|
||||
scratch root. Refuses to run if a transient unit name collides.
|
||||
2. Two configs (instance A = hub-side granted seat, instance B = collaborator's home), each with its own
|
||||
MOSAIC_CONFIG, data root and federation.self; federation.peers names the other.
|
||||
3. One fake OP: a local stand-in that issues Jason's test identity. Its port and socket are per-run, not the live ones.
|
||||
4. Two Consoles, each in its own transient user unit (systemd-run --user --unit=mosaic-r7-<run>-<A|B>); outside agent
|
||||
ancestry; ports picked per run.
|
||||
5. Test flow (the R7 gate): identity -> grant on one hub project -> sign in -> thread -> send to granted instance ->
|
||||
reply in thread -> refused on another project -> refused on board reads -> revoke -> refused after revoke -> run
|
||||
ends. Factory output must show nothing of the hub.
|
||||
6. Mutants: (a) grant project list widened to `*`, must fail the other-project refusal; (b) revocation claim end
|
||||
skipped, must fail the refusal-after-revoke check.
|
||||
|
||||
Unresolved decisions for Lena (I did not decide these):
|
||||
- Whether the harness lives in packages/cli/tests (matching row 190) or packages/conversation/tests (matching
|
||||
cohort.mjs). Brief says packages/*/tests/; pick the one that owns the Console start path.
|
||||
- Fake OP: in-process HTTP stand-in versus file-backed identity. No preference recorded.
|
||||
- Port selection: fixed range per run vs OS-assigned. Brief forbids the live ports; it does not pick a range.
|
||||
|
||||
R2 and R5 learning:
|
||||
- R2 (row 203, #1701) landed as a4da4e6df. I built it. Relevant to R7: validateFederation in
|
||||
scripts/mosaic-config.mjs rejects unknown keys and bad ids, and test-config.sh has an FED block
|
||||
(expect_fed_refusal). Use federation.self/peers exactly as row 203 validates them.
|
||||
Mutant runner pattern: one textual edit that must occur exactly once, then a named test must fail; restore from git.
|
||||
Name filter gotcha: `node --test --test-name-pattern` skips subtests whose parent does not match; include the
|
||||
parent name (T57 for row 203).
|
||||
- R5: I did not build R5 in this session and have no first-hand notes on it. Ask Sage for R5's landing sha and notes.
|
||||
|
||||
Status: design written; no code, no test runs, no claim, no push of the test files. Next owner: Lena.
|
||||
|
||||
## Lena continuation, 2026-10-23
|
||||
|
||||
Owner: Lena, thread db27faca-85fb-4ca0-9fc7-78d740d3fa3a. Branch lena/row296.
|
||||
Base remains 45dca69f6. Quackerjack's handoff c6ab9bdc9 preserved by cherry-pick ad84c1e45.
|
||||
Issue #1802 exists. Reviewer Filbert. Sage owns queue state, shared test slot and landing.
|
||||
Status: Untested. All Node, tests and credential-helper fetch/push held for Sage CLEAR (sage-3615).
|
||||
The earlier canonical `queue next lena` read finished with `nothing` and a rendered-view warning;
|
||||
no queue state was changed. Lena's owner registration is Sage's action.
|
||||
|
||||
### Design decisions sent to Sage (lena-0003)
|
||||
|
||||
- Harness under packages/cli/tests, beside row 190's instance proof; shell entry under scripts/.
|
||||
- Existing HTTPS fakeOidc helper, its generated scratch CA trusted through NODE_EXTRA_CA_CERTS
|
||||
by each real Console CLI. One fake OP, two client ids; no TLS verification bypass.
|
||||
- Kernel-assigned loopback ports (0), discovered from each Console's startup log. Public origins
|
||||
are distinct *.example.test HTTPS names; HTTP requests model the loopback reverse proxy with
|
||||
exact Host and Origin. This proves the Console boundary, not a deployed HTTPS front end (R8).
|
||||
- Transient user service units, not scopes: a scope leaves ancestry beneath the caller; the real
|
||||
user proof walks ancestry. Each Console runs the existing cli.mjs serve --sso --send verb.
|
||||
- Real startHost broker process and createLauncher. Only the harness adapter is a test stand-in,
|
||||
as in thread-revoke.test.mjs. A real sandboxed run replies through the broker.
|
||||
- Scratch-only setup and access grant/revoke use real human CLI transport from a transient service.
|
||||
No injected principal verifier, direct bindHuman/bindUser, or simulated user transport.
|
||||
- Every service has a unique per-run prefix and is recorded before launch. Shell EXIT/INT/TERM
|
||||
traps stop only these services, wait for collection and verify socket and port closure.
|
||||
- Claim-end assertion reads append-only claim history independently of the launcher's final
|
||||
shutdown so the claim-end mutant cannot be hidden by endLaunch's cleanup.
|
||||
|
||||
### Implementation plan
|
||||
|
||||
Goal: an executable scratch proof of federation section 7's R7 gate, without live state.
|
||||
Architecture: shell lifecycle owner, Node test driver, test-only host fixture, existing real Console CLI.
|
||||
Tech: Bash, Node ESM/node:test, systemd user transient services, existing fake OP and bwrap adapter.
|
||||
Spec: docs/plans/2026-10-23_mvp-readiness-rows.md, FED R7, plus preserved handoff above.
|
||||
|
||||
Constraints: scratch only under /mnt/storage/scratch/tmp; no live units/ports/sockets/configs or
|
||||
credential reads; no production behavior/policy changes; no old test edits without Mos via Sage.
|
||||
|
||||
Review focus: partial startup cleanup; signals; unit name collision; mismatched hub/factory sessions;
|
||||
claim end versus process exit. Each must have explicit assertions or a negative cleanup exercise.
|
||||
|
||||
- [ ] Write acceptance driver assertions: Jason test sub binds as collaborator (owner is another
|
||||
fixture identity), one allowed project/instance, thread send and correlated RESULT, private
|
||||
project creation/read refused, board reads refused, factory has no hub objects, revoke denies
|
||||
session and re-login, claim history records access-revoked and runner ends.
|
||||
- [ ] Write shell lifecycle: scratch HOME/TMPDIR, unique unit ledger, bounded command timeout,
|
||||
traps stop/collect only this run's units, verify recorded sockets and ports no longer listen.
|
||||
- [ ] Write host fixture using startHost(bootConfig), createLauncher with fakeHarnesses and the
|
||||
existing thread adapter. Prepare two named configs through bootstrap before fixture edits;
|
||||
supply only generated test client slots. Setup/grant/revoke through real human transport.
|
||||
- [ ] On Sage CLEAR run only the new script, fix failures in new files and record exact results.
|
||||
- [ ] Prepare explicit once-only mutant patches: R5 projects widened to ['*']; R4 #claimEnd call
|
||||
skipped. On Sage CLEAR prove each gives an assertion failure, restore bytes in finally.
|
||||
- [ ] Publish manifest (paths, hashes, assertion mapping, old-test numstat), request Filbert review,
|
||||
push additive commits to lena/row296 only. Sage runs shared suites and gate and lands the row.
|
||||
|
||||
Pre-existing test changes: none (numstat: empty).
|
||||
|
||||
### Source inspection corrections before first execution
|
||||
|
||||
- The real webui chat is channel-gated. Scratch businesses enable only harness pi and channel webui;
|
||||
the loader fixture re-exports the unchanged webui channel. The adapter replacement is exactly
|
||||
fakeHarnesses' pi.adapter export; no product file gains a fixture branch.
|
||||
- startHost creates a real broker child and a real Unix proof socket; it is hosted in the test
|
||||
driver. The two Console services run the exact same-tree webui CLI, which is required by
|
||||
verifyPrincipal. Human setup runs the exact same-tree human-cli through humanTransport.
|
||||
- The earlier in-process host proposal did not need a separate host fixture: startHost exposes
|
||||
the production launcher API directly, so the only helper file is federation-human.mjs.
|
||||
- Read-only SQLite inspects claim evidence while the broker remains the sole writer. The test
|
||||
also checks the registered runner and engine pids/start times are gone after revocation.
|
||||
- `--kill-after-start` kills the test process after both services have listened; Node teardown
|
||||
hooks cannot run. The shell trap owns service cleanup independently. This is an expected-red
|
||||
exercise; acceptance requires its kill marker, nonzero exit and successful cleanup line.
|
||||
- Main driver environment is a whitelist (PATH, scratch HOME/TMPDIR, systemd manager transport,
|
||||
R7 run ids). Console/human services use env -i. No host preload or credential env is carried.
|
||||
- No Node or test has run. Bash syntax and git diff --check passed during preparation.
|
||||
|
||||
### Binding design correction: sage-3625, 2026-10-23 19:17:35Z
|
||||
|
||||
Sage rejected the transient-unit approach: "no transient systemd user units used to satisfy the
|
||||
human proof"; such a unit makes the ancestry walk miss the development agent, and creates host
|
||||
state under mos-1438. This direction replaces the brief/handoff's unit requirement for this build.
|
||||
No unit was created by the superseded draft, and no Node/test execution occurred.
|
||||
|
||||
Current design, replacing the earlier proposals above:
|
||||
|
||||
- Consoles are ordinary spawned child processes in their own process groups; no systemd commands,
|
||||
units, user manager or manager environment are involved.
|
||||
- Test-only federation-host.mjs follows channel-fixture.mjs's mock.module pattern. It re-exports
|
||||
real readProcess and replaces verifyHuman's readProcess argument with real process fields plus
|
||||
ppid=1. This is explicitly a stand-in for Jason's shell, NOT a real ancestry acceptance proof.
|
||||
Real pid/uid/startTime/nonce/path checks still run; verifyPrincipal still checks the Console's
|
||||
actual direct parent path for user calls. No product source receives this override.
|
||||
- Each fixture host runs real startBroker and createLauncher. Its local host API is the existing
|
||||
thread-revoke.test.mjs pattern, with only the pi adapter replaced via fakeHarnesses.
|
||||
- Setup/grant/revoke are plain children using real CLI wrappers against that fixture proof seam.
|
||||
- Shell records each detached group's leader pid and start time, checks both before signalling,
|
||||
and verifies no live group member, TCP listener or Unix listener remains. Killed-driver mode
|
||||
bypasses every Node teardown hook and still leaves cleanup to the shell trap.
|
||||
- Read-only claim evidence and actual runner/engine exit remain independent assertions.
|
||||
- Gate limitation: this proves the revised scratch integration, not real outside-agent ancestry,
|
||||
transient unit cleanup, TLS proxy deployment or live federation. R8 remains row 209.
|
||||
- R5 landing found in git log: ddfb29af7 (#1700).
|
||||
- Targeted Node/tests remain held. Sage permits the first lena/row296 push in the 19:41Z gap,
|
||||
with MOSAIC_GITEA_CREDENTIAL_FILE unset. 226 and 283 have earlier slots.
|
||||
|
||||
Current files: shell entry; federation-acceptance.test.mjs; fixtures/federation-host.mjs and
|
||||
fixtures/federation-human.mjs; this BUILD; mutant runner; manifest; append-only logs; preserved
|
||||
Quackerjack BUILD at ad84c1e45. No pre-existing test modification (numstat empty).
|
||||
|
||||
### First candidate, 2026-10-23
|
||||
|
||||
Implementation is prepared under sage-3625, not yet executed. Static checks: Bash parser accepts
|
||||
scripts/test-federation-acceptance.sh; Python ast.parse accepts mutants.py; git diff --check clean.
|
||||
No test outcome is claimed. The root node_modules links the existing canonical install so the
|
||||
future targeted run needs no install or network. No webui editor build is involved.
|
||||
|
||||
M1 now requires the user project-list leak assertion (`R7 ungranted project must not appear`),
|
||||
so it proves exposure through the actual Console transport rather than only inspecting grant
|
||||
configuration. The private-project thread-create refusal and exact grant-list check follow it.
|
||||
M2 requires `R7 revoke must end the claim as access-revoked` before any host teardown.
|
||||
|
||||
Sage opened the push gap early in sage-3644 (19:23:04Z). The first candidate is committed as
|
||||
Untested, for push only to lena/row296 with MOSAIC_GITEA_CREDENTIAL_FILE unset. Node, acceptance,
|
||||
negative cleanup exercise and mutants still wait for the test slot. Full suites/gate are Sage's.
|
||||
|
||||
### Filbert static review, filbert-296-first-push-static, 19:28:51Z
|
||||
|
||||
Review pinned f06a1f78c against 45dca69f6. Filbert verified all nine manifest hashes from Git
|
||||
objects, no product/pre-existing test changes, no systemd calls, fixture-only ancestry override,
|
||||
real path/uid/startTime/nonce checks, group ledger identity checks and child-only CA trust.
|
||||
He ran nothing. The verdict remains pending execution and gate evidence.
|
||||
|
||||
- C1 confirmed by inspection: subprocess.run(timeout=280) kills the shell on timeout and can
|
||||
bypass its trap, while the shell's own deadline plus cleanup can exceed 280 seconds. Removed
|
||||
the outer Python timeout (the simpler alternative Filbert explicitly allowed). Python now
|
||||
waits for the shell's bounded run and EXIT/TERM cleanup. Source restoration remains in finally.
|
||||
- N1 retained limitation: a SIGKILL between spawn and the pid/startTime ledger append can leave
|
||||
an unrecorded group. The killed-driver exercise begins after both Consoles are recorded and
|
||||
does not cover that gap. Cleanup claims apply to recorded groups; no universal crash-proof
|
||||
process creation claim is made.
|
||||
- N2 addressed before the mutant run: the named readonly claim-history assertion now runs after
|
||||
revoke and before waiting for the run's end. A skipped #claimEnd therefore cannot first fail
|
||||
the run-end wait. The final latest-op check still runs after engine/runner exit. If M2 instead
|
||||
fails an earlier condition, its runner must say NOT KILLED; diagnose the first failure on the
|
||||
same pinned candidate, retain the intended assertion, and rerun on Sage CLEAR. Never accept a
|
||||
timeout or change the mutant's expected assertion merely to mark it killed.
|
||||
- N3 deferred cosmetic fixture detail: wrap() returns fn(value) without await, so async rejection
|
||||
skips its code mapping. This is not an authorization or cleanup change; the rejection still
|
||||
propagates, and no verdict relies on that fixture mapping.
|
||||
- N4: corrected the exact-grant assertion's indentation in the new test.
|
||||
- N5: prepare() first calls bootstrap --instance, then overwrites that bootstrap-generated
|
||||
config inside the unique disposable scratch HOME with fixture Console/provider settings.
|
||||
This is fixture-only setup, not a product/bootstrap overwrite path or a live-config operation.
|
||||
There remains one selected config per scratch instance, with the same marked data root.
|
||||
|
||||
Still Untested. No Node, acceptance, mutant, negative cleanup or CLI suite run. Static validation
|
||||
only; execution waits for Sage's test window. Follow-up push needs a currently authorized gap.
|
||||
|
||||
### Push receipt and N2 ordering proof, 2026-10-23 19:31Z
|
||||
|
||||
Sage sage-3659 authorized push before 19:38Z. e855f82e1846f094927c3de69aea1a801253736a
|
||||
was pushed to origin/lena/row296 with MOSAIC_GITEA_CREDENTIAL_FILE unset. No Node/tests ran.
|
||||
|
||||
N2, filbert-296-c1-ack: this path is synchronous after a successful reload. accessCommand awaits
|
||||
reload at access.mjs:289; reload awaits the access.reload transport at :297. broker.mjs:4645
|
||||
calls #revokeLost before constructing its reply. #revokeLost (:1025–1034) calls #claimEnd inside
|
||||
Store.transaction, which executes COMMIT before returning (store.mjs:282–302). server.mjs:160–161
|
||||
calls broker.request and then writes the response. Therefore the claim record is committed before
|
||||
the successful CLI call returns; no asynchronous sweep is required for this assertion. A refused
|
||||
reload can be reported by the CLI with exit 0, but then immediate revocation evidence is absent
|
||||
and this test must fail; that is not a race the test should hide. No polling change is needed.
|
||||
|
||||
T3 package comparison (sage-3659): the four planned runs execute only the new dedicated
|
||||
federation-acceptance.test.mjs and its fixture host/human children. They do not execute stack-copy,
|
||||
stack-release or the whole CLI suite. Existing helpers are imported; no old test module is run.
|
||||
Asked Researcher to confirm against row216. Full CLI suite remains a later Sage gate requirement,
|
||||
outside the targeted T3 command list. All test resources are unique scratch state.
|
||||
|
||||
### R2 rebase and slot start, 2026-10-24, sage-4035/4036/4038
|
||||
|
||||
New branch lena/row296-r2, base e0c6142f3a411ed70e32b2c50d01930e0b0641c8.
|
||||
Original branch remains a1321f8f9; no force push. Rebased tip before evidence: 17e421cf4.
|
||||
Only conflicts were append-only BUILD-LOG.md and docs/SESSIONS.md: retained all new-base bytes
|
||||
and appended this row's original suffix. All other row files byte-identical to a1321f8f9.
|
||||
Range-diff receipt: run-r2/range-diff.txt. Patch IDs: run-r2/patch-ids.txt.
|
||||
Mapping: ad84c1e45=4a16ebaa4, f06a1f78c→6418b1b12 (only append-log context),
|
||||
e855f82e1=bbc38067f, a1321f8f9=17e421cf4. Stable patch IDs match for 1/3/4;
|
||||
commit 2 differs because patch-id includes changed append-log context. No source delta.
|
||||
|
||||
The prior local root node_modules symlink was unlinked without following it. Own-worktree root
|
||||
npm ci --ignore-scripts --no-audit --no-fund: exit 0, 128 packages. Own-worktree webui same
|
||||
command: exit 0, 2 packages. Both node_modules are real local directories. Canonical dependencies
|
||||
were never installed, removed or changed. This supersedes the earlier link-based setup note.
|
||||
|
||||
Root install had completed before sage-4036 arrived. Exact queue command from worktree refused
|
||||
linked-worktree policy; reported as lena-0013 and held tests. Sage-4038 authorized queue verbs
|
||||
only in canonical; no npm/rm there. All further tests run in this worktree under short scratch
|
||||
TMPDIR, serially: plain script, killed-driver negative exercise, M1, M2, touched new test whole.
|
||||
Filbert cleared C1 on a1321f8f9 (filbert-296-c1-cleared); rebase preserves those source bytes.
|
||||
|
||||
### Scoped acceptance results, 2026-10-24 09:55–09:57Z
|
||||
|
||||
Queue start succeeded: lena-296-start rev3888, briefed→in-progress, through the authorized
|
||||
canonical queue route. Earlier retries refused Sage assignment locks; no lock bypass occurred.
|
||||
|
||||
Commands ran serially in this worktree, each with a newly created six-character external TMPDIR
|
||||
under /mnt/storage/scratch/tmp and timeout -k 10. The script itself additionally creates its own
|
||||
unique r7.* root and owns child group cleanup. All five external TMPDIRs were empty and removed
|
||||
afterward (run-r2/tmpdir-cleanup.txt). No product/test source changed to obtain these results.
|
||||
|
||||
| Run | Exit | Node pass/fail/skip/cancel | Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| plain script, timeout 600 | 0 | 1/0/0/0 | run-r2/plain-1.log, 3.788 s Node duration |
|
||||
| --kill-after-start, timeout 600 | 1 expected; negative wrapper 0 | 0/1/0/0 expected SIGKILL | run-r2/killed-1.log, both-Consoles-started marker and cleanup line |
|
||||
| M1 driver, timeout 600 | 0 (script 1 expected) | 0/1/0/0 expected mutation | run-r2/M1.log, KILLED by R7 ungranted project must not appear |
|
||||
| M2 driver, timeout 600 | 0 (script 1 expected) | 0/1/0/0 expected mutation | run-r2/M2.log, KILLED by R7 revoke must end the claim as access-revoked |
|
||||
| new test whole without script context, timeout 60 | 0 | 0/0/1/0 | run-r2/whole-file.log, expected dedicated-script skip |
|
||||
|
||||
Every script run printed exactly `R7 cleanup: no child groups, ports or sockets remain; no units
|
||||
created`. The killed-driver run exercised shell cleanup with no Node teardown hooks. M1 and M2
|
||||
failed on assertions, not startup failure or timeout; the runner found the named assertion and
|
||||
cleanup marker. pre-mutants.sha256 checked after each mutation: access.mjs and broker.mjs both
|
||||
restored byte-for-byte. git diff against e0c6142f3 confirms neither product path changed.
|
||||
|
||||
No unexpected first-run red: plain acceptance passed on its first run. Only the deliberate killed
|
||||
and mutated runs failed. The ordinary-file skip is intentional; the plain script ran its full test
|
||||
without skips. Module-mocking experimental/deprecation warnings are fixture-only. No old test
|
||||
was run or changed, so no mos-1272 placement was needed. N3 and N6 remain optional diagnostics;
|
||||
N1's documented spawn-to-ledger SIGKILL gap remains outside the killed-driver scenario.
|
||||
|
||||
Scope per sage-4035: dedicated script, touched new test whole, both mutants and cleanup checks.
|
||||
No full CLI suite, unrelated package suite, webui build or other scripts/test-*.sh ran in this slot.
|
||||
Those broader earlier review/gate requirements remain for Sage's integration gate; no full gate
|
||||
claim is made here. R8/live acceptance, real ancestry proof and deployment remain out of scope.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Row 296 / #1802 — R2 scoped acceptance verified; review pending
|
||||
|
||||
Base: e0c6142f3a411ed70e32b2c50d01930e0b0641c8. Branch: lena/row296-r2. Reviewer: Filbert. Gate owner: Sage.
|
||||
Handoff: c6ab9bdc9 preserved as ad84c1e45, rebased as 4a16ebaa4, including
|
||||
agents/quackerjack/work/queue-296/BUILD.md. Every changed path is in MANIFEST.sha256,
|
||||
except the checksum file itself (self-hash is excluded). No product file or pre-existing test changed.
|
||||
Pre-existing test numstat: empty. BUILD-LOG.md and docs/SESSIONS.md are append-only additions.
|
||||
|
||||
Binding ruling: sage-3625 replaces the brief's transient-unit start with plain child Consoles and
|
||||
explicit test stand-ins. **This candidate does not prove genuine outside-agent ancestry.**
|
||||
It creates no transient systemd unit and makes no systemd CLI call. R8 remains row 209.
|
||||
|
||||
## What is real, and what is a stand-in
|
||||
|
||||
- Two configs created by bootstrap --instance hub/factory, two marked data roots, real Console
|
||||
cli.mjs serve --sso --send, real broker runtime and launcher, real path-bound human-cli and
|
||||
user-cli wrappers, real access loader, CLI writes/reload, grant filters and revocation.
|
||||
- One existing HTTPS fakeOidc server, two issuer/client identities. Its generated CA is trusted
|
||||
only through NODE_EXTRA_CA_CERTS in this test's child environment, never any trust store.
|
||||
- federation-host.mjs replaces verifyHuman's readProcess argument with actual process fields plus
|
||||
ppid=1, exactly the test pattern Sage named. This stands in for Jason's shell on setup,
|
||||
grant/revoke and both Consoles' transports. The actual Console parent/path check remains.
|
||||
There is no product parameter, environment switch or altered verifier default.
|
||||
- fakeHarnesses replaces only the pi harness adapter() with a scratch shell invoking the existing
|
||||
thread-adapter.mjs. The existing webui channel is re-exported unchanged. No network model runs.
|
||||
- Public HTTPS origins are modelled through HTTP loopback requests with matching Host/Origin;
|
||||
reverse-proxy TLS deployment is outside this scratch proof.
|
||||
|
||||
## Acceptance map
|
||||
|
||||
All checks live in `FED R7 scratch two-instance acceptance` in
|
||||
packages/cli/tests/federation-acceptance.test.mjs.
|
||||
|
||||
| Requirement | Assertion/evidence |
|
||||
| --- | --- |
|
||||
| Isolated instances | different config paths/data roots/ports; scratch HOME/TMPDIR; two named markers from bootstrap; port 0, forbidden live ports 7330–7332 checked |
|
||||
| Jason is collaborator, not owner | owner melanie; access user jason-test, home factory, fake sub jason-test-sub; hub /api/config user=true |
|
||||
| One project, one granted instance | actual access grant CLI, projects=[shared], instances=[assistant]; user project list contains shared only |
|
||||
| Thread and reply | create thread on shared, POST message, correlated RESULT with in_reply_to=sent.id, from_role=assistant, to_role=user, body contains question |
|
||||
| Other project refused | private project absent from listing; create on private returns 403 access-refused |
|
||||
| Board reads refused | /api/board, /api/bus/inbox and /api/bus/tasks return 403 owner-only |
|
||||
| Factory sees no hub objects | Jason sub refused at factory; factory owner sees factory-only project and no threads; hub cookie gets 401 at factory |
|
||||
| Revoke takes effect | actual access revoke CLI; existing hub session 401 and re-login 403 |
|
||||
| Claim and process ended | live run before revoke; end log exitCode=0; no registered sessions; engine and runner pid/startTime gone; readonly claim history has revoke/access-revoked before the process-end wait, latest op=revoke afterward |
|
||||
| Normal cleanup | EXIT trap terminates only recorded pid/startTime-checked groups; checks no live group member, no recorded TCP/Unix listener, no other scratch Unix listener, removes scratch directory/socket files |
|
||||
| Killed-driver cleanup | --kill-after-start prints both-Consoles-started marker then SIGKILLs test driver; shell performs the same independent cleanup; expected nonzero run with successful cleanup line |
|
||||
|
||||
## Verification commands (executed on sage-4035 CLEAR)
|
||||
|
||||
- scripts/test-federation-acceptance.sh — must exit 0, one acceptance test passes, cleanup line present.
|
||||
- scripts/test-federation-acceptance.sh --kill-after-start — must exit nonzero, print the explicit
|
||||
interruption marker and `R7 cleanup: no child groups, ports or sockets remain; no units created`.
|
||||
- python3 agents/lena/work/queue-296/mutants.py M1 — once-only R5 grant projects -> ['*']; must fail
|
||||
`R7 ungranted project must not appear` and verify cleanup, then restore original source bytes. Python relies on the shell's own run deadline and cleanup; no outer killing timeout.
|
||||
- python3 agents/lena/work/queue-296/mutants.py M2 — once-only R4 claim end omitted; must fail
|
||||
`R7 revoke must end the claim as access-revoked` before shutdown, verify cleanup and restore bytes.
|
||||
- Sage integration gate remains separate: every scripts/test-*.sh and full applicable suites per Sage; not run in this scoped slot.
|
||||
|
||||
Status: scoped gate passed: plain 1/1 (exit 0), killed-driver expected exit 1 with verified cleanup, M1/M2 drivers exit 0 with named assertion kills and source restoration; normal whole-file invocation exit 0 with one intentional dedicated-script skip. All cancellations 0. Receipts in run-r2/; counts and commands in BUILD.md.
|
||||
The script never silently skips a missing tool/sandbox/provider: prerequisites fail the acceptance.
|
||||
Ordinary CLI suites skip the dedicated test until invoked by its owning script.
|
||||
|
||||
Static-review follow-up: filbert-296-first-push-static. C1 removes Python's outer timeout so it
|
||||
cannot SIGKILL the shell before EXIT cleanup. N2 moves the named claim-history assertion before
|
||||
the process-end wait. N1's spawn-to-ledger SIGKILL gap is retained and documented in BUILD.md;
|
||||
--kill-after-start exercises the fully recorded groups. N3 remains a cosmetic fixture limitation.
|
||||
N4 indentation corrected. N5 scratch-bootstrap config overwrite is explicitly fixture-only.
|
||||
|
||||
R2 provenance: source identical to the statically cleared a1321f8f9. Rebase mapping/range-diff
|
||||
and stable patch IDs are in run-r2/. Only append-log conflict context changed. No unexpected
|
||||
first-run failure, no old test changed/run, and no broader suite or shared gate claimed.
|
||||
Manifest coverage is the complete changed-path set over e0c6142f3..candidate, including all
|
||||
text evidence and this description; only MANIFEST.sha256 itself is excluded from its own hash.
|
||||
@@ -0,0 +1,29 @@
|
||||
97a7d3518ac0e3f823c631d6616cea3de277040c673acd016f5bb0efd627938f BUILD-LOG.md
|
||||
5d040043c9ef15a5343ac4bc8382051c67c6bce13f273b7b16071e9e16e42c5d agents/lena/work/queue-296/BUILD.md
|
||||
1b853af963c754152466ae77a27e2136087192abd8cc912045b6d95c4c094e11 agents/lena/work/queue-296/MANIFEST.md
|
||||
bd6e9fd9e3f7aa0ee6702401776ef7bd3e39562059a11b87aaaf2a427904381b agents/lena/work/queue-296/mutants.py
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/lena/work/queue-296/run-r2/M1.exit
|
||||
50ac6f0ea22cc671e4056385fc2feb4d015bfe853a9393a60a6cb38a10ad6cb2 agents/lena/work/queue-296/run-r2/M1.log
|
||||
5079eaa8fcc6964072f7ba20333ba77607fa25729697182151bb3739f3ab15f8 agents/lena/work/queue-296/run-r2/M1.tmpdir
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/lena/work/queue-296/run-r2/M2.exit
|
||||
793b40f72e600ca71d6a29d058b2f11b4aaa0388dd80e8e6f8a925f46af589d7 agents/lena/work/queue-296/run-r2/M2.log
|
||||
01ef19b3acc3ac7f8cf0e69e31b8a0ef5413264109be154987993e0c8ce845d5 agents/lena/work/queue-296/run-r2/M2.tmpdir
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/lena/work/queue-296/run-r2/killed-1.exit
|
||||
6333824a3def94fc983f4d9ede08ae3301dd31921fae350cec77e9b7c220c963 agents/lena/work/queue-296/run-r2/killed-1.log
|
||||
67809d95a8b102392c23ed05e347ad461fc6eee8b66b0a732251be476ca99f08 agents/lena/work/queue-296/run-r2/killed-1.tmpdir
|
||||
c8a0c8d9d9a2c723aab5cfcb973713903a0914ec46394215533531f17a7eeeaf agents/lena/work/queue-296/run-r2/patch-ids.txt
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/lena/work/queue-296/run-r2/plain-1.exit
|
||||
ae2d6a16a97907384f992ef847be503eae0124bd378648fd620717450d56f2f7 agents/lena/work/queue-296/run-r2/plain-1.log
|
||||
254a104ac320b5b8e7ed792b373e5ca3af0febb94d705dc62b54fc6545fe9395 agents/lena/work/queue-296/run-r2/plain-1.tmpdir
|
||||
97858a98531f635813404f1c2a149f9e3d620444ca4cddb49b1e035cb91fa73d agents/lena/work/queue-296/run-r2/pre-mutants.sha256
|
||||
f431f734d74169b9c0932dedadf4cb2bac7ecc37e0ee93f1009b90c5b7bf6541 agents/lena/work/queue-296/run-r2/range-diff.txt
|
||||
36d4dcb2e1a816b5bdde0c1f47510d7e059f86810b75f6e10a5ca91c8341fe4b agents/lena/work/queue-296/run-r2/tmpdir-cleanup.txt
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/lena/work/queue-296/run-r2/whole-file.exit
|
||||
615834d38c4b90b0dc04529c6fc7f280a6e30e1b81c1658bd2e7d5a55e932e01 agents/lena/work/queue-296/run-r2/whole-file.log
|
||||
6e9863193aa62750f7ce89f17484973d5d85ef61980782d353c7ad6bd255b130 agents/lena/work/queue-296/run-r2/whole-file.tmpdir
|
||||
87239327014c49abbe130c17881bb48e13d96545bf9f3e09b6f4aed46e11453b agents/quackerjack/work/queue-296/BUILD.md
|
||||
04512fb4693bc135131857f204addfcc4968c660a00327c2589f5a107f591a2b docs/SESSIONS.md
|
||||
89a15e62aa6f951596b764993afeff7e33eeaa3a4f45d31599eba7d27a4000ad packages/cli/tests/federation-acceptance.test.mjs
|
||||
86b76565f5afe9efdd076ef61cc54e40c447d235f77b95818d0d601ce036effa packages/cli/tests/fixtures/federation-host.mjs
|
||||
b12a9bad4be083f2439c3363738b9763fa0061228f84d501a385ade48fea90e4 packages/cli/tests/fixtures/federation-human.mjs
|
||||
4008361f062a2f21b6075c500060196e10a48351fd7d95b25d681f36b4fa801a scripts/test-federation-acceptance.sh
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""R7 mutants. Run only on Sage CLEAR in Lena's isolated candidate.
|
||||
|
||||
Every replacement must occur once. Product bytes are restored even on timeout
|
||||
or failure. A killed mutant must contain the named assertion in the script's
|
||||
output; startup errors and timeout alone are not evidence. The shell owns its
|
||||
own timeout and EXIT/TERM cleanup. Do not put a killing Python timeout around it.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
repo = Path(__file__).resolve().parents[4]
|
||||
mutants = {
|
||||
'M1': (
|
||||
'packages/cli/src/access.mjs',
|
||||
'projects: resolveProjects(list(parsed.values["--projects"]), rows),',
|
||||
'projects: ["*"],',
|
||||
'R7 ungranted project must not appear',
|
||||
),
|
||||
'M2': (
|
||||
'packages/bus/src/broker.mjs',
|
||||
"this.#claimEnd({ business: t.business }, h, 'revoke', null, 'access-revoked', by);",
|
||||
"/* R7 mutant: skip ending the revoked claim */",
|
||||
'R7 revoke must end the claim as access-revoked',
|
||||
),
|
||||
}
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in mutants:
|
||||
raise SystemExit('usage: mutants.py M1|M2')
|
||||
name = sys.argv[1]
|
||||
file, before, after, assertion = mutants[name]
|
||||
path = repo / file
|
||||
original = path.read_bytes()
|
||||
text = original.decode()
|
||||
if text.count(before) != 1:
|
||||
raise SystemExit(f'{name}: anchor count {text.count(before)}, expected 1')
|
||||
try:
|
||||
path.write_text(text.replace(before, after))
|
||||
result = subprocess.run(['scripts/test-federation-acceptance.sh'], cwd=repo,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True)
|
||||
print(result.stdout, end='')
|
||||
if result.returncode == 0 or assertion not in result.stdout:
|
||||
raise SystemExit(f'{name}: NOT KILLED by expected assertion')
|
||||
if 'R7 cleanup: no child groups, ports or sockets remain; no units created' not in result.stdout:
|
||||
raise SystemExit(f'{name}: cleanup not verified')
|
||||
print(f'{name}: KILLED by {assertion}')
|
||||
finally:
|
||||
path.write_bytes(original)
|
||||
if path.read_bytes() != original:
|
||||
raise SystemExit(f'{name}: restore failed')
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,59 @@
|
||||
✖ FED R7 scratch two-instance acceptance (1924.232002ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2002.66244
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/federation-acceptance.test.mjs:143:1
|
||||
✖ FED R7 scratch two-instance acceptance (1924.232002ms)
|
||||
AssertionError [ERR_ASSERTION]: R7 ungranted project must not appear
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
'd27c7d26-5ea3-446c-b1ce-f54183e16c8a',
|
||||
+ 'c1cb5a81-e752-4932-be3c-d849e5a34284'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/lena-row296/packages/cli/tests/federation-acceptance.test.mjs:185: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: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [ 'd27c7d26-5ea3-446c-b1ce-f54183e16c8a', 'c1cb5a81-e752-4932-be3c-d849e5a34284' ],
|
||||
expected: [ 'd27c7d26-5ea3-446c-b1ce-f54183e16c8a' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
R7 diagnostic: factory-console.log
|
||||
Mosaic Console: http://127.0.0.1:43315/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: factory
|
||||
Sign-in: https://factory.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:35689/factory/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
R7 diagnostic: factory-host.log
|
||||
(node:889203) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:889203) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
R7 diagnostic: hub-console.log
|
||||
Mosaic Console: http://127.0.0.1:34081/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: hub
|
||||
Sign-in: https://hub.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:35689/hub/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
R7 diagnostic: hub-host.log
|
||||
(node:889107) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:889107) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
R7 cleanup: no child groups, ports or sockets remain; no units created
|
||||
M1: KILLED by R7 ungranted project must not appear
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/9Uoj48
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,60 @@
|
||||
✖ FED R7 scratch two-instance acceptance (3143.370798ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3218.651521
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/federation-acceptance.test.mjs:143:1
|
||||
✖ FED R7 scratch two-instance acceptance (3143.370798ms)
|
||||
AssertionError [ERR_ASSERTION]: R7 revoke must end the claim as access-revoked
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/lena-row296/packages/cli/tests/federation-acceptance.test.mjs:226: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: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
R7 diagnostic: factory-console.log
|
||||
Mosaic Console: http://127.0.0.1:40569/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: factory
|
||||
Sign-in: https://factory.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:37617/factory/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
2026-10-24T09:56:19.857Z sso refused code=subject-unknown provider=test-op iss="https://127.0.0.1:37617/factory/" sub="jason-test-sub"
|
||||
R7 diagnostic: factory-host.log
|
||||
(node:892054) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:892054) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
R7 diagnostic: hub-console.log
|
||||
Mosaic Console: http://127.0.0.1:44529/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: hub
|
||||
Sign-in: https://hub.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:37617/hub/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
2026-10-24T09:56:19.257Z chat action=createThread user="jason-test" id=58f85ce3-825f-42ea-9b19-e407157147f1
|
||||
2026-10-24T09:56:19.387Z chat action=send user="jason-test" id=3e803a2f-e10d-4ede-b80e-c7ca3fcc564b
|
||||
2026-10-24T09:56:19.836Z chat action=createThread user="jason-test" error=access-refused
|
||||
2026-10-24T09:56:20.395Z sso refused code=user-revoked provider=test-op user="jason-test"
|
||||
2026-10-24T09:56:20.401Z sso refused code=subject-unknown provider=test-op iss="https://127.0.0.1:37617/hub/" sub="jason-test-sub"
|
||||
R7 diagnostic: hub-host.log
|
||||
(node:892013) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:892013) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
usage: {"event":"usage-unknown","business":"hub","run":"r892d587abe73","instance":"assistant","turn":"3e803a2f-e10d-4ede-b80e-c7ca3fcc564b","source":"@none","code":"baseline-unknown"}
|
||||
mosaic-bus: run r892d587abe73 (assistant) ended: stopped
|
||||
R7 cleanup: no child groups, ports or sockets remain; no units created
|
||||
M2: KILLED by R7 revoke must end the claim as access-revoked
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/wPuOGf
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,41 @@
|
||||
R7 interruption: both Consoles started; killing test driver
|
||||
✖ packages/cli/tests/federation-acceptance.test.mjs (2055.463978ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2061.099035
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/federation-acceptance.test.mjs:1:1
|
||||
✖ packages/cli/tests/federation-acceptance.test.mjs (2055.463978ms)
|
||||
'test failed'
|
||||
R7 diagnostic: factory-console.log
|
||||
Mosaic Console: http://127.0.0.1:42937/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: factory
|
||||
Sign-in: https://factory.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:40605/factory/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
R7 diagnostic: factory-host.log
|
||||
(node:887305) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:887305) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
R7 diagnostic: hub-console.log
|
||||
Mosaic Console: http://127.0.0.1:42267/
|
||||
Board: http://127.0.0.1:1
|
||||
Bus: hub
|
||||
Sign-in: https://hub.example.test/ (--sso), reads and human writes (--send), grant Approve and Deny
|
||||
Sign-in provider: test-op issuer https://127.0.0.1:40605/hub/ subjects 1 (ready)
|
||||
Proxy: proxy proof off
|
||||
Ctrl-C stops this server.
|
||||
R7 diagnostic: hub-host.log
|
||||
(node:887261) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
(node:887261) DeprecationWarning: mock.module(): options.namedExports is deprecated. Use options.exports instead.
|
||||
R7 cleanup: no child groups, ports or sockets remain; no units created
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/bZhAyq
|
||||
@@ -0,0 +1,8 @@
|
||||
old ad84c1e45cf2327b9584c38328db02fb9d5dfc63 af9d89f4c65a791b0aeabe7bf1f19c6709c50bfb
|
||||
old f06a1f78cf135b0cd749e4602b28f6e63c177c85 da6478f871cd450ff3ee43e14b4f6a1d1a1deb59
|
||||
old e855f82e1846f094927c3de69aea1a801253736a 6065440666f6fc49254352e259ecbc5b0a4b40c3
|
||||
old a1321f8f90b41295245859ec92bb5ec4801ede84 d1a7f19fabc3f0c604eda8051312ef1efb50e74e
|
||||
rebased 4a16ebaa4ac96bfac2a0e3787f4437467a13650b af9d89f4c65a791b0aeabe7bf1f19c6709c50bfb
|
||||
rebased 6418b1b12382186e82048cf9aa223b851a23d4d0 2d2d8a223bce50d30fc3525da5f1cda9c38254cd
|
||||
rebased bbc38067f2d6100e58020bc197a84e6251fef83d 6065440666f6fc49254352e259ecbc5b0a4b40c3
|
||||
rebased 17e421cf4c9a3fd0cf432de0205cc1efff7f6044 d1a7f19fabc3f0c604eda8051312ef1efb50e74e
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,10 @@
|
||||
✔ FED R7 scratch two-instance acceptance (3684.148824ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3787.998681
|
||||
R7 cleanup: no child groups, ports or sockets remain; no units created
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/vgwsGf
|
||||
@@ -0,0 +1,2 @@
|
||||
c4949a16173d11f9d32d10d3ddd58a181631848b259bef1998820a78f5694808 packages/cli/src/access.mjs
|
||||
71b32ee4afa322793aad81231441ee7badc3e5aff65ff008c6cc0f7f63b72781 packages/bus/src/broker.mjs
|
||||
@@ -0,0 +1,31 @@
|
||||
1: ad84c1e45 = 1: 4a16ebaa4 row 296 handoff: FED R7 design notes to lena/row296 (mos-1503, Q245)
|
||||
2: f06a1f78c ! 2: 6418b1b12 Untested: row 296 scratch two-instance acceptance (#1802)
|
||||
@@ Commit message
|
||||
Untested: row 296 scratch two-instance acceptance (#1802)
|
||||
|
||||
## BUILD-LOG.md ##
|
||||
-@@ BUILD-LOG.md: Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
|
||||
- 2026-10-23T17:29:46Z | Gyro Vikunja571 sage-3542 Connections amendment | Branch gyro/editor-connections-design cut exactly from landed PRD1.17 30e6bb680; read REQ-LIB-5 in that PRD. Replaced stale J-b waits in parent and W4. Proposed W8 owns current authorized Markdown/DOCX links/backlinks list, labels, versioned refresh, leakage refusals and fixture tests. Build order under mos-1470/comment961 is W7 closed routing candidate, W8 landing, then W7 human first-real-import opening. No REQ-LIB-8 condition or acceptance change. Q234/mos-1363/S0 M1-M14 and post-10-27/MVP priority fixed. Static diff, twelve brief sections and append-only checks pass. No repo Node, tests, build, browser, queue mutation or live action. Fenton reviews after authorized docs branch push; Sage lands on approval.
|
||||
+@@ BUILD-LOG.md: remain; no live state, schema or bus policy change.
|
||||
|
||||
- 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-24 | Goldie | Row265 / #1770 verified (mos-1567, sage-3942) | Approved correction adds solely address import pin + Row265 comment to project-runtime.test and one pure-source RUNNER_FILES mount; new block checks parser has no imports. Serialized fresh-short-TMP timeout-k10: unchanged sandbox10/10, parser15/15, rooms18/18, full bus957/957, twelve named refusal mutants killed (each Node1, driver0), restored parser15/15 + rooms18/18. All positives zero fail/skip/cancel; every mutation restored full broker SHA71b32ee4. Old first-red evidence retained, no other old-test change. Slot returned goldie-0034; Boyd packet explicitly includes mount-list review, candidate push next.
|
||||
+ 2026-10-24T07:18:53Z | Sage | Row 327 (#1825, Bradford, Poe and Dewey) LANDED 68c08b4e7. Before: refactor at 94d9e90a6; the push guard was a scratch script with no reviewed-binary check. After: scripts/push-check.sh and scripts/test-push-check.sh on refactor, 3 commits cherry-picked onto 6a5e12511 with blobs equal to the round 3 tip d858c9f8b, plus doc commits 76f84465f and 68c08b4e7. Binaries are found by numstat or a NUL in the first 8000 committed bytes, regardless of attributes; each needs its sha256 and path in an approved latest-round candidate; an unconfirmed queue tail exits 2. Reviews: Poe c31525 rev 3882, Dewey c31526 rev 3883. Gate: 1st run 06:57Z RED, not the candidate: canonical node_modules was empty from 05:45:15Z (sage-4023). Rerun with its own root npm ci GREEN 07:15:49Z: 17 package suites 0 failures (cli 986, plugins 119, webui 418), 11 scripts exit 0, push-check 6 mutants KILLED; skips are the queue canonical-root check and the row 244 live provider cases. The landed push-check.sh passed its own push (NO_BINARY, NO_ATTRIBUTION, PUSH_READY). mos-1575: gates install their own node_modules; canonical restored with npm ci (128 packages).
|
||||
+
|
||||
+## 2026-10-23 — Lena, row 296 / #1802 FED R7 start
|
||||
+
|
||||
@@ agents/lena/work/queue-296/mutants.py (new)
|
||||
|
||||
## docs/SESSIONS.md ##
|
||||
@@ docs/SESSIONS.md: are never rewritten or removed; corrections are new entries.
|
||||
- 2026-10-23T17:48:43Z | Scrooge (629b6cd3) | Row 252 Console inbox Approve and Deny (#1753) | Rebased onto 306 landing as 580037105 (range-diff all =), slot run bus 907/0 webui 410/0 cli 943/0, in-review revs 3608/3609, Webby r1 APPROVE, J1-J19 specs to Sage; Sage landed 45ea3fa0d (done rev 3616). Part 1 mutant results were never in BUILD.md; they went to Sage by T3 (scrooge-0252-mutant-specs).
|
||||
- 2026-10-23 | Sage (1ef1e4f8) | Rows 308 and 252 landings, 571 Connections design amendment, Vikunja 597 | 308 (PRD 1.17, #1815) landed 30e6bb680 and 252 (#1753) landed 45ea3fa0d (replay of 580037105 onto 30e6bb680, range-diff all =), each after a full gate on the landed tree (252 also J1-J19, J11c and J18 equivalent); Gyro's 571 amendment (Fenton APPROVE) landed e8bb13df8; Vikunja 570, 435 and 597 done, canonical webui esbuild 0.28.2; queue revs 3589-3616 committed 59f013247
|
||||
- 2026-10-23 | Sage (1ef1e4f8) | Vikunja 571 W build rows; rows 13, 42, 257 and 289 check | Filed the design's ten W rows as queue rows 309-318 (revs 3617-3626, owners set to unassigned 3627-3636, queue commit 293b42fee pushed); Vikunja 571 done (comment 994). Row 13's candidate fd72d2689 is on refactor, gate is Monday's row 7 ledger run; 42 paused under mos-1184; 257 post-v1; 289 waits on 214 and 288. Slot released 17:56Z.
|
||||
+ 2026-10-24T04:27Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row265 / #1770 C2-09b rooms | Goldie owns build, Boyd reviews under sage-3915. New goldie/row265 worktree at ec86f2c84, claim rev3814; room verbs/parser/new tests prepared, no old tests changed. Sage-3932 slot: new parser14/14 and rooms18/18 passed; whole bus in progress before refusal mutants, stop rule active.
|
||||
+ 2026-10-24T04:35Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row265 / #1770 verified | First slot stopped at project-runtime import pin. mos-1567 narrowly authorizes pin and pure-source mount correction. sage-3942 rerun: sandbox10/10, parser15/15, rooms18/18, bus957/957, twelve refusal mutants killed/restored SHA71b32ee4, parser15/15 + rooms18/18 again. Slot returned goldie-0034; no further test runs. Boyd reviews RUNNER_FILES alongside rooms; own-branch push/claimant in-review next.
|
||||
+ 2026-10-24T07:18:53Z | Sage (1ef1e4f8) | Row 327 / #1825 push guard landed (mos-1572 option A, mos-1575) | Gate went red 06:57Z on an empty canonical node_modules (Glomgold's webui npm ci through a link to canonical, glomgold-0011); rerun with its own npm ci GREEN 07:15:49Z on 1bd214e38; pushed 68c08b4e7 07:18:03Z; #1825 closed c31530; canonical restored (128 packages); brief template rule 4db23a51f | landed
|
||||
+- 2026-10-23 — lena (Codex, db27faca): row 296 / #1802 FED R7; recovered Quackerjack design on lena/row296, inspecting acceptance paths; Node/tests held pending Sage CLEAR, no live changes.
|
||||
|
||||
## packages/cli/tests/federation-acceptance.test.mjs (new) ##
|
||||
3: e855f82e1 = 3: bbc38067f Untested: preserve R7 shell cleanup during mutant runs (#1802)
|
||||
4: a1321f8f9 = 4: 17e421cf4 Untested: record R7 reload ordering and slot boundaries (#1802)
|
||||
@@ -0,0 +1,5 @@
|
||||
M1: external TMPDIR removed (empty)
|
||||
M2: external TMPDIR removed (empty)
|
||||
killed-1: external TMPDIR removed (empty)
|
||||
plain-1: external TMPDIR removed (empty)
|
||||
whole-file: external TMPDIR removed (empty)
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,9 @@
|
||||
﹣ FED R7 scratch two-instance acceptance (0.501837ms) # run scripts/test-federation-acceptance.sh
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 1
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 77.634961
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/DRs04b
|
||||
@@ -0,0 +1,87 @@
|
||||
# Row 296: FED R7, scratch two-instance acceptance (#1802)
|
||||
|
||||
## Start
|
||||
|
||||
- Started 2026-10-23 18:53:27Z, on Sage's sage-3583 (mos-1489).
|
||||
- Base: origin/refactor 45dca69f6 (pinned; local copy used, no fetch: fetch runs the credential node script, which
|
||||
is held during Darkwing's re-pin window).
|
||||
- Branch: quackerjack/row296 in worktree quackerjack-fed-r5-row296.
|
||||
- Brief: docs/plans/2026-10-23_mvp-readiness-rows.md at 45dca69f6, section "Brief: FED R7, scratch two-instance
|
||||
acceptance" (line 77).
|
||||
- Constraints from Sage: scratch roots only (mktemp -d under /mnt/storage/scratch/tmp), never ~/.mosaic-dev or
|
||||
the live bus. No push, claim, queue write, node or test run until CLEAR.
|
||||
|
||||
## What the brief asks for (summary)
|
||||
|
||||
- One script test under scripts/ with its harness in packages/*/tests/, as row 190's tests are.
|
||||
- Two configs, two data roots, two Consoles, one fake OP. Consoles start outside agent ancestry in a transient
|
||||
user unit. They never use the live stack's units, ports or sockets. The script removes its units on exit (trap).
|
||||
- Test (design section 7 R7 gate): Jason's test identity, granted collaborator on one hub project, signs in,
|
||||
makes a thread, sends to the granted instance, gets the reply in the thread, is refused on another project and
|
||||
on board reads, is revoked, is refused again, and the run ends. The factory shows nothing of the hub.
|
||||
- Mutants: grant project list widened to `*`; revocation claim end skipped.
|
||||
- Gate: Filbert approves the manifest; the script test and every scripts/test-*.sh green on Sage's gate; no unit,
|
||||
port or socket of the script outlives the run. R8 (live window) stays row 209.
|
||||
|
||||
## Open reads before design
|
||||
|
||||
- R4 and R6 candidates (the brief says it builds on their combined candidate). Confirm what they expose: the
|
||||
hub sign-in, grant and revocation paths, and the Console's board-read refusal.
|
||||
- Row 190's test harness (several instances on one uid): how it starts units and cleans up.
|
||||
- Transient user unit support on this host (systemd-run --user), and how the existing tests detect it.
|
||||
|
||||
## Status
|
||||
|
||||
- 18:53Z: worktree created, brief read. No code yet.
|
||||
|
||||
## Handoff 2026-10-23 19:10:09Z (Mos ruling Q245, mos-1503)
|
||||
|
||||
Row 296 moves to Lena (Codex seat, thread db27faca-85fb-4ca0-9fc7-78d740d3fa3a, branch lena/row296). Quackerjack
|
||||
stops here: no builds, no claim, no queue writes. This design is the handoff; Lena continues it.
|
||||
|
||||
Base: 45dca69f6. R4 landed as 2efd127ce (ancestor of the base, checked with merge-base --is-ancestor). R6 landed as
|
||||
d3059c2c3 (row 294). R7 is claimable once Darkwing's window ends (Sage, sage-3592).
|
||||
|
||||
What I verified by reading (not by running):
|
||||
- packages/cli/src/access.mjs:65 reads `system.federation?.peers ?? []` (the R6 seam).
|
||||
- packages/bus/src/user-cli.mjs exists at base (the user-cli transport).
|
||||
- Row 190's tests (packages/cli/tests/stack-instances.test.mjs, stack-release.test.mjs T56, stack-release-fixture.mjs)
|
||||
drive stand-in units and fixtures. They do not start real transient user units. Not a model for R7's Consoles.
|
||||
- Real transient-unit launch pattern: packages/conversation/src/cohort.mjs (`scopeAvailable()` and `ScopeLauncher`,
|
||||
systemd-run --user --scope -p Delegate=yes --unit=...; MANAGER_ENV = XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS).
|
||||
- Test-only spawn of systemd-run in packages/conversation/tests is the nearest precedent for start/stop cleanup.
|
||||
|
||||
What I did NOT verify (so treat as open): the hub sign-in, grant, revocation and board-read paths in R4/R6 code; the
|
||||
Console startup flags; whether the fake OP can be a local stand-in; the trap-based unit cleanup.
|
||||
|
||||
Design (proposal, not yet checked against code):
|
||||
1. scripts/test-federation-acceptance.sh: the entry point. Creates a mktemp -d scratch root under
|
||||
/mnt/storage/scratch/tmp (never ~/.mosaic-dev, never the live bus). trap removes every unit it started and the
|
||||
scratch root. Refuses to run if a transient unit name collides.
|
||||
2. Two configs (instance A = hub-side granted seat, instance B = collaborator's home), each with its own
|
||||
MOSAIC_CONFIG, data root and federation.self; federation.peers names the other.
|
||||
3. One fake OP: a local stand-in that issues Jason's test identity. Its port and socket are per-run, not the live ones.
|
||||
4. Two Consoles, each in its own transient user unit (systemd-run --user --unit=mosaic-r7-<run>-<A|B>); outside agent
|
||||
ancestry; ports picked per run.
|
||||
5. Test flow (the R7 gate): identity -> grant on one hub project -> sign in -> thread -> send to granted instance ->
|
||||
reply in thread -> refused on another project -> refused on board reads -> revoke -> refused after revoke -> run
|
||||
ends. Factory output must show nothing of the hub.
|
||||
6. Mutants: (a) grant project list widened to `*`, must fail the other-project refusal; (b) revocation claim end
|
||||
skipped, must fail the refusal-after-revoke check.
|
||||
|
||||
Unresolved decisions for Lena (I did not decide these):
|
||||
- Whether the harness lives in packages/cli/tests (matching row 190) or packages/conversation/tests (matching
|
||||
cohort.mjs). Brief says packages/*/tests/; pick the one that owns the Console start path.
|
||||
- Fake OP: in-process HTTP stand-in versus file-backed identity. No preference recorded.
|
||||
- Port selection: fixed range per run vs OS-assigned. Brief forbids the live ports; it does not pick a range.
|
||||
|
||||
R2 and R5 learning:
|
||||
- R2 (row 203, #1701) landed as a4da4e6df. I built it. Relevant to R7: validateFederation in
|
||||
scripts/mosaic-config.mjs rejects unknown keys and bad ids, and test-config.sh has an FED block
|
||||
(expect_fed_refusal). Use federation.self/peers exactly as row 203 validates them.
|
||||
Mutant runner pattern: one textual edit that must occur exactly once, then a named test must fail; restore from git.
|
||||
Name filter gotcha: `node --test --test-name-pattern` skips subtests whose parent does not match; include the
|
||||
parent name (T57 for row 203).
|
||||
- R5: I did not build R5 in this session and have no first-hand notes on it. Ask Sage for R5's landing sha and notes.
|
||||
|
||||
Status: design written; no code, no test runs, no claim, no push of the test files. Next owner: Lena.
|
||||
@@ -0,0 +1,140 @@
|
||||
# Size question for Jason: W1 and W2 history cost (Vikunja 618)
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1572 and mos-1573. Rows 309 (W1, Glomgold) and 310 (W2, Gyro)
|
||||
keep building and reviewing on their seat branches. Neither lands on refactor until Jason answers.
|
||||
|
||||
Status: complete for Mos, 06:20Z 10-24, with the 209-plus-14 measurement added at 06:39Z (mos-1574). Every number below is measured. W2's figures come from Gyro's
|
||||
scratch measurement build (gyro-0013, 06:18Z), recorded in `agents/gyro/work/queue-310/BUILD.md` section
|
||||
"Full-lock measurement results (sage-3992)" on gyro/row310 (pushed, tip 1716f5a22 at 06:32Z).
|
||||
|
||||
## Today's repository
|
||||
|
||||
`git count-objects -vH` in the canonical checkout, 05:55Z 10-24: size-pack 107.30 MiB, 137,187 objects in
|
||||
5 packs, 119.26 MiB loose. Every number below adds to that, permanently. Git history isn't pruned here
|
||||
(invariant 8, no force-push).
|
||||
|
||||
## W1, the WordNet thesaurus data (row 309)
|
||||
|
||||
- Generated data files: 21,916,660 bytes of text (Glomgold, glomgold-0004).
|
||||
- Mos's figure for the row's text is about 58 MB, which includes the source data it is generated from.
|
||||
- Changed text: 58,476,260 bytes across 45 paths (WordNet 3.1 source, generated JSON, generator, tests,
|
||||
evidence). No binary file.
|
||||
- Packed size of the row: 17,480,317 bytes (about 16.7 MiB), from
|
||||
`git rev-list --objects d16958bcf..b10711603 | git pack-objects --stdout | wc -c` on glomgold/row309
|
||||
tip b1071160363d5d0204de96b06a356c9a04845287 (glomgold-0007, 05:56Z). That's about 16% on top of
|
||||
today's 107.30 MiB pack.
|
||||
- Rebuild cost: the data is regenerated only when the WordNet source or the generator changes, so most
|
||||
W1 follow-up rows add little. A regeneration with a changed generator adds a new copy of each changed
|
||||
file, compressed against the old one where git's delta search finds it.
|
||||
|
||||
## W2, the WordCraft source and WASM build (row 310)
|
||||
|
||||
- Imported source, as of gyro-0007 05:47Z: upstream WordCraft 582 files, 21,021,869 bytes (30 icon,
|
||||
screenshot and macOS packaging files left out, no notice or license among them). Cargo vendor tree 209
|
||||
packages, 10,499 files, 130,737,230 bytes. Total 151,759,099 bytes.
|
||||
- Non-text input files: 18 upstream (4,853,036 bytes) and 74 vendor (4,168,676 bytes). Fonts, test blobs,
|
||||
an MP4 and ICC profiles. No .a, .so, .dll, .wasm or .exe in the inputs. List:
|
||||
`agents/gyro/work/queue-310/binary-inputs.json` on gyro/row310.
|
||||
- The packaging is still being settled. The 209-package vendor tree doesn't resolve against WordCraft's
|
||||
538-entry lock without build-copy manifest edits (target sections, dangling feature items, cfg
|
||||
evaluation; rulings sage-3976 to sage-3984g). At 05:58Z those edits hit their stop line (gyro-0011).
|
||||
Cargo refuses `glifo ^0.2.0`, locked at 0.2.0, which an optional normal dependency of the reviewed
|
||||
vello_cpu 0.1.0 requires. Only glifo 0.3.0 is in the 209, and no cfg section controls that edge. The
|
||||
edits so far cover 28 manifests, 121 target-dependency sections and 109 dangling feature items, all in
|
||||
scratch.
|
||||
- The full-lock alternative: WordCraft's lock has 538 third-party records, 534 from crates.io and 4 from
|
||||
git. Louie's existing toolchain cache holds all 534 .crate archives, 85,234,501 bytes compressed
|
||||
(`agents/gyro/work/queue-310/full-lock-cache-size.json`). The 209 reviewed packages are 130,737,230
|
||||
bytes extracted. Extracted in scratch from that cache (gyro-0012, 06:06Z), the full lock is
|
||||
614,049,226 bytes in 25,378 files, including generated checksum files. That's 329 packages beyond the
|
||||
reviewed 209 (328 from crates.io, 1 from git), and about 4.7 times the 209-package tree. Every extra
|
||||
package would also need a source and notice review against S0. My read: the full lock is the wrong
|
||||
trade for a few hundred MB of permanent history, so the choice is between one more kind of build-copy
|
||||
edit for the 209 (optional dependencies no wasm32 feature turns on) and keeping the vendor tree off
|
||||
refactor altogether.
|
||||
- (a) History per rebuild if the WASM stays committed: 16,720,159 bytes (about 15.9 MiB) packed. The
|
||||
release build writes four files, 44,142,800 bytes raw: WASM 43,957,877, JS 164,941, HTML 1,604 and an
|
||||
icon 18,378. Gyro wrote those four blobs into a scratch bare repository (git 2.55.0, `hash-object -w`,
|
||||
then `pack-objects --stdout --threads 4`, no gc) and measured the pack. Receipts:
|
||||
`measurement-output-pack.json` and `full-lock-release-measurement.json` in Gyro's packet. Each later
|
||||
rebuild that changes the WASM adds roughly that much again, because compiled WASM deltas badly
|
||||
against the previous build. Ten rebuilds would be about 160 MiB, more than today's whole pack.
|
||||
- (b) Could the release build make the WASM itself, so only source is committed? Facts so far:
|
||||
- `scripts/stack-copy.sh` builds nothing from source. It `git archive`s COPY_PATHS (:29-41, :72) and
|
||||
runs `npm ci --no-audit --no-fund` in the copy (:74), which needs the npm registry. So a release copy
|
||||
is already not offline today.
|
||||
- The pinned toolchain is Rust/Cargo 1.99.1 with the wasm32-unknown-unknown target, Trunk 0.22.0,
|
||||
wasm-bindgen 0.2.129 and wasm-opt 131 (W2 brief, S0). This host's PATH has Arch's rustc and cargo
|
||||
1.98.0 (not the pin), no rustup, and no trunk, wasm-bindgen or wasm-opt. A release host would need
|
||||
all five pinned and hash-checked. That's an install row of its own, and it touches infra on .53/.54,
|
||||
which is Jason's.
|
||||
- Build time: 172 to 393 s per release build in Louie's and S0's baseline runs. Gyro's offline
|
||||
release build took 397.9 s from a fresh target directory at nice 19, 4 CPUs, 4 Cargo jobs and 4
|
||||
Binaryen cores. A first attempt (388.8 s) started at nice 15, inherited from the harness, so it
|
||||
doesn't count. That's about 6.6 minutes per activation on this host, if the release builds it.
|
||||
- No network: the build ran in an isolated network namespace with no external interface, Cargo and
|
||||
Trunk set offline and update checks off, and exit 0. Nothing was fetched or installed. It used the
|
||||
full-lock vendor set extracted in scratch, not the 209-package tree, so it proves an offline build
|
||||
from vendored source works, not that the 209 alone suffice.
|
||||
- Gyro also saw that the resolver used 14 packages outside the compiled closure (14,523,104 bytes,
|
||||
`measurement-resolution-extra-packages.json`). That suggests a vendor set near 209 plus those 14
|
||||
might resolve without the manifest edits. Nobody has tried it. This timing isn't restricted-adapter
|
||||
or CSP evidence (M1 to M14).
|
||||
- Precedent: row 154's editor bundle commits the built `packages/webui/src/public/editor.js`
|
||||
(758,420 bytes of text) next to its third_party source, and `editor-provenance.test.mjs` rebuilds it
|
||||
with the pinned esbuild on the landing gate (`MOSAIC_REQUIRE_EDITOR_BUILD=1`). W2 committing its WASM
|
||||
and proving a rebuild on the gate follows that pattern. Building in stack-release.sh would be new.
|
||||
|
||||
## The 209-plus-14 option, measured (mos-1574)
|
||||
|
||||
Mos asked for this option to be measured before Jason chooses. Everything ran in scratch and nothing is
|
||||
on refactor. Gyro's packet: gyro/row310 tip 1716f5a22, `agents/gyro/work/queue-310/BUILD.md` section
|
||||
"223-source measurement" (line 498).
|
||||
|
||||
- Size: the 223 packages are 11,201 files, 145,260,334 bytes raw, 26,538,894 bytes packed. The 14 extras
|
||||
alone are 702 files, 14,523,104 bytes raw, 1,720,566 bytes packed. Compare the full lock at 614 MB.
|
||||
- Resolution: the existing build-copy filter script, unchanged, gives 31 changed manifests. With it,
|
||||
cargo resolves offline only after writing its own lock. Against WordCraft's pinned lock (558 entries,
|
||||
sha256 06b745e4...) that lock removes 332 entries and adds or changes none (name, version, source,
|
||||
checksum), so it's a strict subset. The pinned lock itself is untouched.
|
||||
- Build: it builds. Gyro's offline release build with the pruned lock under --frozen/--locked exited 0
|
||||
in 380.7 s (gyro-0018, 06:38Z), with the same settings as the full-lock run (nice 19, 4 jobs, isolated
|
||||
netns, fresh directories, no retry used). S0's root closures matched before compiling: 214 normal and
|
||||
224 normal plus build, including 15 local. Outputs: WASM 43,952,335 bytes, JS 164,941, HTML 1,604,
|
||||
icon 18,378 (`measurement-223-build.json`). The WASM is 5,542 bytes smaller than the full-lock build's
|
||||
and its sha256 differs (0322beb9... against c1c15050...). The two builds had different vendor sets, so
|
||||
this proves nothing either way about reproducibility. A repeat build of one input set would.
|
||||
- Source and notice review of the 14 (Gladstone, Codex, gladstone-0026, 06:37Z, not a verdict): all 14
|
||||
archives match the pinned lock's checksums byte for byte, and the extract matches the archives except
|
||||
three filtered Cargo.toml files. 10 pass. 4 have notice concerns to settle before adoption:
|
||||
- cursor-icon 1.2.0 and winit 0.30.13 apply W3C license terms inside source files (src/lib.rs,
|
||||
src/keyboard.rs), which the filename-based notice collector missed. They need adding to M13.
|
||||
- dpi 0.1.2 is "Apache-2.0 AND MIT", not an expression on S0's list. Both texts must stay.
|
||||
- spirv 0.4.0 declares Apache-2.0 but its archive has no license text, so the standard text has to be
|
||||
supplied under M13.
|
||||
- winit also carries four example PNGs (under 3 KB each) and a Web Worker path. Neither is in the
|
||||
measured build's closure, but the binary inventory must list the PNGs and the Worker source isn't
|
||||
cleared for runtime use.
|
||||
- Notice follow-up (gyro-0019 at 9522d2356, Gladstone's recheck gladstone-0027, 06:44Z): the four gaps
|
||||
are now collected as evidence. The W3C terms for cursor-icon and winit are in the notice bundle with
|
||||
their source hashes, both dpi texts and the libm headers stay, spirv maps WordCraft's pinned
|
||||
LICENSE-APACHE text explicitly, and winit's four PNGs are inventoried. Gladstone found no further
|
||||
gaps. What stays open is policy: the W3C form isn't among S0's accepted licenses, and the PNGs and
|
||||
the Worker source need explicit classification if adopted.
|
||||
- What adopting it would mean: commit a cargo-pruned lock in place of WordCraft's (a packaging change
|
||||
for S0's reviewer), the 14 packages' source, the collected notices, and an owner decision on the W3C
|
||||
license form.
|
||||
|
||||
## What Jason is asked
|
||||
|
||||
1. Whether the roughly 260 MB of raw text and source (W1 plus W2's inputs) goes into permanent history,
|
||||
given the packed numbers above. For W2 that now includes which vendor tree: the 209 reviewed
|
||||
packages with build-copy manifest edits (blocked at glifo, would need a further kind of edit), the
|
||||
209 plus the 14 the resolver needed (measured above: 145 MB raw, 26.5 MB packed, a pruned lock and
|
||||
four notice fixes), or the full 538-record lock (614 MB extracted, 329 more packages to review). I
|
||||
recommend 209 plus 14.
|
||||
2. For W2's output, one of: commit the WASM and rebuild-check it on the landing gate (the row 154
|
||||
pattern, adding (a) per rebuild), or build it during release (needs the toolchain on every release
|
||||
host, adds the build time per activation, and stack-copy.sh changes).
|
||||
3. Option C from sage-3978 (Git LFS or an off-tree artifact store) stays open if neither cost is
|
||||
acceptable.
|
||||
@@ -1622,3 +1622,9 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-24T01:14Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 verified | sage-3882: new3/3, business264/264, one key mutant killed/restored, new3/3 again, per-run fresh shortTMP/load/helperCLEAR. Four manifest entries verify; sole old assertion key change, no queue writes. Candidate push/report to Sage and Gladstone next.
|
||||
2026-10-24 | Poe | Q246 / mos-1566, row 208 / 193d (#1706), poe/row208 | Started; approved design hash verified; implementation and isolated acceptance pending.
|
||||
2026-10-24 | Poe | Row 208 / 193d (#1706), poe/row208 | Builder acceptance green: webui 418/418, plugins 119/119, four UI mutants killed with bus bypass proofs; candidate prepared for Gladstone then Duckworth guard review.
|
||||
2026-10-24T04:29Z | Duckworth (Opus 5.5) | row 208 (193d) second review, guards only, on 4a4bef6c1 (sage-3934) | APPROVE (static), no bus change, every hidden control has a broker refusal, grant hint per-grant and self-only, N1 5 s poll (duckworth-0302) |
|
||||
2026-10-24T04:27Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row265 / #1770 C2-09b rooms | Goldie owns build, Boyd reviews under sage-3915. New goldie/row265 worktree at ec86f2c84, claim rev3814; room verbs/parser/new tests prepared, no old tests changed. Sage-3932 slot: new parser14/14 and rooms18/18 passed; whole bus in progress before refusal mutants, stop rule active.
|
||||
2026-10-24T04:35Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row265 / #1770 verified | First slot stopped at project-runtime import pin. mos-1567 narrowly authorizes pin and pure-source mount correction. sage-3942 rerun: sandbox10/10, parser15/15, rooms18/18, bus957/957, twelve refusal mutants killed/restored SHA71b32ee4, parser15/15 + rooms18/18 again. Slot returned goldie-0034; no further test runs. Boyd reviews RUNNER_FILES alongside rooms; own-branch push/claimant in-review next.
|
||||
2026-10-24T07:18:53Z | Sage (1ef1e4f8) | Row 327 / #1825 push guard landed (mos-1572 option A, mos-1575) | Gate went red 06:57Z on an empty canonical node_modules (Glomgold's webui npm ci through a link to canonical, glomgold-0011); rerun with its own npm ci GREEN 07:15:49Z on 1bd214e38; pushed 68c08b4e7 07:18:03Z; #1825 closed c31530; canonical restored (128 packages); brief template rule 4db23a51f | landed
|
||||
- 2026-10-23 — lena (Codex, db27faca): row 296 / #1802 FED R7; recovered Quackerjack design on lena/row296, inspecting acceptance paths; Node/tests held pending Sage CLEAR, no live changes.
|
||||
- 2026-10-24 — lena (db27faca), row 296 / #1802: r2 rebased onto e0c6142f3; scoped acceptance plain 1/1, killed-driver cleanup verified, M1/M2 assertion kills/restored, dedicated-file skip verified; own dependency installs, no old tests/live changes; reviewer and Sage gate pending.
|
||||
@@ -1365,3 +1365,28 @@ public sign-in smoke and a forged-loopback 401 check; rollback drops flags
|
||||
before middleware. Same-uid callers can still read the file; separate service
|
||||
uid is Jason's question on #385. The Authentik allowed-uid file remains an
|
||||
identity input and is never reused for proof.
|
||||
|
||||
## `scripts/push-check.sh` — verify a prospective push
|
||||
|
||||
`scripts/push-check.sh SHA EXPECTED_LOG [--row N]... [--base-ref REF] [--fetch]`
|
||||
checks the repository at the caller's cwd; it never pushes or writes the queue.
|
||||
Git checks cover the full repository even from a subdirectory; EXPECTED_LOG is
|
||||
resolved relative to the original caller. Non-UTF-8 git paths refuse with exit 2.
|
||||
EXPECTED_LOG must contain exactly `git log --oneline BASE..SHA` (including its
|
||||
newlines). BASE defaults to `refs/remotes/origin/refactor`; `--base-ref` overrides
|
||||
it. Only `--fetch` fetches origin, before resolving BASE.
|
||||
|
||||
Success prints `LOG_OK`, `FF_OK`, `NO_BINARY` or `BINARY_REVIEWED n`,
|
||||
`SECRETS_CLEAN`, `NO_ATTRIBUTION`, then `PUSH_READY BASE..SHA` as the last line.
|
||||
A changed present blob counts as binary when numstat marks it binary or its
|
||||
first 8000 committed bytes contain NUL, regardless of git attributes. Each
|
||||
present binary needs its committed blob sha256 and exact path in an approved
|
||||
latest-round candidate from a supplied row. A `changes` receipt blocks that round.
|
||||
Commit candidates authorize only changed paths with matching bytes and mode.
|
||||
Deleted binaries need no review. Queue reads retain canonical-checkout checks
|
||||
and refuse unconfirmed durable tails with exit 2.
|
||||
|
||||
Exit 0 means ready; 1 means refusal (`STOP reason`, always the last line); 2 means
|
||||
usage or environment error. A refusal never prints later success tokens.
|
||||
`timeout -k 10 240 bash scripts/test-push-check.sh` exercises isolated scratch
|
||||
repositories and the six required mutants; set TMPDIR to a scratch filesystem.
|
||||
@@ -207,6 +207,43 @@ install -o mosaic -g mosaic -m 0600 /root/<sha>.bundle /srv/mosaic/incoming/<sha
|
||||
sudo -u mosaic -H git clone -q -b refactor /srv/mosaic/incoming/<sha>.bundle /srv/mosaic/src/mosaic-stack
|
||||
```
|
||||
|
||||
If the first install refuses, use Mos's retry path (mos-1565, ops-0291). Bootstrap can already have
|
||||
written `/srv/mosaic/.config/mosaic-dev/config.json` (`scripts/case2-install.sh:174`,
|
||||
`scripts/bootstrap.sh:13`, `scripts/mosaic-config.mjs:412-424`). The config dir can also hold
|
||||
`businesses/`, the business file, and library persona files and slot dirs
|
||||
(`scripts/case2-install.sh:200-211`, `scripts/case2-persona-import.mjs:300-308`). A validation refusal
|
||||
removes only the business file this run created; its directory remains (`scripts/case2-install.sh:217-219`).
|
||||
The copy may already exist under `/srv/mosaic/.local/share/mosaic-stack/copies/<sha>`
|
||||
(`scripts/case2-install.sh:80`, `scripts/stack-copy.sh:68`).
|
||||
|
||||
As root, move these three paths aside with a UTC suffix. Move, never delete, so their owner and mode
|
||||
stay with them (mos-1565). This is the default-instance retry for the paths in this sheet:
|
||||
|
||||
```sh
|
||||
(
|
||||
cd / || exit 1
|
||||
retry_stamp="$(date -u +%Y%m%dT%H%M%SZ)" || exit 1
|
||||
for retry_path in /root/mosaic-stack /srv/mosaic/src/mosaic-stack /srv/mosaic/.config/mosaic-dev; do
|
||||
if [ -e "$retry_path" ] || [ -L "$retry_path" ]; then
|
||||
retry_saved="$retry_path.$retry_stamp"
|
||||
if [ -e "$retry_saved" ] || [ -L "$retry_saved" ]; then
|
||||
echo "STOP: retry destination already exists: $retry_saved" >&2
|
||||
exit 1
|
||||
fi
|
||||
mv -T -- "$retry_path" "$retry_saved" || exit 1
|
||||
fi
|
||||
done
|
||||
) || echo "STOP: could not move the first-install paths aside; do not continue"
|
||||
```
|
||||
|
||||
Then rerun the first-install block from its first line. Never use the upgrade fetch below on clones
|
||||
whose first install has not finished: it updates `refs/remotes/origin/refactor`, while copy requires
|
||||
the commit to be reachable from both that ref and `refs/heads/refactor` (`scripts/stack-copy.sh:44-48`; mos-1565).
|
||||
Recheck section 9 before starting units. If Jason already filled `claude-session.env`, the library
|
||||
proof file or `library-gitea`, have him re-place those files under the new config dir with section 9's
|
||||
owner and mode. Per-agent slots still use `mosaic credentials issue` or Approve, never a hand copy
|
||||
(section 9; mos-1565). The moved config dir preserves the earlier files.
|
||||
|
||||
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`):
|
||||
@@ -254,6 +291,19 @@ When a run doesn't exit 0, send Mos the exit and the last lines of `/var/log/mos
|
||||
the old copy. The next run refuses `args-differ`, and a render from that file would put the old copy back.
|
||||
Move the file the result line names into place with `mv -f`. The two differ only in the `--copy` value.
|
||||
|
||||
To check a backup by hand before rollback or restore, run as the stack's user from its `bus/` directory:
|
||||
|
||||
```sh
|
||||
sudo -u mosaic -H /bin/sh -c \
|
||||
'cd "<dataRoot>/backups/stack/<activation>/bus" && sha256sum -c ../SHA256SUMS'
|
||||
```
|
||||
|
||||
Expect `bus.sqlite: OK`; stop on a failed check. The set holds `bus/bus.sqlite`, and `SHA256SUMS` names
|
||||
`bus.sqlite` relative to `bus/` (`scripts/stack-release.sh:475-482`). Running `sha256sum -c SHA256SUMS`
|
||||
from the backup set's directory looks for `bus.sqlite` one directory too high (ops-0292). Restore and
|
||||
rehearsal use the same `bus/` form
|
||||
(`scripts/stack-release.sh:502`, `scripts/stack-release.sh:2244`).
|
||||
|
||||
After `case2-install.sh` succeeds, create the human CLI wrapper before running
|
||||
`stack-copy.sh check --units external`. Creating this wrapper is a separate live
|
||||
step requiring Mos's go (`scripts/stack-copy.sh:3`); the install does not create it. Run only on that go:
|
||||
@@ -275,11 +325,52 @@ it. The wrapper must name the same `<sha>` copy as the units.
|
||||
Precheck lines: none. Root's checkout at `<sha>` and the stack's user's clone: precheck line missing.
|
||||
After install, `stack-copy.sh check --units external` reads the copy the units run.
|
||||
|
||||
Before running `case2-install.sh`, check the exact file you will pass as `--business-file`. Each
|
||||
non-library `projects` key must equal the `id` in `<root>/.mosaic/project.json`, and that file must exist
|
||||
in the stack's user's clone. Library entries are skipped by validate (`packages/business/src/cli.mjs:72`).
|
||||
Run this read-only check as `mosaic` with section 2's pinned node:
|
||||
|
||||
```sh
|
||||
sudo -u mosaic -H /opt/mosaic-stack/node-<v>/bin/node --input-type=module - '<business-file>' <<'MOSAIC_PROJECT_KEYS'
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
try {
|
||||
const business = JSON.parse(readFileSync(process.argv[2], 'utf8'));
|
||||
const projects = Object.entries(business.projects);
|
||||
if (!projects.length) throw new Error('business file declares no projects');
|
||||
for (const [id, entry] of projects) {
|
||||
if (entry.kind === 'library') continue;
|
||||
const file = join(entry.root, '.mosaic', 'project.json');
|
||||
const project = JSON.parse(readFileSync(file, 'utf8'));
|
||||
if (project.id !== id)
|
||||
throw new Error(`project file ${file} has id ${project.id}, but business ${business.id} declares it as ${id}`);
|
||||
console.log(`ok project ${id}: ${file} has id ${project.id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`STOP: ${error.message}; do not install`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
MOSAIC_PROJECT_KEYS
|
||||
```
|
||||
|
||||
For .53 expect `ok project stack: /srv/mosaic/src/mosaic-stack/.mosaic/project.json has id stack`
|
||||
(`.mosaic/project.json:3`, `docs/guides/case1-business.mosaic-stack.json:10-11`). Any STOP or nonzero exit
|
||||
stops the install. Fix a mismatched business key, not the project id (mos-1563). The install otherwise
|
||||
reaches that refusal at step 7, after making the copy (`packages/business/src/cli.mjs:80`,
|
||||
`scripts/case2-install.sh:178`, `scripts/case2-install.sh:217`).
|
||||
|
||||
The stack's user's `scripts/case2-install.sh` (C2-13b, design 10 step 2b) makes the first copy with
|
||||
`stack-copy.sh copy <sha>`. The copy runs `npm ci` from `package-lock.json`, which installs the pinned
|
||||
harness, so the host needs to reach the npm registry during the install. The install runs no
|
||||
`release.sh package` or `activate`.
|
||||
|
||||
A missing `.mosaic/project.json` only warns: `project <id>: no project file at <path>; it has no project
|
||||
variables`. Validate reports that project `absent` and continues (`packages/business/src/cli.mjs:74-78`).
|
||||
An install can therefore pass step 7 without the project's variables. It captures validate's output
|
||||
and prints `business <id> validates` (`scripts/case2-install.sh:217-221`). Treat the missing-file warning as a stop,
|
||||
place the correct project file and repeat the check above before continuing (Duckworth 323 N2;
|
||||
`scripts/case2-install.sh:217-221`).
|
||||
|
||||
## 7. The args file
|
||||
|
||||
`/etc/mosaic-stack/dev.args` holds the `case2-unit.sh` arguments, one per line, with the serve flags after a
|
||||
@@ -318,7 +409,7 @@ file with `stat -c '%U %a %n' PATH`, never `cat`. `<business>` is the host's bus
|
||||
| Path | File | Mode | Owner | Profile | Precheck line |
|
||||
|---|---|---|---|---|---|
|
||||
| `/srv/mosaic/.config/mosaic-dev/secrets/` | `claude-session.env`, one line `ZAI_API_KEY=...` | 0600 | mosaic | library | `<path> sets ZAI_API_KEY` |
|
||||
| `/srv/mosaic/.config/mosaic-dev/secrets/` | the Console's proof file, the path the args file's `--proof-file` names | 0600 or 0400 | mosaic | library | none. `case2-unit.sh` refuses at render. Precheck line missing. |
|
||||
| `/srv/mosaic/.config/mosaic-dev/secrets/` | the Console's proof file, the path the args file's `--proof-file` names: one base64url line, at least 32 `[A-Za-z0-9_-]` characters, with one optional trailing LF (`packages/webui/src/serve.mjs:263-264`) | 0600 or 0400 | mosaic | library | none. Render checks metadata only and never opens the file (`scripts/case2-unit.sh:397-408`). Bad bytes refuse at Console start with exit 2 (`packages/webui/src/serve.mjs:267`, `packages/webui/src/cli.mjs:138-140`). Precheck line missing. |
|
||||
| `/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 ...` |
|
||||
| `/srv/mosaic/.config/mosaic-dev/secrets/agents/<business>/<agent>/` | one file per service | 0600, dirs 0700 | mosaic | both | none. Filled only by `mosaic credentials issue`, or Approve after #436. Never by hand. |
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ pane, persistent chat in the right, a draggable divider, document navigation, pi
|
||||
right-click thesaurus. It is not a reskinned row 154 editor. The first useful release, opened at W7 after the required gates, must
|
||||
pass writing, footnote, comment, tracked-change and page-layout save/reopen tests together.
|
||||
|
||||
D2. Q210/Q220 allow design and review now. O2 builds remain after the October 27 MVP work
|
||||
and never take its gate slot. S0 baseline clearance is conditional on M1–M14, Fenton/Filbert
|
||||
D2. Q210/Q220 allow design, review and builds ahead of the October 27 MVP work (mos-1571).
|
||||
O2 rows never take the gate slot ahead of a v1 row; they land only when the slot would
|
||||
otherwise sit idle. S0 baseline clearance is conditional on M1–M14, Fenton/Filbert
|
||||
at `origin/fenton/q195-s0` 74ec32013 (#1798, Sage PASS comment 30917). Q234 approves the
|
||||
exact measured frame CSP; the shell stays unchanged. mos-1363 requires W2 to remeasure zero
|
||||
violations with style-src 'self'; no rfd style hash is approved. These gates are satisfied
|
||||
@@ -585,14 +586,15 @@ Their implementation gates remain separate. No code row W1/W2/W6/W3a/W3b/W3/W4b/
|
||||
before S0 PASS (recorded for this baseline, with M1–M14 still binding). Each also observes its explicit dependencies, source ownership, the CSP gate
|
||||
and the MVP gate-slot priority. Q177 permits chaining reviewed candidates, not overlapping
|
||||
owners. Sage assigns model-only seats. Each implementation reviewer is Opus 5.5 and did not
|
||||
author the code. Fenton reviews this design and can review code only when not its author.
|
||||
author the code. A Codex-built row gets its first reviews from a separate Codex thread (Q246);
|
||||
the Opus 5.5 review runs once, on the landing candidate (mos-1571). Fenton reviews this design and can review code only when not its author.
|
||||
|
||||
Build order (mos-1470, Vikunja 571 comment 961): W7 first supplies reviewed conversion/format
|
||||
routing code with production import still closed, then W8 lands the Connections view, then
|
||||
W7's human opening step may open the first real chapter import. W8 is after W3/W4b and
|
||||
W7's closed routing candidate, before that W7 opening step. This is Mos's build-order rule,
|
||||
not a new REQ-LIB-8 condition or an addition to import acceptance. No converted real chapter
|
||||
exists without the view. O2 code remains after 10-27 and never ahead of MVP rows.
|
||||
exists without the view. O2 code may be built before 10-27 (mos-1571) but never lands ahead of MVP rows.
|
||||
|
||||
G1. Future code acceptance runs each touched test file independently and affected package
|
||||
suites on Sage's slot after MVP priority, plus required repository checks. Source builds need
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# Push check in the repository, with reviewed binaries
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1572 ("Go with A for the guard"), after rows 265 and 310
|
||||
raised the binary question (mos-1570, sage-3978).
|
||||
|
||||
## Brief: push-check.sh passes a binary only when the reviewed candidate lists its path and sha256
|
||||
|
||||
**Why.** The lead's push check is a scratch script, `/mnt/storage/scratch/sage-r64/pushcheck.sh`
|
||||
(mos-0856/0857), so nobody has reviewed it and no test covers it. Its `NO_BINARY` step refuses any
|
||||
path that `git diff --numstat BASE SHA` reports as binary (`-` in the first column). Row 265 had to
|
||||
waive that step by hand for 22 gzip logs. Row 310 (W2) will need committed WASM, fonts and test blobs.
|
||||
Mos chose option A: keep the binary check, and let a binary through only when its path and sha256
|
||||
appear in the candidate a reviewer approved. Whether any large binary lands at all is a separate
|
||||
question for Jason (Vikunja 618). This row builds the guard, not that answer.
|
||||
|
||||
**Read first.**
|
||||
- The scratch script, copied under "The scratch script today" at the end of this brief. Don't read
|
||||
`/mnt/storage/scratch/sage-r64` itself; it's Sage's scratch.
|
||||
- `packages/queue/README.md`, sections on `move ID in-review` (manifest and commit candidates) and
|
||||
`review verify-commit`. `packages/queue/src/review.mjs` for how a round's candidate and its receipts
|
||||
are stored. Running `scripts/mosaic queue show N` prints them.
|
||||
- `docs/plans/2026-10-20_force-push-guard.md`, row 211 (Darkwing, in progress). That row owns
|
||||
`scripts/git-hooks/pre-push`, the hook install and `scripts/slot-busy.sh`. Don't touch those files,
|
||||
and don't wire this check into a hook.
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. `scripts/push-check.sh SHA EXPECTED_LOG [--row N]...` (new). It runs the scratch script's steps in
|
||||
the same order and prints the same tokens, so the lead's push sequence only changes its path:
|
||||
`LOG_OK`, `FF_OK`, `NO_BINARY` or `BINARY_REVIEWED <n>`, `SECRETS_CLEAN`, `NO_ATTRIBUTION`, then
|
||||
`PUSH_READY BASE..SHA` as the last line. Every refusal prints one `STOP <reason>` line and exits 1.
|
||||
A usage or environment error exits 2. It works in the repository the caller's cwd is in. The base
|
||||
ref defaults to `refs/remotes/origin/refactor`, and `--base-ref REF` overrides it for tests. A fetch
|
||||
happens only when `--fetch` is given, and the lead's sequence passes it.
|
||||
2. The binary step. For each path that `git diff --numstat BASE SHA` marks binary and that exists at
|
||||
SHA (a deleted binary needs no review):
|
||||
- compute the sha256 of the blob at `SHA:path` (`git cat-file blob`, not the working tree);
|
||||
- look for an approved candidate among the `--row` rows. A round counts only when it is the row's
|
||||
latest round and has a receipt with verdict `approve` whose candidate equals that round's
|
||||
candidate. A `changes` receipt in the same round blocks it;
|
||||
- for a manifest candidate, the manifest must hold the line `<sha256> <path>` exactly;
|
||||
- for a commit candidate, use the same rule as `queue review verify-commit`. The path must be one the
|
||||
candidate changed from its first parent, and the sha256 of the candidate's blob must equal SHA's;
|
||||
- no match prints `STOP binary <path> <sha256> not in a reviewed candidate`. With no `--row`, any
|
||||
binary stops, which is today's behaviour.
|
||||
Read the queue only through `scripts/mosaic queue show N` or the queue package's read functions.
|
||||
Never write the queue or post anything.
|
||||
3. The secret scan and the attribution check stay as they are. A binary passes the secret scan only
|
||||
through the review above. Decompressing gzip to scan it is a separate row if anyone wants it
|
||||
(mos-1570). Don't add it here.
|
||||
4. `scripts/test-push-check.sh` (new), run by `timeout -k 10` like the other suites. It builds a
|
||||
scratch bare remote, a scratch clone and a scratch queue. It never touches origin, the canonical
|
||||
`docs/plans/queue.json` or a seat's queue lock. If the queue CLI can't read a scratch queue without a
|
||||
full scratch copy of the repository, use a scratch copy. If that is impractical too, stop and ask
|
||||
Sage before inventing a test-only override. Cases:
|
||||
- a text-only range prints `NO_BINARY` and `PUSH_READY`;
|
||||
- an unlisted binary stops, naming the path;
|
||||
- a listed path with a different sha256 stops;
|
||||
- an approved manifest candidate passes and prints `BINARY_REVIEWED 1`;
|
||||
- an approved commit candidate passes. One whose blob for that path differs from SHA's stops;
|
||||
- a round with a `changes` receipt, a superseded earlier round, and an approve receipt for a
|
||||
different candidate each stop;
|
||||
- a deleted binary passes with no `--row`;
|
||||
- a non-fast-forward SHA, a log mismatch, an attribution line and a planted fake secret each stop,
|
||||
and nothing after the stop prints;
|
||||
- the last line is `PUSH_READY` only on success, so `| tail -1 | grep -q '^PUSH_READY '` is safe.
|
||||
5. Mutants, each killed by the test: skip the sha256 comparison; accept a `changes` round; accept an
|
||||
earlier round; read the working tree instead of the blob; let a binary through when `--row` is absent.
|
||||
6. `docs/TOOLS.md`: one entry for `push-check.sh` with usage, the tokens and the exit codes.
|
||||
|
||||
**Files owned.** `scripts/push-check.sh`, `scripts/test-push-check.sh`, the new `docs/TOOLS.md` entry,
|
||||
and your `agents/<seat>/work/queue-<row>/` packet (BUILD.md, plain-text evidence). Anything else needs a
|
||||
brief amendment.
|
||||
|
||||
**Out of scope.** The pre-push hook and slot check (row 211). Deciding whether W2's WASM or W1's data
|
||||
lands (Jason, Vikunja 618). Gzip scanning. Retiring the scratch script, which Sage does after landing.
|
||||
|
||||
**Gate.** A separate Codex thread does the first review. Dewey reviews once on the landing candidate
|
||||
(guards go to a Claude reviewer, mos-1542). `scripts/test-push-check.sh` and every other
|
||||
`scripts/test-*.sh` are green on Sage's landing gate, and the mutants are killed. Done when Sage's next
|
||||
push runs through `scripts/push-check.sh` instead of the scratch script. Evidence is plain text
|
||||
(BRIEF-TEMPLATE, mos-1570). Use a TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/XXXXXX`.
|
||||
|
||||
### The scratch script today
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Push checks for an explicit sha (mos-0856/0857). Usage: pushcheck.sh SHA EXPECTED_LIST_FILE
|
||||
# EXPECTED_LIST_FILE holds the exact `git log --oneline origin/refactor..SHA` lines, newest first.
|
||||
set -o pipefail
|
||||
cd /mnt/storage/src/mosaic-stack || exit 9
|
||||
SHA=$(git rev-parse --verify "$1^{commit}") || { echo "STOP sha"; exit 9; }
|
||||
git fetch -q origin || { echo "STOP fetch"; exit 9; }
|
||||
BASE=$(git rev-parse refs/remotes/origin/refactor) || exit 9
|
||||
diff <(git log --oneline "$BASE..$SHA") "$2" && echo LOG_OK || { echo "STOP log"; exit 1; }
|
||||
git merge-base --is-ancestor "$BASE" "$SHA" && echo FF_OK || { echo "STOP ff"; exit 1; }
|
||||
[ "$(git diff --numstat "$BASE" "$SHA" | awk '$1=="-"' | wc -l)" = 0 ] && echo NO_BINARY || { echo "STOP binary"; exit 1; }
|
||||
if git diff "$BASE" "$SHA" | grep -E '^\+' | grep -iqE 'BEGIN [A-Z ]*PRIVATE KEY|ghp_[A-Za-z0-9]{20}|sk-ant-[A-Za-z0-9-]{10}|xox[bp]-|AKIA[0-9A-Z]{16}|(token|secret|password)["'"'"' ]*[:=] *["'"'"'][A-Za-z0-9_\-]{24,}'; then echo "STOP secrets"; exit 1; fi
|
||||
echo SECRETS_CLEAN
|
||||
if git log --format=%B "$BASE..$SHA" | grep -iqE 'co-authored-by|generated with'; then echo "STOP attribution"; exit 1; fi
|
||||
echo NO_ATTRIBUTION
|
||||
echo "PUSH_READY $BASE..$SHA"
|
||||
```
|
||||
|
||||
### Amendment 1 (Dewey's landing review, dewey-0955)
|
||||
|
||||
`git diff --numstat` follows git attributes, so a `diff` attribute (from the tree's `.gitattributes`,
|
||||
`.git/info/attributes` or `core.attributesFile`) can make a binary blob count as text. Item 2 changes.
|
||||
A path is binary when numstat marks it binary, or when it exists at SHA and its blob (`git cat-file
|
||||
blob SHA:path`) has a NUL byte in its first 8000 bytes, git's own rule. Attributes can add binaries
|
||||
but never remove them. Tests add scratch cases for each of the three attribute sources, each with no
|
||||
`--row` and each expecting `STOP binary`. A sixth mutant drops the NUL scan. Also, when the queue read
|
||||
returns code 0 with an unconfirmed-tail note, the check stops (fail closed), with a test.
|
||||
@@ -59,6 +59,21 @@ fails it on any commit (row 323, mos-1561). A builder whose cli suite
|
||||
can't run says so in the row, and the cli suite counts from the lead's
|
||||
landing gate.
|
||||
|
||||
Evidence that lands with a row is plain text: logs, diffs, manifests. No
|
||||
gzip or other binary file, so the lead's push guard runs its binary and
|
||||
secret checks with no waiver. If a log is too big, cut it to the lines
|
||||
that prove the claim. Or keep it off refactor, in the seat's scratch, and
|
||||
cite its path and sha256 in BUILD.md (mos-1570, after row 265's 22 gzip
|
||||
logs).
|
||||
|
||||
Builders never run npm or remove files in the canonical checkout
|
||||
(`/mnt/storage/src/mosaic-stack`), only in their own worktree. Every gate,
|
||||
the builder's and the lead's landing gate, runs its own
|
||||
`npm ci --ignore-scripts --no-audit --no-fund` in its worktree root and in
|
||||
`packages/webui`, and never links to canonical node_modules (mos-1575,
|
||||
after canonical node_modules was found empty at 05:45:15Z 10-24 and row
|
||||
327's gate went red on it).
|
||||
|
||||
### Handoffs
|
||||
|
||||
Every seat hands off directly (DECISIONS Q210, mos-1221). When the owner
|
||||
|
||||
@@ -756,3 +756,15 @@ The guard is right. The test is the weak part: its prefix leaves no margin, and
|
||||
timeout. Fix: shorten the prefix and fail on a launch-failed log line instead of waiting 20 s. That's an
|
||||
old-test change, so it needs Mos's go (mos-1272). Bound to the next row that edits revoke-window.test.mjs.
|
||||
Until then, docs/plans/BRIEF-TEMPLATE.md's Gate section sets TMPDIR to `mktemp -d /mnt/storage/scratch/tmp/XXXXXX`.
|
||||
|
||||
## ops-0282 B2-2 closed by row 326
|
||||
|
||||
Sage. Row 326 (#1822) landed in f58049de0 (33f49f7d5). docs/guides/host-run-sheet.md now gives the Console
|
||||
proof file format in the .54 library table, so the B2-2 entry above is closed. Row226 N1-N4 stay open.
|
||||
|
||||
## Row 208 N1: user.js re-renders every 5 seconds
|
||||
|
||||
Sage, for Duckworth's N1 on row 208 (c31496, landed f58049de0). packages/webui/src/public/user.js:16
|
||||
re-renders the user view on a 5 s timer while the tab is visible, whether or not anything changed. That's a
|
||||
steady client and server load per open tab, not a correctness or guard issue. Fix: render on change, or poll the bus only for a change
|
||||
marker. Bound to the next row that edits user.js.
|
||||
+9
-7
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
|
||||
|
||||
<!-- mosaic-queue:begin -->
|
||||
|
||||
Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
Generated from `docs/plans/queue.json` revision 3885 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
|
||||
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
@@ -193,7 +193,7 @@ Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not e
|
||||
| 154 | A WYSIWYG Console editor built from reviewable source (Q168) | researcher; reviewers dewey, filbert | #1648 | done | 148 done | Mos's decision 123 go on the license and candidate before any code is imported, and later on the public/ diff; Dewey and Filbert approve naming the candidate manifest; webui, bus, cli, project suites and every scripts/test-*.sh green. Needs row 148 done. (sage) | `docs/plans/2026-10-16_q168-wysiwyg-editor.md` § A WYSIWYG Console editor built from reviewable source | — |
|
||||
| 155 | The human and the chief of staff configure the assistant (Q169) | darkwing; reviewers dewey, filbert | #1649 | done | 139 done, 141 done | Dewey and Filbert approve on #1649 naming the candidate manifest; Mos's decision 123 go on the public/ diff; bus, business, harness, cli, webui suites and every scripts/test-*.sh green; own window and tested migration if schema changes. Needs rows 139 and 141 done. (sage) | `docs/plans/2026-10-16_q169-assistant-config.md` § The human and the chief of staff configure the assistant | — |
|
||||
| 156 | The host starts the landing run once a merge decision is approved | dewey; reviewers filbert | #1651 | done | 125 done, 149 done | Filbert approves on #1651 naming the candidate manifest; Sage's full gate rerun at the landing target; the re-pin needs Mos's go (sage) | `docs/plans/2026-10-18_landing-run-launch.md` § The host starts the landing run once a merge decision is approved | — |
|
||||
| 157 | I2 install images, the install config key, the endpoint helper and the egress proxy | researcher; reviewers darkwing, filbert | #1652 | queued | 87 done, 163 done | Filbert and Darkwing approve on #1652 naming the candidate manifest, and Mos's provider-host output is on the issue; Sage's full gate green on the landing tree (sage) | `docs/plans/2026-10-18_containerized-install-rows.md` § I2 install images, the install config key, the endpoint helper and the egress proxy | Decision 96 orders I2-I6 after D1 and D2, which have no rows yet; the goal order sets priority |
|
||||
| 157 | I2 install images, the install config key, the endpoint helper and the egress proxy | bentina; reviewers boyd, darkwing | #1652 | in-review, round 2, request posted | 87 done, 163 done | Boyd (Codex, first review) and Darkwing approve on #1652 naming the candidate manifest, and Mos's provider-host output is on the issue; Sage's full gate green on the landing tree (sage) | `docs/plans/2026-10-18_containerized-install-rows.md` § I2 install images, the install config key, the endpoint helper and the egress proxy | Decision 96 orders I2-I6 after D1 and D2, which have no rows yet; the goal order sets priority |
|
||||
| 158 | I3 two broker listeners, the human-socket via value and the host relay | dewey; reviewers darkwing, filbert | #1653 | queued | 157 done | Filbert and Darkwing approve on #1653 naming the candidate manifest; Sage's full gate green on the landing tree (sage) | `docs/plans/2026-10-18_containerized-install-rows.md` § I3 two broker listeners, the human-socket via value and the host relay | Decision 96 orders I2-I6 after D1 and D2, which have no rows yet; the goal order sets priority |
|
||||
| 159 | I4 the Console in a container with signed-in human authority | darkwing; reviewers dewey, filbert | #1654 | queued | 158 done | Filbert and Dewey approve on #1654 naming the candidate manifest; Sage's full gate green on the landing tree; lands before M3 (sage) | `docs/plans/2026-10-18_containerized-install-rows.md` § I4 the Console in a container with signed-in human authority | Decision 96 orders I2-I6 after D1 and D2, which have no rows yet; the goal order sets priority. B1 is decision 147 |
|
||||
| 160 | I5 managed sessions as containers | researcher; reviewers dewey, filbert | #1655 | queued | 158 done | Filbert and Dewey approve on #1655 naming the candidate manifest; Sage's full gate green on the landing tree (sage) | `docs/plans/2026-10-18_containerized-install-rows.md` § I5 managed sessions as containers | Decision 96 orders I2-I6 after D1 and D2, which have no rows yet; the goal order sets priority |
|
||||
@@ -244,7 +244,7 @@ Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not e
|
||||
| 205 | 193b1a: the message filter (#1703; FED R3 and 193b1b build on it) | morgana; reviewers duckworth | #1703 | done | 196 done | Duckworth approves the manifest; bus suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1a, the message filter | Landed ce6a96ad6 fast-forward on 0fa7bf421 (3 commits, 4 paths, manifest 858463b1). Duckworth APPROVE r1 c30375 (signed :124 test diff, mos-1262). Gate: Morgana slot205 10 files, bus 652/0, M1 M11-M14 killed, 0 skips; Sage test-*.sh 10/10, 3 by-design skips. N1/N3 to 205b or R3, N2 to 193b2 brief, N4 = #1782 before any user thread. Rebase next: 268 r4, 194, 236, 253, 274. |
|
||||
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | launchpad; reviewers duckworth | #1704 | done | 205 done, 234 done, 236 done, 275 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | Landed 0cbe2e9b3 fast-forward on 17f415792 (10 commits, 22 paths), pushed 19:47Z. Duckworth APPROVE rev 3322 c30795 on 0cbe2e9b3; request rev 3320/3321 c30793 posted as jarvis. Gate (Launchpad, 9538e0fc8, tip differs in BUILD.md only): bus 850/0, cli 869/0, ten scripts/test-*.sh rc=0, touched files whole green, M2-M4 M7-M9 and Q2 killed. One placed fixture red (mos-1272). Schema 3l to 3m; live --migrate is Mos's go under Q173. |
|
||||
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | megavolt; reviewers duckworth | #1705 | done | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | LANDED c4f77373f 22:49Z 10-22 (fast-forward from f0e3dcdf4). Rebuild of gated 9bb406c6c: range-diff 4/4 =, patch-ids equal, 4 manifest blobs equal. Duckworth APPROVE c30849 rev 3356. Gate on 712f79c0a under sage-3041: access.test 31/31, M1-M8 killed, cli 894/894, ten scripts green. N1 DEFERRED (next access.test row). No live change. |
|
||||
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done, 294 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
|
||||
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | poe; reviewers duckworth, gladstone | #1706 | done | 192 done, 206 done, 294 done | Gladstone (Codex, first review) and Duckworth approve the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | LANDED f58049de0 (build 4a4bef6c1, Duckworth note f58049de0), combined with row 326 on one full gate 04:36-04:51Z: all package suites except queue 0 fail 0 skip (webui 418, plugins 119, cli 986), every scripts/test-*.sh green. Gladstone c31495, Duckworth c31496. #1706 closed c31504. N1 user.js 5 s re-render to DEFERRED. Bundle 9fc7754a. |
|
||||
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
|
||||
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | done | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree. |
|
||||
| 211 | Force-push guard: scripts/git-hooks/pre-push refuses non-fast-forward and delete pushes from every worktree, and scripts/slot-busy.sh exits 1 on BUSY (#1709, Vikunja #426, mos-1078/1083) | darkwing; reviewers dewey | #1709 | in-progress | — | Dewey approves the manifest; the new or changed scripts/test-*.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; hook installed in the canonical checkout with a scripted refused force-push (sage) | `docs/plans/2026-10-20_force-push-guard.md` § Brief: a pre-push hook that refuses force-pushes, and a busy check that fails on BUSY (#426) | — |
|
||||
@@ -301,7 +301,7 @@ Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not e
|
||||
| 262 | Email E06: IMAP source module (post-v1) | unassigned | #1767 | queued | 261 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E06 IMAP source module | — |
|
||||
| 263 | Email E07: address and rule evaluator (post-v1) | unassigned | #1768 | queued | 258 done, 259 done, 260 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E07 address and rule evaluator | — |
|
||||
| 264 | T1 item 7: independent sessions, compaction counts and clear context | gizmoduck; reviewers duckworth | #1769 | done | — | The named reviewer approves the manifest. The design's T1-T7 acceptance tests run whole plus every touched suite on Sage's slot; T3 waits on Vikunja 466 (N2, Claude's compaction count format and the managed dataRoot reader). The design's section 6 mutants are killed. (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | LANDED 84f53540d ff on e479521d3 14:18:23Z, 21 commits = gizmoduck/row264-r13 tip (no rewrite, range-diff identity). Manifest 257408e9 approved Duckworth rev 3214 (c30640). Gate r13 2cc34aef8: 20 files, bus 827, cli 837, webui 345/1 skip, 10 shells; M1-M4 assertion kills (mos-1279 B, M2 4 files). Overlap none (13, 257). N1 actor_run binds row 269; N2 esbuild skip later gate (sage-2695). |
|
||||
| 265 | Case 2 C2-09b rooms: room.open, room.send with @tags, room.read | rocko; reviewers dewey | #1770 | queued | 219 done | Dewey approves the manifest. The brief's tests run whole plus the bus suite and every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09b rooms: room.open, room.send with @tags, room.read | — |
|
||||
| 265 | Case 2 C2-09b rooms: room.open, room.send with @tags, room.read | goldie; reviewers boyd | #1770 | done | 219 done | Boyd approves the manifest. The brief's tests run whole plus the bus suite and every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09b rooms: room.open, room.send with @tags, room.read | LANDED 200dd396c (cherry-pick of ef4c93853, 50 blobs equal, log appends equal) 05:13Z on full gate 04:57-05:12Z: all package suites except queue 0 fail 0 skip (bus 957, cli 986, seat 218), every scripts/test-*.sh green. Boyd c31501 incl mos-1567 mount read. 22 gzip logs scanned decompressed, clean. #1770 closed c31510. Bundle 02c3c286. |
|
||||
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | researcher; reviewers dewey | #1771 | queued | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | Haiku high subagent under decision 129. |
|
||||
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
|
||||
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | done | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | Landed c27207f6e fast-forward on ce6a96ad6 (1 commit, 9 paths, digest caa0fa4e). r4 is a carry of r3 aca53c4f0: range-diff =, patch-id 2942acc8 equal, blobs equal. Webby APPROVE round 2 on ecd9b35eb stands; carries webby-0112 (r3) and webby-0113 (r4, notes 34e1ab2bf). Gate: Gizmoduck r4 rerun brief 14/14, library 83/83, business 209/209, 0 skips; gate grep empty. 194 rebases next (business.test.mjs). |
|
||||
@@ -345,8 +345,8 @@ Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not e
|
||||
| 306 | A revoked run's late bus call stops it instead of exiting 22 (295 N1) | morgana; reviewers dewey | #1813 | done | 272 done | Dewey approves the design, then the manifest; bus, harness and cli suites and every scripts/test-*.sh green on Sage's gate (sage) | `docs/plans/2026-10-23_gate-env-rows.md` § Brief: a revoked run's late bus call stops it instead of exiting 22 (295 N1) | Landed 2d0318a44 on 4874d1e83: 33d3844a0..683833205 replayed, 4 patch-ids equal (a1960f36 55fadbc3 af1a3d88 35cf81a3), range-diff all =. Dewey APPROVE c31206 rev 3596 on 683833205. access-revoke.test numstat 7 7 (mos-1463). Sage gate 16:33Z: every package suite 0 fail 0 skipped (bus 894, cli 942, plugins 119, webui 392, business 231, harness 151), every scripts/test-*.sh rc 0, M1-M3 each killed on every named file, restores equal. N1/N2 to DEFERRED next. |
|
||||
| 307 | Row 270's pi compaction reading moves behind the harness kind (REQ-PLUG-2, Vikunja 598) | gizmoduck; reviewers duckworth | #1814 | done | — | Duckworth approves the design, then the manifest; bus, cli, plugins (0 failures), webui and business suites, every scripts/test-*.sh green and the mutants killed on Sage's gate (sage) | `docs/plans/2026-10-23_harness-kind-compactions.md` § Brief: row 270's pi compaction reading moves behind the harness kind (Vikunja 598) | LANDED d243f6b25: 6 row commits replayed onto 8dc132bec, every non-log patch-id equal, candidate paths = 6fdfe3790 (manifest 17c9f8d9). Duckworth APPROVE rev 3586 c31189 round 1, C2 met. Sage gate: bus, cli 0 skipped, plugins fully green incl harness.test.mjs:192, webui, business, seat, every scripts/test-*.sh; M1-M4 killed. P19 598 commented. DEFERRED: 307 N1 store-unreadable not sticky. N3 doc-only BUILD.md red list one short, next BUILD.md change. |
|
||||
| 308 | PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | gosalyn; reviewers webby | #1815 | done | 291 done | Webby checks the PRD diff against DRAFT-d9.md at 4607e15b8 one instruction at a time and that nothing else changed. No new Jason question (Q239 approved the text). tasks whole, bus whole, cli stack-copy.test and every scripts/test-*.sh green on Sage's gate. Mos gets the landing commit (mos-1470). (sage) | `docs/plans/2026-10-23_prd-1-17-brief.md` § PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | Landed 30e6bb680: Gosalyn 45fe7b3ce replayed onto 7defb58e9, patch-id 4658c831 equal, range-diff =, four blobs equal. Webby r1 APPROVE c31224 rev 3606, d9 at 4607e15b8 applied word for word. Sage gate 17:19Z: every package suite 0 fail 0 skipped (tasks 100, bus 894, cli 942, plugins 119, webui 392), every scripts/test-*.sh rc 0. Mos gets the sha (mos-1470). 570/571 build rows next. |
|
||||
| 309 | 571 W1: reviewed local thesaurus data (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W1 reviewed local thesaurus data | — |
|
||||
| 310 | 571 W2: WordCraft source import and reproducible restricted web build (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W2 WordCraft source import and reproducible restricted web build | — |
|
||||
| 309 | 571 W1: reviewed local thesaurus data (post-10-27) | glomgold; reviewers boyd, duckworth | #1823 | in-review, round 1, request posted | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W1 reviewed local thesaurus data | — |
|
||||
| 310 | 571 W2: WordCraft source import and reproducible restricted web build (post-10-27) | gyro; reviewers filbert, gladstone | #1824 | in-progress | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W2 WordCraft source import and reproducible restricted web build | — |
|
||||
| 311 | 571 W3a: bus pins and human-only editor settings (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3a bus pins and human-only editor settings | — |
|
||||
| 312 | 571 W3b: human DOCX store and instant commits (post-10-27) | unassigned | — | queued | 311 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3b human DOCX store and instant commits | — |
|
||||
| 313 | 571 W6: owned browser bridge and editor-only route (post-10-27) | unassigned | — | queued | 309 done, 310 done, 312 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W6 owned browser bridge and editor-only route | — |
|
||||
@@ -361,7 +361,9 @@ Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not e
|
||||
| 322 | F8/F9: case2-install.sh runs the library persona import from the stack copy, and its refusal carries the error line (433 Run B, ops-0273) | goldie; reviewers morgana | #1818 | required; done | — | Morgana approves (install path). The new case2-install test fails on 094124b4e and passes on the candidate; a mutant restoring REPO at :185 is killed; Sage's full landing gate runs every package suite and scripts/test-*.sh. Then ops-01 reruns Run B P1. (sage) | `docs/plans/2026-10-23_host-fixes-1816-brief.md` § Brief: F8 and F9, the library persona import runs without the clone's node_modules | LANDED 467b05fe2, cherry-pick of 7b4ea4217 on 860afc2b6; added and removed lines identical, patch-id differs only by BUILD-LOG/SESSIONS append context (both sides kept). Pushed with tip 3569bf54f 23:32:54Z 10-23. Morgana APPROVE c31451 rev 3758, N1-N3 non-blocking. Sage full gate GREEN 23:32:28Z on 3569bf54f, all suites 0 fail, every scripts/test-*.sh rc 0. Next: ops-01 Run B P1 rerun on 467b05fe2 (mos-1553). Live unchanged 45dca69f6. |
|
||||
| 323 | Case 1 factory business file for .53: docs/guides/case1-business.mosaic-stack.json plus a loader and factory-check test (#414 stage 2, ops-0277) | goldie; reviewers duckworth | #1819 | done | — | Sage full gate: all package suites except queue plus every scripts/test-*.sh (mos-1455); Duckworth review; new test file and one bus.tester mutant on CLEAR (sage) | `docs/plans/2026-10-23_case1-business-file-brief.md` § Brief: the Case 1 factory business file | LANDED 571376c30 + evidence 6a0f0f4f8 (ff from dd6cb5872), tip 99930362e. Duckworth approve c31477 rev 3778 on 6a0f0f4f8. Sage gate green 00:54Z on 6a0f0f4f8: all package suites 0 fail, every scripts/test-*.sh rc 0. Root cause of the revoke-window red: TMPDIR length, tools.sock over 107 bytes (session.mjs:278). Brief template rule and DEFERRED entry in c25799928 (mos-1561). N1/N2 doc only. |
|
||||
| 324 | Tracker optional in the business file (Q247) | goldie; reviewers duckworth | #1820 | done | — | New tests only. Absent tracker: loader and validate pass, the bus boots with no credential-file error and no @sync. Present tracker unchanged. A mutant that skips the presence check is killed. Then whole touched test files, then the full landing gate. (sage) | `docs/plans/2026-10-23_optional-tracker-brief.md` § Brief: tracker becomes optional in the business file | LANDED 7e59ba8b3 + evidence dbdfcfd52 (ff from 3569bf54f), pushed with tip dd6cb5872 00:06:58Z 10-24. Duckworth APPROVE c31470 rev 3770 (static, N1 doc only). Goldie: new 5/5 and 4/4, business.test 21/21, presence mutant killed. Sage full gate GREEN 00:05:49Z on 64ea54083: every package suite 0 fail 0 skip (business 254, cli 986, bus 924, plugins 119, webui 411), every scripts/test-*.sh rc 0. Live unchanged 45dca69f6. Run B N1 for ops-01 P2/P3 (mos-1558). |
|
||||
| 325 | Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563) | goldie; reviewers gladstone | #1821 | in-review, round 1, request posted | — | New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455) (sage) | `docs/plans/2026-10-24_case1-project-key-brief.md` § Brief: re-key the Case 1 project to stack | — |
|
||||
| 325 | Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563) | goldie; reviewers gladstone | #1821 | done | — | New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455) (sage) | `docs/plans/2026-10-24_case1-project-key-brief.md` § Brief: re-key the Case 1 project to stack | LANDED c5f96836d (ff from 99930362e), tip 3d2569ce2. Gladstone approve c31484 rev 3788 on c5f96836d. Sage gate green 01:31Z on c5f96836d: all package suites 0 fail (business 264, cli 986), every scripts/test-*.sh rc 0. Project key is now stack, matching .mosaic/project.json; new case1-project-key test checks the real project file. Old-test line :70 under mos-1564. |
|
||||
| 326 | Host run sheet: five operator gaps (B2-2 proof format, 323 N2, pre-install key check, failed-first-install retry, backup SHA256SUMS form) | goldie; reviewers gladstone | #1822 | done | — | Gladstone approves the manifest; every new claim cites its line; no test reads the sheet; Sage's full landing gate green (sage) | `docs/plans/2026-10-24_run-sheet-doc-brief.md` § Brief: five run-sheet gaps | LANDED 33f49f7d5 (cherry-pick of a1b13c60e, patch-id match, sheet e4c14516) in f58049de0 with row 208 on one full gate 04:36-04:51Z, all suites green. Gladstone c31489. #1822 closed c31507. B2-2 closes in DEFERRED. Bundle 9fc7754a. |
|
||||
| 327 | Push check in the repository: scripts/push-check.sh passes a binary only when an approved review candidate lists its path and sha256, plus scripts/test-push-check.sh (#1825, mos-1572 A) | bradford; reviewers dewey, poe | #1825 | done | — | Poe gives the first Codex review, Dewey reviews the landing candidate; scripts/test-push-check.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; Sage's next push runs through scripts/push-check.sh (sage) | `docs/plans/2026-10-24_push-guard-binary-manifest.md` § Brief: push-check.sh passes a binary only when the reviewed candidate lists its path and sha256 | LANDED 68c08b4e7 (pushed 07:18:03Z 10-24): 3 commits on 6a5e12511, blobs equal round 3 d858c9f8b; Poe c31525 rev 3882, Dewey c31526 rev 3883; gate GREEN 07:15:49Z with its own npm ci (first run red on empty canonical node_modules, sage-4023); 6 push-check mutants KILLED; next push 42ba0f5bc ran through scripts/push-check.sh --fetch, PUSH_READY; #1825 closed c31530. |
|
||||
|
||||
<!-- mosaic-queue:end -->
|
||||
|
||||
|
||||
+3046
-54
File diff suppressed because it is too large.
Load diff
@@ -242,6 +242,9 @@ a receipt with empty `changes`. A refusal is `action.refused` with its code alon
|
||||
| `replies` | `{id}`: a message UUID; human and reader sessions only. The messages whose `in_reply_to` is that id, oldest first, at most 50. Writes nothing. |
|
||||
| `project.create` | `{name,repo?}`; human sessions only (row 121, #1615). `repo` must name one of the business's `projects` (`unknown-repo`); a second project with the same name refuses `project-exists`. Returns `{id}`. |
|
||||
| `project.list` | No args; human sessions only. The business's projects, oldest first. |
|
||||
| `room.open` | `{project}`; human only. Creates/reuses the caller's canonical member threads, named by sorted `THREAD_DEFINITIONS` instance keys. A taken name/owner/instance refuses `room-thread-name-taken` atomically. Returns `{project,threads:[{id,instance,name}]}`. |
|
||||
| `room.send` | `{project,body}`; human only, room must be fully open (`room-not-open`). Leading whitespace and `@name` tokens (including a final EOF tag) address current display names or member keys; no tags broadcasts, repeats send once. Unknown/ambiguous members refuse `unknown-addressee`/`addressee-ambiguous`; tags-only refuses `invalid-request`. Stores the original body, one normal message/route/human.input per target in one transaction, with one shared message timestamp. Returns `{ids,to}` in member-key order. |
|
||||
| `room.read` | `{project,after?,limit?}`; human only, complete owned room. Oldest first across all sessions of member threads. Returns entries with first-message fields plus `thread`, `session`, `instance`, `ids` and `to`. Only consecutive human rows with equal at/body/class and distinct instances merge; repeated instances split identical sends. `limit` counts merged entries (default 50, 1–200). Continue after the last entry's last `ids` element; an `after` outside the room refuses `message-not-found`. No tester access or schema change. |
|
||||
| `thread.create` | `{project,name}`; human sessions only. The thread belongs to the caller and to the business's one thread instance (Projects and threads, below). Returns `{id,instance}`. Refuses `project-not-found`, `thread-exists`, `thread-instance-missing` or `thread-instance-ambiguous`. |
|
||||
| `thread.list` | `{project?,shared?}`; human sessions only. The caller's own threads, oldest first. With `shared: true` (row 206), the threads others own that the caller may read (Thread shares, below), each row adding `owner`. |
|
||||
| `thread.open` | `{thread,session?}`; human sessions only. Records a `thread_opens` row, which asks the launcher to start the thread's session. Returns `{opened}`. |
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// C2-09b: a pure leading-address parser, shared with the room UI's parity test.
|
||||
// The remainder is for empty-body checks; the broker always stores the original input.
|
||||
export function parseAddress(input) {
|
||||
if (typeof input !== 'string') throw new TypeError('body must be a string');
|
||||
const tags = [];
|
||||
let body = input.trimStart();
|
||||
while (true) {
|
||||
const token = /^@(\S+)(?:\s+|$)/u.exec(body);
|
||||
if (!token) break;
|
||||
tags.push(token[1]);
|
||||
body = body.slice(token[0].length);
|
||||
}
|
||||
return { tags, body };
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
||||
import { parseAddress } from './address.mjs';
|
||||
import { isAbsolute, dirname } from 'node:path';
|
||||
import { initializeControl, processingStatus } from '../../processing-control/src/state.mjs';
|
||||
import { NAME as HARNESS_NAME } from '../../plugins/src/kinds.mjs';
|
||||
@@ -6418,6 +6419,99 @@ export class Broker {
|
||||
.all('SELECT id,at,name,repo FROM projects WHERE business=? ORDER BY seq', s.business)
|
||||
.filter((p) => !s.user || this.#granted(s, 'project.list', p.id, null));
|
||||
}
|
||||
// C2-09b: canonical per-instance threads form one human-owned project room.
|
||||
case 'room.open':
|
||||
case 'room.send':
|
||||
case 'room.read': {
|
||||
this.#human(s);
|
||||
const opening = verb === 'room.open';
|
||||
const sending = verb === 'room.send';
|
||||
keys(a, opening ? ['project'] : sending ? ['project', 'body'] : ['project', 'after', 'limit'],
|
||||
sending ? ['project', 'body'] : ['project']);
|
||||
const project = identifier(a.project);
|
||||
if (!this.#store.get('SELECT 1 FROM projects WHERE id=? AND business=?', project, s.business)) fail('project-not-found');
|
||||
const members = Object.keys(b.roles).filter((instance) => this.#threaded(s.business, instance)).sort();
|
||||
if (!members.length) fail('thread-instance-missing');
|
||||
const owner = this.#owner(s);
|
||||
// Preflight all collisions/missing threads before open creates or send writes anything.
|
||||
const threads = members.map((instance) => {
|
||||
const t = this.#store.get('SELECT * FROM threads WHERE project=? AND name=?', project, instance);
|
||||
if (t && (t.instance !== instance || t.owner !== owner))
|
||||
fail(opening ? 'room-thread-name-taken' : 'room-not-open');
|
||||
if (!t && !opening) fail('room-not-open');
|
||||
return t ?? { instance, name: instance };
|
||||
});
|
||||
if (opening) {
|
||||
return { project, threads: threads.map((t) => ({
|
||||
id: t.id ?? this.#dispatch(s, 'thread.create', { project, name: t.name, instance: t.instance }).id,
|
||||
instance: t.instance, name: t.name,
|
||||
})) };
|
||||
}
|
||||
if (sending) {
|
||||
string(a.body, 32768);
|
||||
const addressed = parseAddress(a.body);
|
||||
if (!addressed.body.trim()) fail('invalid-request');
|
||||
const selected = new Set();
|
||||
for (const tag of addressed.tags) {
|
||||
const candidates = new Set([this.#named(s.business, tag), tag].filter((instance) => members.includes(instance)));
|
||||
if (!candidates.size) fail('unknown-addressee');
|
||||
if (candidates.size > 1) fail('addressee-ambiguous');
|
||||
selected.add([...candidates][0]);
|
||||
}
|
||||
const targets = threads.filter((t) => !addressed.tags.length || selected.has(t.instance));
|
||||
const sessions = targets.map((t) => {
|
||||
const session = this.#defaultSession(t.id);
|
||||
this.#sessionFence(session);
|
||||
this.#checkMessaging(s.business, 'human', t.instance);
|
||||
return session;
|
||||
});
|
||||
const at = this.#now();
|
||||
const ids = targets.map((t, i) => {
|
||||
const mid = randomUUID();
|
||||
this.#store.run(
|
||||
'INSERT INTO messages(id,at,business,from_role,from_run,to_role,class,in_reply_to,decision,corrects,body) VALUES(?,?,?,?,?,?,?,?,?,?,?)',
|
||||
mid, at, s.business, 'human', 'human-cli', t.instance, 'INFO', null, null, null, a.body,
|
||||
);
|
||||
this.#route(s.business, mid, t.instance, sessions[i]);
|
||||
this.#event(s, 'human.input', { kind: 'instruction', message: mid }, mid);
|
||||
return mid;
|
||||
});
|
||||
return { ids, to: targets.map((t) => t.instance) };
|
||||
}
|
||||
const limit = a.limit ?? THREAD_PAGE;
|
||||
if (!Number.isInteger(limit) || limit < 1 || limit > THREAD_PAGE_MAX) fail('invalid-request');
|
||||
const tids = threads.map((t) => t.id);
|
||||
const placeholders = tids.map(() => '?').join(',');
|
||||
let after = 0;
|
||||
if (a.after !== undefined) {
|
||||
after = this.#store.get(
|
||||
`SELECT m.seq FROM messages m JOIN message_threads mt ON mt.message=m.id WHERE m.id=? AND m.business=? AND mt.thread IN (${placeholders})`,
|
||||
identifier(a.after), s.business, ...tids,
|
||||
)?.seq;
|
||||
if (after === undefined) fail('message-not-found');
|
||||
}
|
||||
// At most one row per instance joins a group; one lookahead row completes the last page.
|
||||
const rows = this.#store.all(
|
||||
`SELECT m.*,mt.thread,mt.session,t.instance FROM messages m JOIN message_threads mt ON mt.message=m.id JOIN threads t ON t.id=mt.thread
|
||||
WHERE m.business=? AND mt.thread IN (${placeholders}) AND m.seq>? ORDER BY m.seq LIMIT ${limit * members.length + 1}`,
|
||||
s.business, ...tids, after,
|
||||
);
|
||||
const entries = [];
|
||||
let previous;
|
||||
for (const m of rows) {
|
||||
const entry = entries.at(-1);
|
||||
if (entry && m.from_role === 'human' && previous.from_role === 'human' && m.seq === previous.seq + 1
|
||||
&& m.at === previous.at && m.body === previous.body && m.class === previous.class && !entry.to.includes(m.instance)) {
|
||||
entry.ids.push(m.id);
|
||||
entry.to.push(m.instance);
|
||||
} else {
|
||||
if (entries.length === limit) break;
|
||||
entries.push({ ...m, ids: [m.id], to: [m.instance] });
|
||||
}
|
||||
previous = m;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
// A thread belongs to the user who made it and to one declared thread instance.
|
||||
// Row 177 (#1675): a tester's thread is owned by `tester`, so neither reaches the other's.
|
||||
case 'thread.create': {
|
||||
|
||||
@@ -3,3 +3,4 @@ export { startBroker } from './runtime.mjs';
|
||||
export { Client } from './client.mjs';
|
||||
export { views } from './views.mjs';
|
||||
export { busBusiness } from './business.mjs';
|
||||
export { parseAddress } from './address.mjs';
|
||||
@@ -0,0 +1,16 @@
|
||||
// C2-09b / C2-10c: reusable parser vectors. Keep the expected result independent of the parser.
|
||||
export const addressCases = [
|
||||
['one', '@continuity x', { tags: ['continuity'], body: 'x' }],
|
||||
['many', '@bean @chet hello', { tags: ['bean', 'chet'], body: 'hello' }],
|
||||
['repeat', '@bean @bean hi', { tags: ['bean', 'bean'], body: 'hi' }],
|
||||
['plain', 'hello @bean', { tags: [], body: 'hello @bean' }],
|
||||
['stop', '@bean hello @chet', { tags: ['bean'], body: 'hello @chet' }],
|
||||
['EOF tag', '@continuity', { tags: ['continuity'], body: '' }],
|
||||
['EOF last tag', '@bean @chet', { tags: ['bean', 'chet'], body: '' }],
|
||||
['leading whitespace', ' \t\n@bean\t@chet\nhello ', { tags: ['bean', 'chet'], body: 'hello ' }],
|
||||
['tags only', '@bean \t\n', { tags: ['bean'], body: '' }],
|
||||
['empty', '', { tags: [], body: '' }],
|
||||
['whitespace', ' \t', { tags: [], body: '' }],
|
||||
['bare at', '@ hello', { tags: [], body: '@ hello' }],
|
||||
['punctuation is a name', '@unknown, hi', { tags: ['unknown,'], body: 'hi' }],
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { parseAddress } from '../src/address.mjs';
|
||||
import { addressCases } from './address-cases.mjs';
|
||||
|
||||
for (const [label, input, expected] of addressCases) {
|
||||
test(`parseAddress: ${label}`, () => assert.deepEqual(parseAddress(input), expected));
|
||||
}
|
||||
test('parseAddress rejects non-text without coercion', () => {
|
||||
for (const input of [null, undefined, {}, 1]) assert.throws(() => parseAddress(input), TypeError);
|
||||
});
|
||||
|
||||
// mos-1567: new coverage, separate from the pre-existing broker import pin.
|
||||
test('the mounted address parser has no import dependency', () => {
|
||||
assert.doesNotMatch(readFileSync(new URL('../src/address.mjs', import.meta.url), 'utf8'), /\bimport\b/);
|
||||
});
|
||||
@@ -14,6 +14,8 @@ test('Broker keeps the sandbox client import closure: only the listed imports, n
|
||||
assert.deepEqual([...source.matchAll(/^import .*;$/gm)].map(m => m[0]), [
|
||||
// Row 210 (#1707): createHash gives host.run events the argv, stdin and output sha256 values.
|
||||
"import { randomUUID, randomBytes, createHash } from 'node:crypto';",
|
||||
// Row 265 (#1770): pure room address parsing, mounted in RUNNER_FILES beside the broker.
|
||||
"import { parseAddress } from './address.mjs';",
|
||||
// Row 65 (#1558): bindLaunch checks a run's transcript path is absolute.
|
||||
"import { isAbsolute, dirname } from 'node:path';",
|
||||
// Row 191 / sage-1367: the one additional read-only CODE source, with no project/git imports.
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Store } from '../src/store.mjs';
|
||||
import { Broker, THREAD_PAGE, THREAD_PAGE_MAX, TESTER_VERBS } from '../src/broker.mjs';
|
||||
|
||||
const MEMBERS = ['bean', 'chet', 'continuity'];
|
||||
const shipped = (name) => JSON.parse(readFileSync(new URL(`../../../roles/${name}.json`, import.meta.url), 'utf8'));
|
||||
function setup(t, { deny = false, empty = false } = {}) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'bus-rooms-'));
|
||||
const store = new Store(root);
|
||||
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
|
||||
const role = (definition) => ({ definition, authority: { withinRole: ['message.send'], crossRole: [] } });
|
||||
// Deliberately unsorted, with two instances of one definition and a non-member chief.
|
||||
const roles = { continuity: role('continuity'), chet: role('companion'), bean: role('companion'), chief: role('chief-of-staff') };
|
||||
const definitions = {
|
||||
'chief-of-staff': {}, companion: { messaging: shipped('companion').messaging },
|
||||
continuity: { messaging: deny ? { sendTo: ['human'], receiveFrom: [] } : shipped('continuity').messaging },
|
||||
};
|
||||
const b = new Broker({ store, businesses: { demo: {
|
||||
id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' },
|
||||
roles: empty ? { chief: roles.chief } : roles, definitions,
|
||||
projects: { stack: { root: '/nonexistent' } }, vars: { 'bus.tester': true },
|
||||
} } });
|
||||
const caps = {
|
||||
human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }),
|
||||
tester: b.bindTester({ business: 'demo' }),
|
||||
};
|
||||
const call = (verb, args = {}, who = 'human') => b.request(caps[who], { verb, args });
|
||||
caps.chief = b.bindLaunch({ business: 'demo', role: 'chief', run: 'chief-run', harness: 'pi' });
|
||||
call('role.claim', {}, 'chief');
|
||||
const project = call('project.create', { name: 'room', repo: 'stack' }).id;
|
||||
const open = () => call('room.open', { project });
|
||||
const send = (body, extra = {}) => call('room.send', { project, body, ...extra });
|
||||
const read = (extra = {}) => call('room.read', { project, ...extra });
|
||||
const effects = () => ['messages', 'message_threads', 'deliveries'].map((table) => store.get(`SELECT count(*) n FROM ${table}`).n)
|
||||
.concat(store.get("SELECT count(*) n FROM events WHERE kind='human.input'").n);
|
||||
const refuse = (code, fn) => {
|
||||
const before = effects();
|
||||
assert.throws(fn, { code });
|
||||
assert.deepEqual(effects(), before, `${code}: no message, route, delivery or human.input`);
|
||||
};
|
||||
return { b, caps, store, call, project, open, send, read, effects, refuse };
|
||||
}
|
||||
|
||||
test('room.open sorts all threaded instances, creates defaults, and reuses caller-owned threads', (t) => {
|
||||
const { open, call, project, store } = setup(t);
|
||||
const existing = call('thread.create', { project, name: 'chet', instance: 'chet' }).id;
|
||||
const result = open();
|
||||
assert.equal(result.project, project);
|
||||
assert.deepEqual(result.threads.map(({ instance, name }) => [instance, name]), MEMBERS.map((x) => [x, x]));
|
||||
assert.equal(result.threads[1].id, existing);
|
||||
for (const thread of result.threads) {
|
||||
assert.equal(store.get('SELECT owner FROM threads WHERE id=?', thread.id).owner, 'jason');
|
||||
assert.equal(call('session.list', { thread: thread.id }).filter((s) => s.default).length, 1);
|
||||
}
|
||||
assert.deepEqual(open(), result);
|
||||
assert.equal(store.get('SELECT count(*) n FROM threads').n, 3);
|
||||
});
|
||||
|
||||
for (const collision of ['instance', 'owner']) {
|
||||
test(`room.open preflights ${collision} collisions atomically`, (t) => {
|
||||
const { call, project, open, store } = setup(t);
|
||||
call('thread.create', { project, name: 'continuity', instance: collision === 'instance' ? 'bean' : 'continuity' },
|
||||
collision === 'owner' ? 'tester' : 'human');
|
||||
assert.throws(open, { code: 'room-thread-name-taken' });
|
||||
assert.deepEqual(store.all('SELECT name FROM threads').map((r) => r.name), ['continuity']);
|
||||
assert.equal(store.get('SELECT count(*) n FROM sessions').n, 1);
|
||||
});
|
||||
}
|
||||
|
||||
test('room verbs require the human, and none is a tester verb', (t) => {
|
||||
const { call, project, open, refuse } = setup(t);
|
||||
open();
|
||||
for (const verb of ['room.open', 'room.send', 'room.read']) {
|
||||
const args = { project, ...(verb === 'room.send' ? { body: 'hello' } : {}) };
|
||||
assert.ok(!TESTER_VERBS.includes(verb));
|
||||
refuse('tester-refused', () => call(verb, args, 'tester'));
|
||||
refuse('human-required', () => call(verb, args, 'chief'));
|
||||
}
|
||||
});
|
||||
|
||||
test('room verbs refuse missing projects and zero member businesses', (t) => {
|
||||
const { call, refuse } = setup(t);
|
||||
for (const verb of ['room.open', 'room.send', 'room.read']) {
|
||||
refuse('project-not-found', () => call(verb, { project: randomUUID(), ...(verb === 'room.send' ? { body: 'hello' } : {}) }));
|
||||
}
|
||||
const empty = setup(t, { empty: true });
|
||||
assert.throws(empty.open, { code: 'thread-instance-missing' });
|
||||
});
|
||||
|
||||
for (const state of ['missing', 'foreign-owner', 'wrong-instance']) {
|
||||
test(`send/read refuse a ${state} canonical member without effects`, (t) => {
|
||||
const { call, project, send, read, refuse } = setup(t);
|
||||
for (const instance of MEMBERS) {
|
||||
if (instance === 'continuity' && state === 'missing') continue;
|
||||
call('thread.create', { project, name: instance, instance: instance === 'continuity' && state === 'wrong-instance' ? 'bean' : instance },
|
||||
instance === 'continuity' && state === 'foreign-owner' ? 'tester' : 'human');
|
||||
}
|
||||
refuse('room-not-open', () => send('hello'));
|
||||
refuse('room-not-open', read);
|
||||
});
|
||||
}
|
||||
|
||||
test('room.send targets a key, broadcasts without tags, and preserves original text and ordinary route/input rows', (t) => {
|
||||
const { open, send, store, read } = setup(t);
|
||||
const threads = open().threads;
|
||||
const one = send('@continuity x');
|
||||
assert.deepEqual(one.to, ['continuity']);
|
||||
assert.equal(one.ids.length, 1);
|
||||
assert.equal(store.get('SELECT thread FROM message_threads WHERE message=?', one.ids[0]).thread, threads[2].id);
|
||||
const body = ' \t@bean @chet hello @continuity';
|
||||
const two = send(body);
|
||||
assert.deepEqual(two.to, ['bean', 'chet']);
|
||||
const all = send('hello @bean');
|
||||
assert.deepEqual(all.to, MEMBERS);
|
||||
const rows = store.all('SELECT * FROM messages ORDER BY seq');
|
||||
assert.deepEqual(rows.map((r) => r.body), ['@continuity x', body, body, ...MEMBERS.map(() => 'hello @bean')]);
|
||||
assert.equal(new Set(rows.slice(-3).map((r) => r.at)).size, 1);
|
||||
assert.ok(rows.every((r) => r.from_role === 'human' && r.from_run === 'human-cli' && r.class === 'INFO'));
|
||||
assert.equal(store.get('SELECT count(*) n FROM message_threads').n, 6);
|
||||
assert.equal(store.get("SELECT count(*) n FROM deliveries WHERE op='routed'").n, 6);
|
||||
assert.equal(store.get("SELECT count(*) n FROM events WHERE kind='human.input'").n, 6);
|
||||
assert.deepEqual(read().map((r) => r.to), [['continuity'], ['bean', 'chet'], MEMBERS]);
|
||||
});
|
||||
|
||||
test('current display names and repeated tags resolve once; a replaced display name is unknown', (t) => {
|
||||
const { open, call, send, refuse } = setup(t);
|
||||
open();
|
||||
call('name.set', { instance: 'bean', name: 'beanie' });
|
||||
assert.deepEqual(send('@beanie @bean @beanie hi').to, ['bean']);
|
||||
call('name.set', { instance: 'bean', name: 'newbeanie' });
|
||||
refuse('unknown-addressee', () => send('@beanie hi'));
|
||||
assert.deepEqual(send('@newbeanie hi').to, ['bean']);
|
||||
});
|
||||
|
||||
test('a legacy name colliding with another member key is ambiguous; its own key resolves once', (t) => {
|
||||
const { open, send, store, refuse } = setup(t);
|
||||
open();
|
||||
// Naming policy now forbids key collisions; legacy data can still contain them. Append real rows.
|
||||
store.run('INSERT INTO instance_names(at,business,instance,name,by) VALUES(?,?,?,?,?)', new Date().toISOString(), 'demo', 'bean', 'chet', 'fixture');
|
||||
refuse('addressee-ambiguous', () => send('@chet hi'));
|
||||
store.run('INSERT INTO instance_names(at,business,instance,name,by) VALUES(?,?,?,?,?)', new Date().toISOString(), 'demo', 'bean', 'bean', 'fixture');
|
||||
assert.deepEqual(send('@bean @bean hi').to, ['bean']);
|
||||
});
|
||||
|
||||
test('unknown or non-member addressees and tags-only bodies leave no message effects', (t) => {
|
||||
const { open, send, refuse } = setup(t);
|
||||
open();
|
||||
for (const body of ['@nobody x', '@chief x', '@bean @nobody x']) refuse('unknown-addressee', () => send(body));
|
||||
for (const body of ['@continuity', ' \t@bean\n@chet ', '', ' \t']) refuse('invalid-request', () => send(body));
|
||||
});
|
||||
|
||||
test('messaging denial on the last target rolls back the entire broadcast; allowed subset still works', (t) => {
|
||||
const { open, send, refuse } = setup(t, { deny: true });
|
||||
open();
|
||||
refuse('message-sender-refused', () => send('all'));
|
||||
assert.deepEqual(send('@bean @chet okay').to, ['bean', 'chet']);
|
||||
});
|
||||
|
||||
test('a clearing last member fences the whole send; another active target still accepts', (t) => {
|
||||
const { b, caps, open, call, send, refuse } = setup(t);
|
||||
const thread = open().threads[2].id;
|
||||
caps.worker = b.bindLaunch({ business: 'demo', role: 'continuity', run: 'continuity-run', harness: 'pi', thread });
|
||||
call('role.claim', {}, 'worker');
|
||||
const session = call('session.list', { thread })[0].id;
|
||||
call('session.clear', { thread, session, revision: 0, op: 'room-clear', confirm: true });
|
||||
refuse('session-clearing', () => send('all'));
|
||||
assert.deepEqual(send('@bean okay').to, ['bean']);
|
||||
});
|
||||
|
||||
test('room.read groups one send, pages by the last grouped id, and rejects foreign or absent cursors', (t) => {
|
||||
const { open, send, read, call, project, refuse } = setup(t);
|
||||
const threads = open().threads;
|
||||
const first = send('first');
|
||||
const second = send('@continuity second');
|
||||
const page = read({ limit: 1 });
|
||||
assert.equal(page.length, 1);
|
||||
assert.deepEqual(page[0].ids, first.ids);
|
||||
assert.deepEqual(page[0].to, MEMBERS);
|
||||
assert.equal(page[0].thread, threads[0].id);
|
||||
assert.equal(page[0].instance, 'bean');
|
||||
assert.deepEqual(read({ after: first.ids.at(-1), limit: 1 }).map((r) => r.ids), [second.ids]);
|
||||
assert.deepEqual(read({ after: second.ids[0] }), []);
|
||||
const other = call('project.create', { name: 'other', repo: 'stack' }).id;
|
||||
const thread = call('thread.create', { project: other, name: 'bean', instance: 'bean' }).id;
|
||||
const foreign = call('thread.send', { thread, body: 'elsewhere' }).id;
|
||||
const noncanonical = call('thread.create', { project, name: 'side', instance: 'bean' }).id;
|
||||
const side = call('thread.send', { thread: noncanonical, body: 'side' }).id;
|
||||
for (const after of [foreign, side, randomUUID()]) refuse('message-not-found', () => read({ after }));
|
||||
for (const limit of [0, -1, 1.1, THREAD_PAGE_MAX + 1, '1']) refuse('invalid-request', () => read({ limit }));
|
||||
});
|
||||
|
||||
test('room.read keeps identical sends at the same timestamp separate and includes every session', (t) => {
|
||||
const { open, call, project, store, read } = setup(t);
|
||||
const threads = open().threads;
|
||||
const at = '2026-10-24T00:00:00.000Z';
|
||||
const batches = [[], []];
|
||||
// Broker clocks are monotonic. Append two independently valid transaction-shaped batches to
|
||||
// exercise the historical same-at boundary without altering production clocks or existing rows.
|
||||
for (const batch of batches) for (const thread of threads) {
|
||||
const id = randomUUID();
|
||||
batch.push(id);
|
||||
const session = call('session.list', { thread: thread.id })[0].id;
|
||||
store.run('INSERT INTO messages(id,at,business,from_role,from_run,to_role,class,body) VALUES(?,?,?,?,?,?,?,?)',
|
||||
id, at, 'demo', 'human', 'human-cli', thread.instance, 'INFO', 'same');
|
||||
store.run('INSERT INTO message_threads(message,thread,session) VALUES(?,?,?)', id, thread.id, session);
|
||||
}
|
||||
assert.deepEqual(read().map((r) => r.ids), batches);
|
||||
assert.deepEqual(read({ limit: 1 })[0].ids, batches[0]);
|
||||
assert.deepEqual(read({ after: batches[0].at(-1) }).map((r) => r.ids), [batches[1]]);
|
||||
const created = call('session.create', { thread: threads[0].id, name: 'later', op: 'new-later' });
|
||||
call('thread.send', { thread: threads[0].id, session: created.id, body: 'later session' });
|
||||
assert.equal(read().at(-1).body, 'later session');
|
||||
assert.equal(read().at(-1).session, created.id);
|
||||
assert.equal(read().at(-1).thread, threads[0].id);
|
||||
assert.equal(store.get('SELECT count(*) n FROM threads WHERE project=?', project).n, 3);
|
||||
});
|
||||
|
||||
test('room.read defaults and caps the number of merged entries, not recipient rows', (t) => {
|
||||
const { open, send, read } = setup(t);
|
||||
open();
|
||||
for (let i = 0; i <= THREAD_PAGE_MAX; i++) send(`message ${i}`);
|
||||
assert.equal(read().length, THREAD_PAGE);
|
||||
assert.equal(read({ limit: THREAD_PAGE_MAX }).length, THREAD_PAGE_MAX);
|
||||
assert.ok(read({ limit: 1 })[0].to.length === 3);
|
||||
});
|
||||
|
||||
test('room.read includes an agent reply with its thread and instance without merging it with human rows', (t) => {
|
||||
const { b, caps, open, call, send, read, refuse, project } = setup(t);
|
||||
const thread = open().threads[0].id;
|
||||
send('question');
|
||||
caps.worker = b.bindLaunch({ business: 'demo', role: 'bean', run: 'bean-run', harness: 'pi', thread });
|
||||
call('role.claim', {}, 'worker');
|
||||
const reply = call('message.send', { to: 'human', body: 'answer' }, 'worker');
|
||||
const entries = read();
|
||||
assert.equal(entries.length, 2);
|
||||
assert.deepEqual(entries[0].to, MEMBERS);
|
||||
assert.equal(entries[1].id, reply.id);
|
||||
assert.equal(entries[1].from_role, 'bean');
|
||||
assert.equal(entries[1].thread, thread);
|
||||
assert.equal(entries[1].instance, 'bean');
|
||||
refuse('human-required', () => call('room.read', { project }, 'worker'));
|
||||
});
|
||||
@@ -0,0 +1,234 @@
|
||||
// FED R7 / #1802. Run through scripts/test-federation-acceptance.sh: it owns
|
||||
// the child process groups and scratch lifetime. Ordinary CLI suites skip it.
|
||||
// sage-3625: federation-host.mjs supplies a labelled readProcess ancestry
|
||||
// stand-in for setup, grant/revoke and both Console transports. This is NOT
|
||||
// a genuine outside-agent human proof. Product transports/grant checks stay real.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { request } from 'node:http';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { businessDoc, launchDoc, writeJson } from '../../business/tests/helpers.mjs';
|
||||
import { REPO_SKILLS } from '../../harness/tests/helpers.mjs';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { SESSION_COOKIE } from '../../webui/src/sso.mjs';
|
||||
import { fakeOidc } from '../../webui/tests/helpers/fake-oidc.mjs';
|
||||
import { socketPath, REPO } from '../src/config.mjs';
|
||||
import { startTimeOf } from '../src/host.mjs';
|
||||
import { launchLogFile, readSessions } from '../../seat/src/session.mjs';
|
||||
|
||||
const root = process.env.MOSAIC_R7_ROOT;
|
||||
const helper = fileURLToPath(new URL('./fixtures/federation-human.mjs', import.meta.url));
|
||||
const adapterModule = fileURLToPath(new URL('./fixtures/thread-adapter.mjs', import.meta.url));
|
||||
const webCli = join(REPO, 'packages/webui/src/cli.mjs');
|
||||
const cli = join(REPO, 'packages/cli/src/cli.mjs');
|
||||
const pause = ms => new Promise(r => setTimeout(r, ms));
|
||||
const json = path => JSON.parse(readFileSync(path, 'utf8'));
|
||||
|
||||
function run(cmd, args, options = {}) {
|
||||
const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 30000, ...options });
|
||||
assert.equal(r.status, 0, `${cmd} ${args.join(' ')}: ${r.stderr ?? ''} ${r.error?.message ?? ''}`);
|
||||
return r.stdout;
|
||||
}
|
||||
async function until(read, label, ms = 20000) {
|
||||
const end = Date.now() + ms;
|
||||
while (Date.now() < end) {
|
||||
const value = await read();
|
||||
if (value) return value;
|
||||
await pause(50);
|
||||
}
|
||||
assert.fail(`R7 assertion: ${label}`);
|
||||
}
|
||||
|
||||
function http(instance, path, { cookie, body, method = body === undefined ? 'GET' : 'POST' } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const text = body === undefined ? undefined : JSON.stringify(body);
|
||||
const headers = { host: new URL(instance.origin).host };
|
||||
if (cookie) headers.cookie = cookie;
|
||||
if (text !== undefined) Object.assign(headers, { origin: instance.origin, 'sec-fetch-site': 'same-origin', 'content-type': 'application/json', 'content-length': Buffer.byteLength(text) });
|
||||
const req = request({ host: '127.0.0.1', port: instance.port, path, method, headers, timeout: 5000 }, res => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data && String(res.headers['content-type']).startsWith('application/json') ? JSON.parse(data) : data }));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => req.destroy(new Error('R7 HTTP timeout')));
|
||||
req.end(text);
|
||||
});
|
||||
}
|
||||
let serial = 0;
|
||||
function child(instance, label, command) {
|
||||
const log = join(root, `${instance.name}-${label}.log`);
|
||||
const fd = openSync(log, 'a', 0o600);
|
||||
let proc;
|
||||
try { proc = spawn(command[0], command.slice(1), { detached: true, env: instance.env, stdio: ['ignore', fd, fd] }); }
|
||||
finally { closeSync(fd); }
|
||||
proc.on('error', e => { console.error(`R7 child ${label}: ${e.message}`); });
|
||||
assert.ok(proc.pid, `R7 ${label} spawned`);
|
||||
const start = startTimeOf(proc.pid);
|
||||
assert.ok(start, `R7 ${label} start time recorded`);
|
||||
// EXIT trap owns the detached group even if Node's test driver is killed.
|
||||
writeFileSync(join(root, 'groups'), `${proc.pid} ${start}\n`, { flag: 'a' });
|
||||
proc.unref();
|
||||
return { log, proc };
|
||||
}
|
||||
// Jason-shell ancestry stand-in is in the fixture host; transport/path are real.
|
||||
function human(instance, verb, args = {}) {
|
||||
const out = join(root, `human-${++serial}.json`);
|
||||
run(process.execPath, [helper, socketPath(instance.dataRoot), instance.name, verb, JSON.stringify(args), out], { env: instance.env });
|
||||
return json(out);
|
||||
}
|
||||
// R5/R4 CLI with that same labelled fixture proof, never a live human shell.
|
||||
function access(instance, args) {
|
||||
return run(process.execPath, [cli, 'access', ...args, '--business', instance.name], { env: instance.env });
|
||||
}
|
||||
|
||||
function prepare(name, iss, ca) {
|
||||
const home = process.env.HOME;
|
||||
const env = { HOME: home, PATH: process.env.PATH, TMPDIR: process.env.TMPDIR };
|
||||
run(join(REPO, 'scripts/bootstrap.sh'), ['--instance', name], { env });
|
||||
const config = join(home, '.config', `mosaic-${name}`, 'config.json');
|
||||
const system = json(config), dataRoot = system.dataRoot, configDir = dirname(config);
|
||||
const fixtureRoot = join(root, name);
|
||||
mkdirSync(fixtureRoot, { recursive: true });
|
||||
const doc = businessDoc(fixtureRoot, name);
|
||||
doc.human = 'melanie';
|
||||
doc.plugins = { harness: ['pi'], channel: ['webui'] };
|
||||
doc.roles.chief.vars = { harness: 'pi', model: 'claude-opus-5-5', thinking: 'high' };
|
||||
doc.roles.assistant = { definition: 'assistant', vars: { harness: 'pi', model: 'claude-sonnet-5-5', thinking: 'high' } };
|
||||
doc.launch = launchDoc('chief', ['coder', 'assistant'], { opus: 1, sonnet: 2 });
|
||||
doc.launch.threadIdleSeconds = 3600;
|
||||
for (const role of Object.values(doc.roles)) role.vars = { harness: 'pi', model: 'glm-5.3-flash', thinking: 'high', ...role.vars };
|
||||
writeJson(join(configDir, 'businesses', `${name}.json`), doc);
|
||||
const origin = `https://${name}.example.test`;
|
||||
system.console = { sso: { publicOrigin: origin, providers: [{ id: 'test-op', issuer: iss.issuer, clientId: iss.clientId,
|
||||
clientSecret: { slot: name }, algs: ['RS256'], subjects: [{ sub: 'owner-sub', name: 'melanie' }] }] } };
|
||||
system.federation = { self: name, peers: [{ id: name === 'hub' ? 'factory' : 'hub', origin: `https://${name === 'hub' ? 'factory' : 'hub'}.example.test`, label: name === 'hub' ? 'Factory' : 'Hub' }] };
|
||||
writeJson(config, system);
|
||||
writeJson(join(configDir, 'access.json'), { accessVersion: 1, revision: 1, users: [], grants: [] });
|
||||
// Generated fake OP material only; never copies a host slot or credential.
|
||||
writeJson(join(configDir, 'secrets/mosaic-stack', name), iss.clientSecret);
|
||||
cpSync(REPO_SKILLS, join(dataRoot, 'skills-enabled'), { recursive: true });
|
||||
const projectRoot = doc.projects.stack.root;
|
||||
const gitEnv = { ...env, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null' };
|
||||
const git = (...args) => run('git', ['-c', 'user.name=R7 fixture', '-c', '[email protected]', '-C', projectRoot, ...args], { env: gitEnv });
|
||||
git('init', '-q', '-b', 'main');
|
||||
writeFileSync(join(projectRoot, 'file.txt'), `${name} fixture\n`);
|
||||
git('add', 'file.txt'); git('commit', '-q', '-m', 'scratch fixture');
|
||||
const adapter = join(fixtureRoot, 'adapter.sh');
|
||||
writeFileSync(adapter, `exec '${process.execPath}' '${adapterModule}'\n`);
|
||||
return { name, origin, configDir, dataRoot, adapter, env: { ...env, MOSAIC_CONFIG: config, NODE_EXTRA_CA_CERTS: ca }, port: null };
|
||||
}
|
||||
|
||||
async function signIn(instance, op, iss, sub) {
|
||||
const start = await http(instance, '/auth/login');
|
||||
assert.equal(start.status, 303, JSON.stringify(start.body));
|
||||
const location = new URL(start.headers.location);
|
||||
const { code } = op.grant(iss, location.href, { sub });
|
||||
const cookie = start.headers['set-cookie'][0].split(';')[0];
|
||||
const result = await http(instance, `/auth/callback?${new URLSearchParams({ code, state: location.searchParams.get('state') })}`, { cookie });
|
||||
const session = (result.headers['set-cookie'] ?? []).find(c => c.startsWith(`${SESSION_COOKIE}=`) && !c.includes('Max-Age=0'))?.split(';')[0];
|
||||
return { ...result, cookie: session };
|
||||
}
|
||||
const logs = i => existsSync(launchLogFile(i.dataRoot, i.name)) ? readFileSync(launchLogFile(i.dataRoot, i.name), 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse) : [];
|
||||
|
||||
function claimHistory(instance, run) {
|
||||
const store = new DatabaseSync(join(instance.dataRoot, 'bus/bus.sqlite'), { readOnly: true });
|
||||
try { return store.prepare('SELECT op, reason FROM thread_claims WHERE holder_run=? ORDER BY seq').all(run); }
|
||||
finally { store.close(); }
|
||||
}
|
||||
|
||||
test('FED R7 scratch two-instance acceptance', { skip: !root && 'run scripts/test-federation-acceptance.sh', timeout: 180000 }, async t => {
|
||||
const op = await fakeOidc();
|
||||
t.after(() => op.close());
|
||||
const hubIssuer = op.issuer('hub'), factoryIssuer = op.issuer('factory');
|
||||
assert.notEqual(hubIssuer.clientId, factoryIssuer.clientId);
|
||||
const ca = join(root, 'fake-op-ca.pem'); writeFileSync(ca, op.cert);
|
||||
assert.ok(![7330, 7331, 7332].includes(Number(new URL(op.base).port)));
|
||||
writeFileSync(join(root, 'ports'), `${new URL(op.base).port}\n`, { flag: 'a' });
|
||||
const hub = prepare('hub', hubIssuer, ca), factory = prepare('factory', factoryIssuer, ca);
|
||||
assert.notEqual(hub.dataRoot, factory.dataRoot);
|
||||
assert.notEqual(hub.env.MOSAIC_CONFIG, factory.env.MOSAIC_CONFIG);
|
||||
for (const i of [hub, factory]) {
|
||||
const ready = join(root, `${i.name}-host.ready`);
|
||||
const host = child(i, 'host', [process.execPath, '--experimental-test-module-mocks',
|
||||
fileURLToPath(new URL('./fixtures/federation-host.mjs', import.meta.url)), i.name, i.adapter, ready]);
|
||||
await until(() => existsSync(ready), `${i.name} fixture host ready; ${host.log}`);
|
||||
writeFileSync(join(root, 'sockets'), `${socketPath(i.dataRoot)}\n`, { flag: 'a' });
|
||||
}
|
||||
const shared = human(hub, 'project.create', { name: 'hub-shared', repo: 'stack' }).id;
|
||||
const privateProject = human(hub, 'project.create', { name: 'hub-private' }).id;
|
||||
human(factory, 'project.create', { name: 'factory-only' });
|
||||
access(hub, ['user', 'add', 'jason-test', '--home', 'factory', '--subject', 'test-op:jason-test-sub', '--revision', '1']);
|
||||
access(hub, ['grant', '--user', 'jason-test', '--projects', shared, '--instances', 'assistant', '--level', 'collaborator', '--revision', '2']);
|
||||
for (const i of [hub, factory]) {
|
||||
const { log } = child(i, 'console', [process.execPath, webCli, 'serve', '--port', '0', '--business', i.name, '--board', 'http://127.0.0.1:1', '--sso', '--send']);
|
||||
i.port = Number(await until(() => existsSync(log) && readFileSync(log, 'utf8').match(/Mosaic Console: http:\/\/127\.0\.0\.1:(\d+)\//)?.[1], `${i.name} Console started`));
|
||||
assert.ok(![7330, 7331, 7332].includes(i.port));
|
||||
writeFileSync(join(root, 'ports'), `${i.port}\n`, { flag: 'a' });
|
||||
}
|
||||
assert.notEqual(hub.port, factory.port);
|
||||
if (process.env.MOSAIC_R7_INTERRUPT === '1') {
|
||||
console.log('R7 interruption: both Consoles started; killing test driver');
|
||||
process.kill(process.pid, 'SIGKILL');
|
||||
await pause(60000);
|
||||
}
|
||||
const signed = await signIn(hub, op, hubIssuer, 'jason-test-sub');
|
||||
assert.equal(signed.status, 303, JSON.stringify(signed.body)); assert.ok(signed.cookie);
|
||||
const cookie = signed.cookie;
|
||||
const config = await http(hub, '/api/config', { cookie });
|
||||
assert.equal(config.status, 200); assert.equal(config.body.user, true);
|
||||
const projects = await http(hub, '/api/chat/projects', { cookie });
|
||||
assert.equal(projects.status, 200, JSON.stringify(projects.body));
|
||||
assert.deepEqual(projects.body.rows.map(p => p.id), [shared], 'R7 ungranted project must not appear');
|
||||
const created = await http(hub, '/api/chat/threads', { cookie, body: { project: shared, instance: 'assistant', name: 'Jason scratch thread' } });
|
||||
assert.equal(created.status, 200, JSON.stringify(created.body));
|
||||
const thread = created.body.id;
|
||||
const sent = await http(hub, `/api/chat/threads/${thread}/messages`, { cookie, body: { body: 'R7 scratch question' } });
|
||||
assert.equal(sent.status, 200, JSON.stringify(sent.body));
|
||||
const reply = await until(async () => {
|
||||
const r = await http(hub, `/api/chat/threads/${thread}/messages`, { cookie });
|
||||
assert.equal(r.status, 200, JSON.stringify(r.body));
|
||||
return r.body.rows.find(m => m.class === 'RESULT' && m.in_reply_to === sent.body.id);
|
||||
}, 'granted instance replies in Jason thread');
|
||||
assert.equal(reply.from_role, 'assistant'); assert.equal(reply.to_role, 'user');
|
||||
assert.match(reply.body, /R7 scratch question/);
|
||||
const refused = await http(hub, '/api/chat/threads', { cookie, body: { project: privateProject, instance: 'assistant', name: 'must refuse' } });
|
||||
assert.equal(refused.status, 403); assert.equal(refused.body.error, 'access-refused');
|
||||
assert.deepEqual(json(join(hub.configDir, 'access.json')).grants[0].projects, [shared], 'R7 grant must name exactly one project');
|
||||
for (const path of ['/api/board', '/api/bus/inbox', '/api/bus/tasks']) {
|
||||
const r = await http(hub, path, { cookie });
|
||||
assert.equal(r.status, 403, path); assert.equal(r.body.error, 'owner-only', path);
|
||||
}
|
||||
const factoryLogin = await signIn(factory, op, factoryIssuer, 'jason-test-sub');
|
||||
assert.equal(factoryLogin.status, 403); assert.equal(factoryLogin.cookie, undefined);
|
||||
const factoryOwner = await signIn(factory, op, factoryIssuer, 'owner-sub');
|
||||
assert.equal(factoryOwner.status, 303); assert.ok(factoryOwner.cookie);
|
||||
const factoryProjects = await http(factory, '/api/chat/projects', { cookie: factoryOwner.cookie });
|
||||
assert.equal(factoryProjects.status, 200);
|
||||
assert.deepEqual(factoryProjects.body.rows.map(p => p.name), ['factory-only']);
|
||||
const factoryThreads = await http(factory, '/api/chat/threads', { cookie: factoryOwner.cookie });
|
||||
assert.equal(factoryThreads.status, 200); assert.deepEqual(factoryThreads.body.rows, []);
|
||||
const hubCookieAtFactory = await http(factory, '/api/chat/projects', { cookie });
|
||||
assert.equal(hubCookieAtFactory.status, 401);
|
||||
const [launch] = logs(hub).filter(e => e.event === 'launch' && e.thread === thread);
|
||||
assert.ok(launch, 'R7 sandboxed run launched');
|
||||
const liveRun = readSessions(hub.dataRoot).find(s => s.run === launch.run);
|
||||
assert.ok(liveRun, 'R7 run remains live before revoke');
|
||||
access(hub, ['revoke', '--user', 'jason-test', '--revision', '3']);
|
||||
const after = await http(hub, '/api/chat/projects', { cookie });
|
||||
assert.equal(after.status, 401); assert.equal(after.body.error, 'sign-in required');
|
||||
assert.equal((await signIn(hub, op, hubIssuer, 'jason-test-sub')).status, 403);
|
||||
// Pin the claim before waiting for process exit: a missing broker revocation
|
||||
// must fail this assertion, even if it also prevents the runner from ending.
|
||||
assert.ok(claimHistory(hub, launch.run).some(r => r.op === 'revoke' && r.reason === 'access-revoked'),
|
||||
'R7 revoke must end the claim as access-revoked');
|
||||
const ended = await until(() => logs(hub).find(e => e.event === 'end' && e.run === launch.run), 'revoked run ended');
|
||||
assert.equal(ended.exitCode, 0, JSON.stringify(ended));
|
||||
assert.deepEqual(readSessions(hub.dataRoot), []);
|
||||
await until(() => startTimeOf(liveRun.pid) !== liveRun.startTime && startTimeOf(liveRun.runnerPid) !== liveRun.runnerStartTime, 'revoked engine and runner processes gone');
|
||||
assert.equal(claimHistory(hub, launch.run).at(-1).op, 'revoke', 'R7 shutdown must not replace missing revocation with release');
|
||||
assert.deepEqual(human(factory, 'thread.list'), []);
|
||||
});
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user