Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96659ccf43 | ||
|
|
2d274a5b6c | ||
|
|
6558c7bf53 | ||
|
|
11a9ca1cff | ||
|
|
fc7110f0a7 | ||
|
|
2655ec010f | ||
|
|
a447880cb4 | ||
|
|
7400509ba3 | ||
|
|
b9ef816ff7 |
No files matched your search
@@ -5476,3 +5476,12 @@ remain; no live state, schema or bus policy change.
|
||||
|
||||
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 | Beakley (88b97f65) | Row 217 #1718 takeover, before implementation | Clean base 45dca69f6 on beakley/row217. Recovering Launchpad handoff for citation guard, live settings, page store and runner retry. Acceptance is C2-06/design 5.2 plus sage-3595/3599/3602. Tracking agents/beakley/work/queue-217/BUILD.md; no product changes or Node/tests; Sage CLEAR required.
|
||||
|
||||
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 untested draft checkpoint | Continued Launchpad e25ef0373; added runner retry/text/note implementation, pure protocol constants and sage-3640 approved single read-only mount, CLI documentation and new acceptance test files. Static checks caught missing projects compatibility, async request mutation and Git pathspec matching; bounded fixes and regression tests authored. git diff --check clean. No Node/test/mutant executed; Sage holds CLEAR. 283/216/285 dependency checks and all acceptance execution remain open. Packet agents/beakley/work/queue-217/MANIFEST.md.
|
||||
|
||||
2026-10-23 | Beakley (88b97f65) | Row 217 early-look follow-up, Untested | Webby 0939c0682 E1 adds ordinary mallory human-swap test; E2 adds stored trailer-derived agent/file event evidence; E6 creative-sender matrix and E7 runtime readRaw tests authored. Sage-3668/3670 accepts direct writer substitution because setter keys exclude replyLimits (organization.set exists, correcting review premise), and requires distinct availability refusal for E4. Real held-lock/socket and runner-reason regressions plus M5 authored before that production fix; expected RED awaits Sage CLEAR. No Node/tests/mutants, no old-test edits, no green claim.
|
||||
|
||||
2026-10-24 | Beakley (88b97f65) | Row 217 round 2, sage-4037 | Rebased published dde5b8496 onto e0c6142f3 on new beakley/row217-r2; both append-log histories retained. Conflicts preserve landed grant dispatch, revocation hook and lookup exports. Added E4 citation-unavailable product path and real supporting-scope/index comparison tests after row283 8d2856ee3. Untested: no Node/npm/tests/mutants/build; ready for Sage CLEAR after Lena296. Full range-diff and honest discarded local rebase correction in agents/beakley/work/queue-217/BUILD.md.
|
||||
|
||||
2026-10-24T10:21Z | Beakley (88b97f65) | Row 217 round2 slot verified, sage-4042/4043 | Source 2d274a5b6: touched57/57, whole library142/142 business270/270 bus974/974 cli988/988 harness172/172 seat218/218, all zero fail/skip/cancel. M1-M5 killed, controls/restored controls green, product hashes restored after each; E4 RED stand-in observed. Only first-red was new runtime fixture invalid-authority, corrected and retained in receipts; no old test failed/edited. Local root/webui npm ci, short TMPDIR, timeout-k10; no canonical npm/rm or module link. Full evidence agents/beakley/work/queue-217/run-r2; Sage owns scripts landing gate and formal review.
|
||||
@@ -0,0 +1,314 @@
|
||||
# Row 217 — C2-06 citation guard, page store and runner retry
|
||||
|
||||
Owner: Beakley (88b97f65-97db-4f05-8c6e-081f92bc3501), assigned by Mos Q245.
|
||||
Issue: #1718. Reviewer: Webby. Current branch: `beakley/row217-r2` (original `beakley/row217` preserved).
|
||||
Brief: `docs/plans/2026-10-20_case2-rows.md`, C2-06.
|
||||
Status: six whole suites green and M1–M5 killed under sage-4042/4043; ready for formal review. Landing scripts and integration remain Sage's.
|
||||
|
||||
## Plan and boundaries
|
||||
|
||||
Continue Launchpad's implementation from its handed-off ref. Complete the guard,
|
||||
live reply limits and M11 writer fence, broker page store and check evidence,
|
||||
CLI setter and runner retry/note behavior against the brief and design 5.2.
|
||||
Supply acceptance tests and mutants, then request Sage's test slot and Webby's
|
||||
review. Sage owns queue movement, gate and landing. Push only `beakley/*`, add
|
||||
commits without amend/force or attribution lines. No credential reads.
|
||||
|
||||
## Standing rulings received from Sage (sage-3610)
|
||||
|
||||
- sage-3595: stub supporting scope until row 283 lands, then rebase onto it and
|
||||
add real-fixture tests. Landing follows 283. `runtime.mjs` wiring approved.
|
||||
- Check evidence belongs in an `action.allowed` event in the same transaction
|
||||
as message insertion; no new event kind. Read the library at the cited commit.
|
||||
- In library businesses only, JSON over 32,768 characters gets the
|
||||
`request-too-large` retry. `role.claim` adds `library` only for a library business.
|
||||
- Store `check.citations` entries with `scope`, `agent`, and `file` (the file's
|
||||
last commit at the cited commit). On `thread.messages`, add `last` at the served
|
||||
tip, one git call per distinct path per read. Scope/agent are broker evidence.
|
||||
- sage-3599: optional `BusError.reason`, guard refusals only, capped at 300
|
||||
characters. Add bus server/client to manifest. Reasonless envelopes stay
|
||||
byte-identical.
|
||||
- `render.mjs` obsolete notice hits belong to row 225.
|
||||
- Node and tests require Sage CLEAR (sage-3610 supersedes the initial permission
|
||||
for targeted new tests). Full suites, webui builds and gate require shared slot.
|
||||
- Existing test edits require Mos GO through Sage; list every file and numstat
|
||||
below before seeking that approval.
|
||||
|
||||
## Recovery and coordination
|
||||
|
||||
Initial base `45dca69f6`, clean. `scripts/mosaic queue next beakley` refused:
|
||||
linked worktrees cannot run the queue. Reported to Sage/Mos in beakley-0001;
|
||||
Sage will change ownership at a queue gap. No queue files changed.
|
||||
Read AGENTS.md, agents/README.md, C2-06 brief, design 5.2 and the row 225 BUILD
|
||||
contract. Requested Launchpad handoff in beakley-0002. Its header timestamp was
|
||||
mistakenly typed ahead of the actual clock; sequence key identifies the message.
|
||||
|
||||
## Existing test changes
|
||||
|
||||
None yet. No tests or Node commands run.
|
||||
|
||||
|
||||
## Handoff adopted and independent work (2026-10-23)
|
||||
|
||||
Fast-forwarded `beakley/row217` to Launchpad `e25ef0373` on `45dca69f6`.
|
||||
The full original handoff/rulings remain at
|
||||
`agents/launchpad/work/queue-217/BUILD.md`. No handoff code was rebuilt from scratch.
|
||||
|
||||
Implemented the runner path: role.claim library switch; exact fixed texts; note
|
||||
inside JSON text with citations retained and no JSON clip; fixed texts defer
|
||||
notes; one same-session retry for guard codes and both existing size caps;
|
||||
retry fallback chosen from the second refusal; separate usage turn id for retry.
|
||||
Outside library businesses the existing send path remains intact.
|
||||
|
||||
Added the CLI README usage paragraph. New acceptance tests cover library parsing,
|
||||
real Git reachability/quotes/scope/origin, settings fallback, M11 direct writer
|
||||
and serialization, CLI dispatch, broker insert/check atomicity, live settings,
|
||||
page bounds/lifecycle, socket reasons, per-path last reads, request snapshots,
|
||||
and runner RESULT/typed send_message, fixed texts, long JSON and unslop behavior.
|
||||
These are authored evidence only: none has executed.
|
||||
|
||||
Static defects corrected locally: a missing projects object now means no library;
|
||||
the guard sends its captured request arguments after the asynchronous check;
|
||||
origin/last Git logs use literal pathspecs; a web quote can match any retained
|
||||
fetch of the cited URL. Each has new-file regression coverage.
|
||||
|
||||
## Scope additions and cross-row contract
|
||||
|
||||
- `packages/library/src/reply-protocol.mjs`: shared GUARD_CODES/FIXED_TEXTS,
|
||||
import-free, so runner imports do not reach host Git modules. guard.mjs
|
||||
reexports them to preserve Launchpad's API. Fethry confirmed no renderer import
|
||||
is needed when the renderer simply displays fixed texts as ordinary text.
|
||||
- `packages/seat/src/sandbox.mjs`: one RUNNER_FILES entry for that pure file,
|
||||
approved by sage-3640. No other mount change. The existing sandbox.test.mjs
|
||||
import-closure test must pass unchanged; seat joins the gate list.
|
||||
- Runtime hunks remain exactly Launchpad's import and library guard/limits/last
|
||||
adapters, plus library.limits.set dispatch. Drift checks wait on 216 and 285.
|
||||
- Status contract confirmed with Fethry (beakley-0004, fethry-0009): nested
|
||||
`replyLimits: {requireCitation, citationsPerReply, fallback: [keys],
|
||||
problem: "reply-limits-unreadable" | null}`. Check entries remain ordered
|
||||
`scope/agent/file`, plus `last` on reads; no changed boolean.
|
||||
|
||||
## Verification and remaining acceptance
|
||||
|
||||
- Static `git diff --check`: clean so far. No Node, suite, test or mutant run.
|
||||
- Existing test file edits: none (numstat 0/0 for every pre-existing test).
|
||||
All new test files are listed in manifest.r1.sha256; no Mos approval requested.
|
||||
- `mutants.py` is authored for isolated HEAD archives with green controls:
|
||||
M1 remove both writer fences; M2 compare incoming human; M3 split evidence
|
||||
from insert; M4 drop client reason. Not executed.
|
||||
- Still required: Sage CLEAR for targeted runs; fix first-run failures; supporting
|
||||
scope acceptance against row 283; 216 index/readLibrary fixture comparison;
|
||||
285/runtime drift check; full library/business/bus/cli/harness/seat suites and
|
||||
every scripts/test-*.sh on Sage's gate; mutant execution; Webby approval.
|
||||
- Before in-review, perform brief's obsolete-code/key search and report outside
|
||||
ownership hits to Sage. render.mjs/render.test.mjs hits belong to 225.
|
||||
- No queue changes, protected branch pushes, gate claims or acceptance claims.
|
||||
|
||||
Static obsolete-code/key search completed: only packages/library/src/render.mjs:6 and packages/library/tests/render.test.mjs:58 match no-cited-answer; both belong to row 225 per standing ruling. No owned-path hit. Repeat before in-review after dependency changes.
|
||||
|
||||
|
||||
## Webby early look 0939c0682 / webby-0249 and sage-3668/3670
|
||||
|
||||
This remains an untested draft. The follow-up adds tests only; production E4
|
||||
changes wait for the held-lock regression's first RED under Sage CLEAR.
|
||||
|
||||
- E1: added an ordinary actor `mallory` that sets both `human` to itself and
|
||||
`replyLimits`. The existing role/tester actors did not distinguish M2 because
|
||||
the actor-shape exclusions rejected them independently of the human comparison.
|
||||
M2 is still unrun; no kill claimed.
|
||||
- E2: added a real bus event assertion for `agent: "acme/scribe"` from a file's
|
||||
Mosaic-Agent trailer. The cited tip includes a later unrelated commit, so the
|
||||
event must store the file commit rather than the library tip. thread.messages
|
||||
must show `last === file` for that unchanged path. Supporting scope still waits
|
||||
for row 283's real fixture, as ruled.
|
||||
- E3: sage-3668 accepts direct writer.write tests under chief and tester actors
|
||||
as substitutes for the brief's setter cases (brief lines 483-491 at base
|
||||
45dca69f6). Correction to the early-look premise: organization.set DOES exist,
|
||||
broker.mjs:4701/4709, but ORGANIZATION_KEYS excludes replyLimits; instances.set
|
||||
also cannot carry it. Direct writes exercise the shared M11 boundary those
|
||||
setters cannot reach with that input. Sage confirmed the accurate reason in
|
||||
sage-3670; the unrelated actor case remains required.
|
||||
- E4: traced readSyncState -> withLibraryLock -> library-sync-busy after five
|
||||
seconds -> servedMirror catch-all -> null served -> misleading citation-invalid.
|
||||
New real-flock/socket test first proves the cite valid, holds the lock, asserts
|
||||
the exact citation-unavailable reason the agent receives, verifies no insert,
|
||||
and checks fixed fallback delivery and recovery after unlock. Additional tests
|
||||
distinguish malformed sync state from absent/new mirrors, and assert that the
|
||||
actual runner retry request contains that reason. These tests are expected RED
|
||||
until the fix. Planned production change: only an absent mirror or legitimate
|
||||
empty state yields null; other failures become GuardError citation-unavailable;
|
||||
bodies without path cites do not require the mirror lock. Add the code to
|
||||
GUARD_CODES so the runner sends the agent that reason on its one retry. Per
|
||||
sage-3670, existing four fallback bodies can remain only if that delivery is
|
||||
verified. M5 restores catch-all behavior; authored, not run.
|
||||
- E5: first-library-project selection is a Launchpad handoff implementation
|
||||
choice, not a validated rule that a business can only have one library.
|
||||
Cites into another library remain a limitation. No mapping/schema rule added.
|
||||
- E6: the broker's full guard table now runs as both coder and research.
|
||||
- E7: new citation-runtime.test.mjs starts the real runtime, reads limits from
|
||||
the real organization file, then changes, corrupts and removes that fixture
|
||||
file and checks library.status without replacing the library adapter.
|
||||
|
||||
No Node, tests or mutants ran. No test that predated row 217 changed.
|
||||
|
||||
|
||||
## Webby follow-up 192049c98 / webby-0258
|
||||
|
||||
E1, E2, E6 and E7 closed statically; no executed acceptance claimed. E3 correction and E5 limitation accepted. E4 tests accepted statically; real RED then production fix/green remain on hold for Sage CLEAR, with supporting scope waiting on 283. F1: citation-helpers status now uses its mutable served value, matching guard/last after setServed instead of reporting the original fixture commit. Test-only fixture correction, unrun. manifest.r3.sha256 records the updated helper; no production or old-test changes.
|
||||
|
||||
|
||||
## Round 2 / sage-4037 — 2026-10-24, Untested
|
||||
|
||||
New branch `beakley/row217-r2` from published `dde5b8496`, rebased exactly onto
|
||||
`origin/refactor` at `e0c6142f3` (109 upstream commits since `45dca69f6`). The
|
||||
published original branch is unchanged; no amend or force-push. The replayed
|
||||
four commits are b9ef816ff, 7400509ba, a447880cb, 2655ec010. Full range-diff:
|
||||
`rebase-r2.range-diff.txt`. Last commit is `=`, the first three are `!` from
|
||||
conflict context/indentation and append-log placement described below.
|
||||
|
||||
Conflict resolutions:
|
||||
- broker.mjs: retained landed grants.scope/grants.scope.set dispatch and added
|
||||
library.limits.set to the organization verb dispatch; neither path removed.
|
||||
- runtime.mjs: retained grants.scope.set and added library.limits.set alongside
|
||||
it; retained query/sync wiring and all new base lifecycle behavior.
|
||||
- organization.mjs: retained row 239's beforeWrite/revoked wrapper, placing both
|
||||
M11 fences before validations/revocation as on the old candidate. A refused
|
||||
reply-limit write cannot invoke beforeWrite.
|
||||
- library/index.mjs: retained C2-05a buildIndex/query exports and added the guard
|
||||
and reply-limits exports.
|
||||
- BUILD-LOG.md and docs/SESSIONS.md: retained upstream appends followed by all
|
||||
Beakley appends. Static byte-prefix checks prove the entire e0c6142f3 log
|
||||
histories survive; all original Beakley entries also survive.
|
||||
- business.mjs, cli README and sandbox.mjs auto-merged. Static diff against the
|
||||
target confirms only this row's TOP_KEYS addition, README paragraph and one
|
||||
import-free protocol mount; landed supporting scope and room-address mount
|
||||
remain intact. runtime diff contains only the approved import, injected
|
||||
guard/limits/last adapters and setter dispatch, now beside grants.scope.set.
|
||||
|
||||
Correction recorded honestly: the first unpublished rebase attempt used an
|
||||
unbounded marker regex that removed code after a conflict. The diff stat caught
|
||||
it immediately; aborted the rebase and repeated with bounded markers. No result
|
||||
from that discarded attempt was pushed. The successful first replay matches the
|
||||
original 589 insertions/20 deletions; no broad deletions remain.
|
||||
|
||||
Implemented E4 under sage-4037's explicit direction to make the product fix
|
||||
while keeping all tests unrun. This supersedes the earlier planned RED-first
|
||||
execution order; no RED or green has been observed. The existing regression
|
||||
cases predate the fix. GUARD_CODES now includes citation-unavailable. Only a
|
||||
mirror confirmed absent by lstat, or readSyncState's legitimate empty state,
|
||||
yields no served commit. Other read/lock/metadata errors give the fixed safe
|
||||
reason: "The library is busy or unavailable. Retry the answer when the library
|
||||
is available." The runner receives it through BusError.reason and includes it
|
||||
in its one retry request. The existing four fallback bodies remain. Plain,
|
||||
web-only and fixed replies require no mirror read, so a busy mirror cannot
|
||||
block the runner's fixed fallback. M5's real production anchor now exists.
|
||||
|
||||
Supporting scope after row 283 (`8d2856ee3`): added two broker tests using real
|
||||
mapping/reader classification, not a scope stub. A path in both supporting and
|
||||
playground refuses as canon; an agent-committed supporting path stores
|
||||
scope=supporting, agent=acme/scribe and its file commit in message.checked.
|
||||
|
||||
C2-05a comparison: index-build.mjs builds its file map directly from readLibrary
|
||||
files; indexFor calls that same reader at the requested commit. Added a real
|
||||
fixture test comparing the index set, readLibrary set and libraryReader.lines
|
||||
admission, including supporting, unmapped Markdown and .canvas. This comparison
|
||||
is authored and statically traced, not executed; numerical results wait on CLEAR.
|
||||
|
||||
Current packet: manifest.r4.sha256, relative to e0c6142f3. No test file already
|
||||
present at that base was edited. No Node, npm, tests, mutants or webui build run.
|
||||
Ready for CLEAR after Lena's row 296. On CLEAR, install with npm ci
|
||||
--ignore-scripts --no-audit --no-fund in THIS worktree root and packages/webui;
|
||||
never link to canonical node_modules or run npm/rm in the canonical checkout.
|
||||
Use short mktemp TMPDIR and timeout -k 10. Any old-test red goes to Sage before
|
||||
an edit. Full green gate, mutants and Webby verdict remain outstanding.
|
||||
|
||||
Range-diff receipt rendering trims trailing whitespace on diff separator lines; no substantive range-diff content changed. This removes whitespace warnings caused by embedding raw diff output in a tracked text file.
|
||||
|
||||
Final base-to-candidate whitespace check found a trailing blank line inherited from Launchpad in limits.mjs; removed it without behavior changes. manifest.r5.sha256 supersedes r4 for the exact candidate file hashes.
|
||||
|
||||
|
||||
## CLEAR sage-4042/4043 — execution started 2026-10-24
|
||||
|
||||
Queue beakley-217-start initially refused Lena's review-outcome lock twice;
|
||||
same-op retry succeeded at rev3894 (briefed→in-progress). No queue bypass.
|
||||
Short TMPDIR /mnt/storage/scratch/tmp/tNoueZ. Worktree-local npm ci at root
|
||||
(128 packages) and packages/webui (2) completed; neither path was a symlink.
|
||||
No npm/rm command ran in canonical. All test commands use timeout -k 10.
|
||||
|
||||
E4 current file: 3 pass/0 fail/0 skip. M5 isolated pre-fix stand-in: 1 pass,
|
||||
2 expected assertion failures (citation-invalid instead of citation-unavailable),
|
||||
exit1. Restored product bytes and checkout product hashes, then E4 restored
|
||||
control 3/3. Receipts under run-r2/m5-red. M1 six-space anchors both match
|
||||
exactly once on the eight-space lines.
|
||||
|
||||
Touched test files first run: 56 pass/1 fail, zero skip/cancel. The sole first-red
|
||||
was NEW citation-runtime.test.mjs invalid-authority at Broker.checkBusiness:
|
||||
validator output has unresolved roles and is not the host's broker projection.
|
||||
Adjusted that new fixture only, following limits-runtime.test.mjs: minimal
|
||||
empty-authority coder, validated real project mappings, real organization adapter
|
||||
and systemFor(root). Single rerun 1/1, then all touched files 57/57. No base test
|
||||
failure or edit. First log retained as run-r2/touched-first.log and reported to
|
||||
Sage in beakley-0014. No production change was required.
|
||||
|
||||
The isolated mutant driver now wraps each node run in timeout -k 10, refuses a
|
||||
symlinked root node_modules, restores mutated files in finally, verifies original
|
||||
checkout product hashes after every mutant and reruns the restored control.
|
||||
Optional name filters allow the E4 M5 RED before the full mutant pass. No
|
||||
canonical dependency link is used; isolated copies link only this worktree's
|
||||
own dependency install.
|
||||
|
||||
|
||||
## Final slot results / 2026-10-24 10:21Z
|
||||
|
||||
All requested runs completed serially under sage-4042/4043 on source revision
|
||||
`2d274a5b6`. Final receipt commits change only tracking and logs, not packages.
|
||||
|
||||
| Whole suite | Pass | Fail / skip / cancel |
|
||||
| --- | ---: | --- |
|
||||
| library | 142 | 0 / 0 / 0 |
|
||||
| business | 270 | 0 / 0 / 0 |
|
||||
| bus | 974 | 0 / 0 / 0 |
|
||||
| cli | 988 | 0 / 0 / 0 |
|
||||
| harness | 172 | 0 / 0 / 0 |
|
||||
| seat | 218 | 0 / 0 / 0 |
|
||||
|
||||
Total 2,764 whole-suite tests. Touched files rerun 57/57. The unchanged
|
||||
RUNNER_FILES import-closure test passed in seat (seat-suite.log:153). The only
|
||||
first-run failure was the NEW runtime fixture already recorded above; no old
|
||||
test failed and no test present at e0c6142f3 was edited.
|
||||
|
||||
M1–M5 all killed by the intended assertion, each with Node exit1, a green
|
||||
control and green restored control. Driver exit0. M1 catches missing writer
|
||||
refusals; M2 is killed by mallory's human swap; M3 detects one message left
|
||||
behind after evidence failure; M4 detects the missing reason; M5 detects
|
||||
citation-invalid in place of citation-unavailable on contention and bad state.
|
||||
Every mutation was in a disposable HEAD archive. Each changed file was restored,
|
||||
checkout product hashes checked after every mutant, and the restored control
|
||||
rerun. All five final restored.sha256 certificates match current product files.
|
||||
M5 also ran separately before the whole suites as the authorized pre-fix RED
|
||||
stand-in; its held-lock and malformed-state assertions failed, then restored
|
||||
E4 passed 3/3. Full raw controls/negatives/restores are retained.
|
||||
|
||||
Receipts: run-r2/summary.json and summary.txt; *-suite.log; touched-first.log,
|
||||
touched-rerun.log; availability-green.log; runtime-fixture-retry.log; npm-root.log
|
||||
and npm-webui.log; m1-anchors.txt; m5-red/ and mutants/. All are text, no binary
|
||||
artifact added. All Node/mutant/npm runs used the short TMPDIR and timeout -k 10.
|
||||
Both node_modules trees are local npm ci installs. No canonical npm/rm or
|
||||
canonical dependency link. No further runtime/source changes after the tests.
|
||||
|
||||
Acceptance formerly waiting on dependencies is executed: real row283 supporting
|
||||
canon refusal and supporting/agent event pass; the C2-05a readLibrary/index set
|
||||
comparison passes; runtime readRaw live/corrupt/missing-file test passes; runner
|
||||
retry request carries the E4 availability reason. The repeat obsolete-name
|
||||
search finds only render.mjs:6 and render.test.mjs:58, owned by row225; no owned
|
||||
hit. First-library selection remains the accepted E5 limitation. Webby-0274 N1
|
||||
is a nonblocking wording limitation: configuration faults share the safe
|
||||
busy-or-unavailable retry text. No policy/schema restriction was invented.
|
||||
|
||||
Current complete changed-file manifest over e0c6142f3..candidate is
|
||||
candidate-manifest.sha256, excluding itself to avoid a circular hash. It includes
|
||||
source, tests, records and evidence. Earlier numbered manifests are historical.
|
||||
Formal Webby verdict and all scripts/test-*.sh at landing remain Sage's steps;
|
||||
this is verified for review, not an integration/deployment claim.
|
||||
|
||||
Exact negative-test logs retain Node's whitespace-only diagnostic lines. Whole diff --check reports those 12 blank-line padding warnings in run-r2 logs; source/docs diff --check excluding captured run-r2 evidence is clean. No source whitespace problem or test failure is hidden by that exclusion.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Row 217 round 2 — verified for review
|
||||
|
||||
Issue #1718, C2-06. Base e0c6142f3; branch beakley/row217-r2. Continued
|
||||
Launchpad e25ef0373 through the preserved original dde5b8496 branch. Range-diff,
|
||||
conflict resolutions and acceptance receipts are in BUILD.md. Tested source
|
||||
revision: 2d274a5b6; the final commits add records/evidence only.
|
||||
|
||||
The broker checks agent-to-human citations before insertion, stores trusted
|
||||
scope/agent/file evidence atomically with the message, and supplies per-path last
|
||||
on thread reads. It keeps session-local fetched pages and reads reply settings
|
||||
live through the M11 human-only writer. The runner preserves cited JSON, defers
|
||||
notes on fixed replies, and retries once with the refusal code and safe reason.
|
||||
E4 adds citation-unavailable for mirror/state/lock faults; uncited/web-only/fixed
|
||||
bodies do not need the mirror lock. Supporting scope uses landed row283 code.
|
||||
|
||||
## Product and test scope
|
||||
|
||||
The package paths listed in candidate-manifest.sha256 are exactly this row's
|
||||
base-to-candidate changes. Fourteen product paths cover library guard/limits/
|
||||
protocol/exports; business validation/writer; bus broker/runtime/server/client;
|
||||
CLI command/README; runner; and the single approved pure-protocol sandbox mount.
|
||||
Nine test/helper/fixture paths are additions. No existing test is modified.
|
||||
The mounted protocol stays import-free. Runtime retains the landed grant/query/
|
||||
sync behavior; the writer retains beforeWrite/revoked handling and fences before
|
||||
revocation. No SQL schema, event kind, role authority or credential change.
|
||||
|
||||
## Executed evidence
|
||||
|
||||
| Whole suite | Pass | Fail / skip / cancel |
|
||||
| --- | ---: | --- |
|
||||
| library | 142 | 0 / 0 / 0 |
|
||||
| business | 270 | 0 / 0 / 0 |
|
||||
| bus | 974 | 0 / 0 / 0 |
|
||||
| cli | 988 | 0 / 0 / 0 |
|
||||
| harness | 172 | 0 / 0 / 0 |
|
||||
| seat | 218 | 0 / 0 / 0 |
|
||||
|
||||
Touched files 57/57; total whole suites 2,764. First run was 56/57 due solely
|
||||
to a new runtime fixture missing the broker's resolved authority shape. Only
|
||||
that new fixture changed; single and full touched reruns passed. No old test
|
||||
failed. First-red evidence remains in run-r2/touched-first.log.
|
||||
|
||||
M1–M5 killed: missing M11 fences, incoming-human trust, split check transaction,
|
||||
dropped client reason, and hidden library availability. Every negative run has
|
||||
green control/restored runs and matching restored product certificates. M5 is
|
||||
also the authorized E4 pre-fix RED stand-in. Supporting canon refusal, stored
|
||||
supporting/agent evidence, the C2-05a index comparison, runtime raw settings and
|
||||
agent-visible E4 retry reason all pass. The unchanged sandbox closure test passes.
|
||||
|
||||
Commands ran serially under Sage CLEAR with short TMPDIR and timeout -k 10.
|
||||
Root/webui dependencies came from local npm ci, never canonical links. Full logs,
|
||||
summary.json and mutant certificates are in run-r2/. The complete changed-file
|
||||
manifest candidate-manifest.sha256 covers e0c6142f3..candidate, excluding itself;
|
||||
its SHA256 is supplied with the pushed tip. Historical manifests stay untouched.
|
||||
|
||||
## Remaining boundaries
|
||||
|
||||
Sage runs every scripts/test-*.sh at landing and posts the formal Webby review
|
||||
request. No merge/deployment performed. First-library selection remains the
|
||||
accepted limitation; configuration faults share the safe unavailable reason
|
||||
(Webby N1, nonblocking). Obsolete-name search hits only row225's render files.
|
||||
@@ -0,0 +1,80 @@
|
||||
74398edcedf16a3d856ab9e7ea900eb61a4df7f726f04b74b2ff123e7536e9c1 BUILD-LOG.md
|
||||
87880c30c2be0180e6eec4dbe203f86e93ae2b1b4be44031b6a4903c6245a0ec agents/beakley/work/queue-217/BUILD.md
|
||||
e7ecf624ccb2b918efb08001ae9da36380fe918f8c27db704860809b6792e6f2 agents/beakley/work/queue-217/MANIFEST.md
|
||||
f4a042b5d6d393d2be62b107d7157ae51dd8fbf36f4360f5cd8cf07c03c74251 agents/beakley/work/queue-217/manifest.r1.sha256
|
||||
53f051a4e416a34181ad9bcc7687f0d686274542b06d9758e2db7eb81e848811 agents/beakley/work/queue-217/manifest.r2.sha256
|
||||
f96fccc566d01d70db32188574d0b5331ef3c90bce66558449a51b3f21da1b29 agents/beakley/work/queue-217/manifest.r3.sha256
|
||||
faf8ec3e0ab458fe95120345ec93e077b0dcad9458c0f2683a20effb6ce3ed93 agents/beakley/work/queue-217/manifest.r4.sha256
|
||||
a58f0e273b60508b95c833cbace26799d7aa935117fd1f9a27812fba65b93d54 agents/beakley/work/queue-217/manifest.r5.sha256
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/manifest.r6.sha256
|
||||
04a9d0585b5ae4818d87d67ac27f838355f1a6f586625a16cf01d91d7bb6d4a0 agents/beakley/work/queue-217/mutants.py
|
||||
4a5f7b3373fd3ff46e2d6a5f7ee7106ad76a71c9034ca5a44b9e7340f0cc4cff agents/beakley/work/queue-217/rebase-r2.range-diff.txt
|
||||
630070b0d682120ba64b382ab3b47b3fa3e8f0bcfc695ca09432d01d705fbcb3 agents/beakley/work/queue-217/run-r2/README.md
|
||||
c4245272d5fc89c5fd3a9ea979e7d65727ea76e24364190af57556c59ad2fe34 agents/beakley/work/queue-217/run-r2/availability-green.log
|
||||
700fff38b0a99ffdb44863f13015673cb459e5e645c5df46580eafbc407c95c8 agents/beakley/work/queue-217/run-r2/bus-suite.log
|
||||
ef216d0af325c2b6647eb8405e5de70ebb5a0e7a28bb3d8fe3abc6a289a8d3d2 agents/beakley/work/queue-217/run-r2/business-suite.log
|
||||
af173117f83359ebba62c5c00eadc749bb179551a8e290bcf55bbe39b1e7272c agents/beakley/work/queue-217/run-r2/cli-suite.log
|
||||
a615f69ba60859e9d7817f3353b0d7c71e6c12eecc3ecf0f630d473be21e5fef agents/beakley/work/queue-217/run-r2/harness-suite.log
|
||||
acaa08001101fd69a12cae377984e0d469c2cedc2affb5ccf9af426a72549dcc agents/beakley/work/queue-217/run-r2/library-suite.log
|
||||
5414ce5b94e7ac7918ed64f62e5fd2b921d49c7e93cf73042b9187d014069934 agents/beakley/work/queue-217/run-r2/m1-anchors.txt
|
||||
7d7138b25568adb98932bbe180a337fe6798c3eddcd7134ce7278c24fea48818 agents/beakley/work/queue-217/run-r2/m5-driver.log
|
||||
de7dd716d3981fd9fa1d114882e60f9a83736cdb8f8e2096c3913c9f8620238a agents/beakley/work/queue-217/run-r2/m5-red/M5-hide-library-unavailable-control.log
|
||||
6b98d3943d04d99f3ef212f11901d2257422410c477abec27ed3100a0d20dad9 agents/beakley/work/queue-217/run-r2/m5-red/M5-hide-library-unavailable-restored-green.log
|
||||
a58f0e273b60508b95c833cbace26799d7aa935117fd1f9a27812fba65b93d54 agents/beakley/work/queue-217/run-r2/m5-red/M5-hide-library-unavailable-restored.sha256
|
||||
d852a464fd624ce12d8d6bdc9800402639bb500a6d40c628cb836ca1bcf1e326 agents/beakley/work/queue-217/run-r2/m5-red/M5-hide-library-unavailable.log
|
||||
7d7138b25568adb98932bbe180a337fe6798c3eddcd7134ce7278c24fea48818 agents/beakley/work/queue-217/run-r2/m5-red/summary.txt
|
||||
1c405f1f15d2ce2db37b5809cdf5eaff68f18c99c5621e1c43262a289c5e35c4 agents/beakley/work/queue-217/run-r2/mutants-driver.log
|
||||
c96487e87c1ae9e34a7222b04701c5efcce25fe201f03a0213b989f7cde2c376 agents/beakley/work/queue-217/run-r2/mutants/M1-writer-fence-control.log
|
||||
8b294ecc057ad64ace7f520a1785b54df7df5a9eddf962813c3e5f8fa723ffd4 agents/beakley/work/queue-217/run-r2/mutants/M1-writer-fence-restored-green.log
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/run-r2/mutants/M1-writer-fence-restored.sha256
|
||||
be749486956e85a71f08107139613e09b62c53b58e8cc10e0fbe807e95d079a6 agents/beakley/work/queue-217/run-r2/mutants/M1-writer-fence.log
|
||||
525da1cc780dfea1a66f95739d444516bd16271c7ec539d615012ec9c8e7c139 agents/beakley/work/queue-217/run-r2/mutants/M2-incoming-human-control.log
|
||||
3546705980bb4a73295f16e42ab55f38c6adca9a5c53252264ae4ef66c595dfc agents/beakley/work/queue-217/run-r2/mutants/M2-incoming-human-restored-green.log
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/run-r2/mutants/M2-incoming-human-restored.sha256
|
||||
77352c22c459c516ff47ff1caa42fe9763fe201a04079646b0ccc0da6a145804 agents/beakley/work/queue-217/run-r2/mutants/M2-incoming-human.log
|
||||
58de145ec0384de4f560b28dcfa76e98c384be5c771d6561fb1054f07ea8166b agents/beakley/work/queue-217/run-r2/mutants/M3-split-check-transaction-control.log
|
||||
6a4cd0ef36b0d2318ef71c941ce300d215b6d2f87d0b4328c461ba53d812434c agents/beakley/work/queue-217/run-r2/mutants/M3-split-check-transaction-restored-green.log
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/run-r2/mutants/M3-split-check-transaction-restored.sha256
|
||||
2deb72f2d442f413fd06e205093f6fa3f955ec6ef707b7ca65b86be8c8883f9b agents/beakley/work/queue-217/run-r2/mutants/M3-split-check-transaction.log
|
||||
31cac91f43c8b7ebe0431f27a038b0fcd554a12b232592747f3972213cfa192b agents/beakley/work/queue-217/run-r2/mutants/M4-drop-client-reason-control.log
|
||||
e06c1da8dcd9e7eb013a4597b14369e296e21b812476bd6d88c7a7aa040d4065 agents/beakley/work/queue-217/run-r2/mutants/M4-drop-client-reason-restored-green.log
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/run-r2/mutants/M4-drop-client-reason-restored.sha256
|
||||
66d8f72df3bc0428624dab437cb04923ecab42055d29c6ffde43af98d47f32ae agents/beakley/work/queue-217/run-r2/mutants/M4-drop-client-reason.log
|
||||
3f74fbd3b4297868b9bb637a7924739363470964873a4bd3dc2f5daf2d0e9c8a agents/beakley/work/queue-217/run-r2/mutants/M5-hide-library-unavailable-control.log
|
||||
96d95a45d5326e614328531a7dadebce5d3ce70e40ec23dd49a66c1a676b45be agents/beakley/work/queue-217/run-r2/mutants/M5-hide-library-unavailable-restored-green.log
|
||||
120b15db5eb75897095fe3d0759553e482365d58061d763beba7754a6e0d0734 agents/beakley/work/queue-217/run-r2/mutants/M5-hide-library-unavailable-restored.sha256
|
||||
fa57189e9de99c4a76be27da3b857c07fc76706f7d0ee7409ba4efd43c3642b7 agents/beakley/work/queue-217/run-r2/mutants/M5-hide-library-unavailable.log
|
||||
1c405f1f15d2ce2db37b5809cdf5eaff68f18c99c5621e1c43262a289c5e35c4 agents/beakley/work/queue-217/run-r2/mutants/summary.txt
|
||||
862418d3d4124871ca5fcaf35e16c318a04e2888da83836751ddfea110efeb06 agents/beakley/work/queue-217/run-r2/npm-root.log
|
||||
38b75d8bddacf8d227883a29dd8a10f87a7f02d8a95f3f8809cb966fc9c8cf7a agents/beakley/work/queue-217/run-r2/npm-webui.log
|
||||
7f29b5a328fd6be61ebc4110a048305b50856810ab97a7798cd427924d74e8c9 agents/beakley/work/queue-217/run-r2/runtime-fixture-retry.log
|
||||
3bd51fe222a61412e8285eb522a2f7b5c1b0b035f7dca0e9ff66190bd53e6706 agents/beakley/work/queue-217/run-r2/seat-suite.log
|
||||
8b98c6ba76de46ddd858b4b95bae7bce2d1bf969edb85901ad8819e1280869a1 agents/beakley/work/queue-217/run-r2/summary.json
|
||||
e374cb7ab28a46dd560b267b0fe6691b11c0c2fabd00087c4e49a02ab1bf8086 agents/beakley/work/queue-217/run-r2/summary.txt
|
||||
ad2022c7ccdfa151e029eed92cd309369a97f197d9304775f6d2be3dc5f11080 agents/beakley/work/queue-217/run-r2/touched-first.log
|
||||
e1fe5ebae1a36f196470848bf27b99c21d316c9236a1d1328d7bcaca0ee4d3e9 agents/beakley/work/queue-217/run-r2/touched-rerun.log
|
||||
0c1b1f079b4b993e8d1aeb730d743e28696413eae8be6dd79731d749d1d45d0b agents/launchpad/work/queue-217/BUILD.md
|
||||
76b0a5785f134c09cda677e8f5fb3e7454b39883c4a6f55360d9d277476fcd2c docs/SESSIONS.md
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,21 @@
|
||||
68c05644d29188b897e2d4695d8e7025e143d424d69a91bc07bc950f589d34bf packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
49e01e5e73cc3e830ad48a1b6b13fe4d06a38950f17847dc049b75209d6d3f0a packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
29114051a9bc5271e48978a9552d64b51b0ac4810f94a1f98940bf617b148df8 packages/bus/tests/citation-guard.test.mjs
|
||||
dc2d5836f10f591fc174aaf9587a3bff793b24ac29efe9ab68c6a9243d1d1c2c packages/bus/tests/citation-helpers.mjs
|
||||
825da818ba3cf45a4837f0f15a9c2cdba55a3d0bbb4479d55236f35799427c6f packages/business/src/business.mjs
|
||||
dbd79a4dfc926620d034b540f98f39336d81e4c79f214424c3cf19250c1d1f8d packages/business/src/organization.mjs
|
||||
bec04a894bc2af000548283ed7719e4de1c6d56688f42238a113390e9b2d3fbe packages/business/tests/reply-limits.test.mjs
|
||||
1f1bea250f694a2aae9b9dc856b3c3424ceca153ccf826c99ccb98e6ea9cd394 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
ec7ef0cbf493217eb9229895cae2fe7ef603f41913840e59a73220df387c81ca packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
0365114cf5773cbaa9fba196fbd1e773b2ec5ad2300c40231c357ca58884b9e3 packages/library/src/guard.mjs
|
||||
aaa8f7ba60a729a88a6ef57b3d5ff5a69bcf0e17b60e7db51a8dc5a697627b6c packages/library/src/index.mjs
|
||||
3e04fa25a64334d33ffbec704205b32cd01992f2d341e908f933a345199beee0 packages/library/src/limits.mjs
|
||||
c9e682eff87e822647921288d7fce0ebdc7b09f0a6dfdd6528f10249ae26e81b packages/library/src/reply-protocol.mjs
|
||||
dd056409712113eb9c1b70e29b21022f5b30df6ffbfb2658e293edd6ec7195c7 packages/library/tests/guard.test.mjs
|
||||
2f3ec99129d16394d4458e252b9cba4e38ff66cd9393e3889f8afc3140e15a8e packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,23 @@
|
||||
68c05644d29188b897e2d4695d8e7025e143d424d69a91bc07bc950f589d34bf packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
49e01e5e73cc3e830ad48a1b6b13fe4d06a38950f17847dc049b75209d6d3f0a packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
8dc344b8a905c58d544b031ca84adb7eb869d9de88eb8e6b0fc1d9190702a38a packages/bus/tests/citation-guard.test.mjs
|
||||
62d8a55945d4b700e22c8c49b6420a5e25719d266901a2ad20c72514735e65e9 packages/bus/tests/citation-helpers.mjs
|
||||
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||
825da818ba3cf45a4837f0f15a9c2cdba55a3d0bbb4479d55236f35799427c6f packages/business/src/business.mjs
|
||||
dbd79a4dfc926620d034b540f98f39336d81e4c79f214424c3cf19250c1d1f8d packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
1f1bea250f694a2aae9b9dc856b3c3424ceca153ccf826c99ccb98e6ea9cd394 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
0365114cf5773cbaa9fba196fbd1e773b2ec5ad2300c40231c357ca58884b9e3 packages/library/src/guard.mjs
|
||||
aaa8f7ba60a729a88a6ef57b3d5ff5a69bcf0e17b60e7db51a8dc5a697627b6c packages/library/src/index.mjs
|
||||
3e04fa25a64334d33ffbec704205b32cd01992f2d341e908f933a345199beee0 packages/library/src/limits.mjs
|
||||
c9e682eff87e822647921288d7fce0ebdc7b09f0a6dfdd6528f10249ae26e81b packages/library/src/reply-protocol.mjs
|
||||
dd056409712113eb9c1b70e29b21022f5b30df6ffbfb2658e293edd6ec7195c7 packages/library/tests/guard.test.mjs
|
||||
2f3ec99129d16394d4458e252b9cba4e38ff66cd9393e3889f8afc3140e15a8e packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,23 @@
|
||||
68c05644d29188b897e2d4695d8e7025e143d424d69a91bc07bc950f589d34bf packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
49e01e5e73cc3e830ad48a1b6b13fe4d06a38950f17847dc049b75209d6d3f0a packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
8dc344b8a905c58d544b031ca84adb7eb869d9de88eb8e6b0fc1d9190702a38a packages/bus/tests/citation-guard.test.mjs
|
||||
8a2d4b636f8d4f926c484254c33ecf7b7e4722acef3250d2730593984d5ac06b packages/bus/tests/citation-helpers.mjs
|
||||
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||
825da818ba3cf45a4837f0f15a9c2cdba55a3d0bbb4479d55236f35799427c6f packages/business/src/business.mjs
|
||||
dbd79a4dfc926620d034b540f98f39336d81e4c79f214424c3cf19250c1d1f8d packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
1f1bea250f694a2aae9b9dc856b3c3424ceca153ccf826c99ccb98e6ea9cd394 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
0365114cf5773cbaa9fba196fbd1e773b2ec5ad2300c40231c357ca58884b9e3 packages/library/src/guard.mjs
|
||||
aaa8f7ba60a729a88a6ef57b3d5ff5a69bcf0e17b60e7db51a8dc5a697627b6c packages/library/src/index.mjs
|
||||
3e04fa25a64334d33ffbec704205b32cd01992f2d341e908f933a345199beee0 packages/library/src/limits.mjs
|
||||
c9e682eff87e822647921288d7fce0ebdc7b09f0a6dfdd6528f10249ae26e81b packages/library/src/reply-protocol.mjs
|
||||
dd056409712113eb9c1b70e29b21022f5b30df6ffbfb2658e293edd6ec7195c7 packages/library/tests/guard.test.mjs
|
||||
2f3ec99129d16394d4458e252b9cba4e38ff66cd9393e3889f8afc3140e15a8e packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
3e04fa25a64334d33ffbec704205b32cd01992f2d341e908f933a345199beee0 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run only on Sage CLEAR. Mutate isolated HEAD archives; never edit the checkout.
|
||||
|
||||
A green control run is required before each negative result counts as a kill.
|
||||
Usage: python3 agents/beakley/work/queue-217/mutants.py OUTPUT_DIRECTORY
|
||||
"""
|
||||
from pathlib import Path
|
||||
import io
|
||||
import hashlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = Path(sys.argv[1]).resolve()
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
WRITER = "packages/business/src/organization.mjs"
|
||||
BROKER = "packages/bus/src/broker.mjs"
|
||||
CLIENT = "packages/bus/src/client.mjs"
|
||||
ATOMIC = "if (check) this.#event(s, 'action.allowed', { action: 'message.checked', message: mid, check }, mid, checkEventId(mid));"
|
||||
REENTER = "return this.#reenter(cap, s, { verb: 'message.send', args }, check);"
|
||||
SPLIT = """const result = this.#reenter(cap, s, { verb: 'message.send', args }, check);
|
||||
if (check) this.#store.transaction(() => this.#event(s, 'action.allowed', { action: 'message.checked', message: result.id, check }, result.id, checkEventId(result.id)));
|
||||
return result;"""
|
||||
MUTANTS = [
|
||||
("M1-writer-fence", [(WRITER, " replyLimitsFence(old, document, actor);", ""),
|
||||
(WRITER, " replyLimitsFence(old, written, actor);", "")],
|
||||
["packages/business/tests/reply-limits.test.mjs"]),
|
||||
("M2-incoming-human", [(WRITER, "actor === old.human", "actor === document.human")],
|
||||
["packages/business/tests/reply-limits.test.mjs"]),
|
||||
("M3-split-check-transaction", [(BROKER, ATOMIC, ""), (BROKER, REENTER, SPLIT)],
|
||||
["packages/bus/tests/citation-guard.test.mjs"]),
|
||||
("M5-hide-library-unavailable", [("packages/library/src/guard.mjs",
|
||||
"refuse('citation-unavailable', 'The library is busy or unavailable. Retry the answer when the library is available.');",
|
||||
"served = null;")], ["packages/bus/tests/citation-availability.test.mjs"]),
|
||||
("M4-drop-client-reason", [(CLIENT, "new BusError(r.error, typeof r.reason === 'string' ? r.reason.slice(0, 300) : undefined)", "new BusError(r.error)")],
|
||||
["packages/bus/tests/citation-guard.test.mjs"]),
|
||||
]
|
||||
|
||||
archive = subprocess.check_output(["git", "archive", "HEAD", "packages", "roles", "scripts", "tools", "docs", "package.json", "RELEASE"], cwd=ROOT)
|
||||
summary = []
|
||||
selected = set(sys.argv[2:])
|
||||
product_files = [line.split(' ', 1)[1] for line in (ROOT / 'agents/beakley/work/queue-217/manifest.r5.sha256').read_text().splitlines()]
|
||||
def hashes(base, files):
|
||||
return {file: hashlib.sha256((base / file).read_bytes()).hexdigest() for file in files}
|
||||
original_products = hashes(ROOT, product_files)
|
||||
for name, changes, tests in MUTANTS:
|
||||
if selected and name not in selected:
|
||||
continue
|
||||
with tempfile.TemporaryDirectory(prefix="row217-mutant-") as directory:
|
||||
work = Path(directory)
|
||||
with tarfile.open(fileobj=io.BytesIO(archive)) as source:
|
||||
source.extractall(work, filter="data")
|
||||
modules = ROOT / "node_modules"
|
||||
if modules.is_symlink():
|
||||
raise SystemExit('root node_modules must be a local install, never a canonical link')
|
||||
if modules.exists():
|
||||
os.symlink(modules.resolve(), work / "node_modules", target_is_directory=True)
|
||||
cmd = ["timeout", "-k", "10", "120", "node", "--test", *tests]
|
||||
with (OUT / f"{name}-control.log").open("w") as log:
|
||||
control = subprocess.run(cmd, cwd=work, stdout=log, stderr=subprocess.STDOUT, timeout=120)
|
||||
if control.returncode:
|
||||
raise SystemExit(f"{name}: control RED, no mutant result claimed")
|
||||
originals = {file: (work / file).read_bytes() for file, _, _ in changes}
|
||||
try:
|
||||
for file, before, after in changes:
|
||||
path = work / file
|
||||
text = path.read_text()
|
||||
if text.count(before) != 1:
|
||||
raise SystemExit(f"{name}: expected exactly one mutation anchor in {file}")
|
||||
path.write_text(text.replace(before, after))
|
||||
with (OUT / f"{name}.log").open("w") as log:
|
||||
mutant = subprocess.run(cmd, cwd=work, stdout=log, stderr=subprocess.STDOUT, timeout=140)
|
||||
if mutant.returncode != 1:
|
||||
raise SystemExit(f"{name}: expected assertion failure exit 1, got {mutant.returncode}")
|
||||
finally:
|
||||
for file, content in originals.items():
|
||||
(work / file).write_bytes(content)
|
||||
assert all((work / file).read_bytes() == content for file, content in originals.items())
|
||||
assert hashes(ROOT, product_files) == original_products, 'checkout product hashes changed'
|
||||
(OUT / f"{name}-restored.sha256").write_text(''.join(f'{digest} {file}\n' for file, digest in sorted(original_products.items())))
|
||||
with (OUT / f"{name}-restored-green.log").open("w") as log:
|
||||
restored = subprocess.run(cmd, cwd=work, stdout=log, stderr=subprocess.STDOUT, timeout=140)
|
||||
if restored.returncode:
|
||||
raise SystemExit(f"{name}: restored control RED")
|
||||
summary.append(f"{name}: killed (control green, mutant exit {mutant.returncode}, hashes restored, restored green)")
|
||||
(OUT / "summary.txt").write_text("\n".join(summary) + "\n")
|
||||
print(summary[-1], flush=True)
|
||||
@@ -0,0 +1,112 @@
|
||||
1: e25ef0373 ! 1: b9ef816ff launchpad: row 217 C2-06 work in progress, UNTESTED, handed to Beakley (mos-1500)
|
||||
@@ packages/bus/src/broker.mjs: export class Broker {
|
||||
if (CREDENTIAL_VERBS.includes(request.verb)) return this.#requestCredential(s, request.verb, structuredClone(args));
|
||||
if (request.verb === 'instance.hire' || request.verb === 'instance.fire')
|
||||
return this.#chiefLifecycle(s, request.verb, structuredClone(args));
|
||||
+ if (request.verb === 'grants.scope' || request.verb === 'grants.scope.set')
|
||||
+ return this.#grantScopeVerb(s, request.verb, structuredClone(args));
|
||||
- if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set", "veto.set"].includes(request.verb)) {
|
||||
+ if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set", "veto.set", "library.limits.set"].includes(request.verb)) {
|
||||
// Row 123 (#1617, REQ-COS-1, REQ-LIMIT-1): organization.set is limits.set for a role whose
|
||||
@@ packages/bus/src/runtime.mjs: export async function startBroker({
|
||||
project: {
|
||||
readSpec: context => projectModule.readSpec({ ...context, dataRoot }),
|
||||
@@ packages/bus/src/runtime.mjs: export async function startBroker({
|
||||
- else if(verb==='plugins.set')result=plugins.set(id,args,actor);
|
||||
- // Row 194 (#1692, V6): the human's keep and noHire lists, through the instance writer.
|
||||
else if(verb==='veto.set')result=instances.veto(id,args,actor);
|
||||
+ // Row 298: the human's grant scope, through the instance writer.
|
||||
+ else if(verb==='grants.scope.set')result=instances.grantScope(id,args,actor);
|
||||
+ // C2-06 M11: the human's replyLimits, through the organization writer's fence.
|
||||
+ else if(verb==='library.limits.set')result=setReplyLimits(organizationWriter({dir:organization.dir,rolesDir:organization.rolesDir}),id,args,actor);
|
||||
// Row 194 (D2.1, D2.2): the chief's hire and fire, with the reason in the change context.
|
||||
@@ packages/business/src/organization.mjs: export function organizationWriter({ dir
|
||||
const parent = join(dir, "businesses");
|
||||
directory(evidence);
|
||||
@@ packages/business/src/organization.mjs: export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
- // mapping, including library playgrounds and hybrid universes.
|
||||
- if (canonicalJson(document?.projects) !== canonicalJson(old.projects))
|
||||
- fail('organization-projects-protected');
|
||||
-+ replyLimitsFence(old, document, actor);
|
||||
- validateBusinessDocument(document, file, { rolesDir });
|
||||
- const bytes = JSON.stringify(document, null, 2) + "\n";
|
||||
- // Check and log exactly what lands on disk, for every setter: a value JSON
|
||||
+ // mapping, including library playgrounds and hybrid universes.
|
||||
+ if (canonicalJson(document?.projects) !== canonicalJson(old.projects))
|
||||
+ fail('organization-projects-protected');
|
||||
++ replyLimitsFence(old, document, actor);
|
||||
+ validateBusinessDocument(document, file, { rolesDir });
|
||||
+ const bytes = JSON.stringify(document, null, 2) + "\n";
|
||||
+ // Check and log exactly what lands on disk, for every setter: a value JSON
|
||||
@@ packages/business/src/organization.mjs: export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
- const written = JSON.parse(bytes);
|
||||
- if (canonicalJson(written.projects) !== canonicalJson(old.projects))
|
||||
- fail('organization-projects-protected');
|
||||
-+ replyLimitsFence(old, written, actor);
|
||||
- validateBusinessDocument(written, file, { rolesDir });
|
||||
- const after = sha256(bytes);
|
||||
- const transaction = randomUUID();
|
||||
+ const written = JSON.parse(bytes);
|
||||
+ if (canonicalJson(written.projects) !== canonicalJson(old.projects))
|
||||
+ fail('organization-projects-protected');
|
||||
++ replyLimitsFence(old, written, actor);
|
||||
+ validateBusinessDocument(written, file, { rolesDir });
|
||||
+ if (beforeWrite) {
|
||||
+ beforeWrite();
|
||||
@@ packages/business/src/organization.mjs: export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
});
|
||||
}
|
||||
@@ packages/library/src/guard.mjs (new)
|
||||
+}
|
||||
|
||||
## packages/library/src/index.mjs ##
|
||||
-@@ packages/library/src/index.mjs: export { renderItems, KINDS, NOTICES, UNREADABLE } from './render.mjs';
|
||||
- export { survey, formatSurvey, surveyMain } from './survey.mjs';
|
||||
- export { SYNC_STATE_FILE, mirrorPath, ensureMirror, withLibraryLock, servedCommit, readSyncState, libraryStatus, readLibraryFile } from './mirror.mjs';
|
||||
+@@ packages/library/src/index.mjs: export { SYNC_STATE_FILE, mirrorPath, ensureMirror, withLibraryLock, servedCommi
|
||||
export { syncCommand, sync, ASKPASS, SLOT, SYNC_INTERVAL_MS } from './sync.mjs';
|
||||
+ export { buildIndex, indexFor, clearIndexCache, indexCacheSize } from './index-build.mjs';
|
||||
+ export { sees, find, get, search, manuscript, universeInfo, QUERY_REPLY_CAP, LOOKUP_LIMIT, MANUSCRIPT_LINES } from './query.mjs';
|
||||
+export { REPLY_LIMITS, resolveReplyLimits, checkReplyLimits } from './limits.mjs';
|
||||
+export { GUARD_CODES, FIXED_TEXTS, WEB_CITE_DEFINITIONS, REASON_MAX, GuardError, parseReply, checkReply, libraryReader, guardReply, lastCommits } from './guard.mjs';
|
||||
|
||||
2: c25f5b8da ! 2: 7400509ba row 217: citation runner retry and acceptance draft (Untested)
|
||||
@@ Commit message
|
||||
row 217: citation runner retry and acceptance draft (Untested)
|
||||
|
||||
## 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 | Beakley (88b97f65) | Row 217 #1718 takeover, before implementation | Clean base 45dca69f6 on beakley/row217. Recovering Launchpad handoff for citation guard, live settings, page store and runner retry. Acceptance is C2-06/design 5.2 plus sage-3595/3599/3602. Tracking agents/beakley/work/queue-217/BUILD.md; no product changes or Node/tests; Sage CLEAR required.
|
||||
+
|
||||
+2026-10-23 | Beakley (88b97f65) | Row 217 #1718 untested draft checkpoint | Continued Launchpad e25ef0373; added runner retry/text/note implementation, pure protocol constants and sage-3640 approved single read-only mount, CLI documentation and new acceptance test files. Static checks caught missing projects compatibility, async request mutation and Git pathspec matching; bounded fixes and regression tests authored. git diff --check clean. No Node/test/mutant executed; Sage holds CLEAR. 283/216/285 dependency checks and all acceptance execution remain open. Packet agents/beakley/work/queue-217/MANIFEST.md.
|
||||
@@ agents/beakley/work/queue-217/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 | Beakley (88b97f65, Codex gpt-6.1-sol) | Row 217 #1718 takeover under Q245 | Registered beakley/row217; read brief/design and standing Sage rulings; awaiting Launchpad ref before implementation. Queue next refused linked worktree; reported to Sage. Untested, no Node/test run pending CLEAR.
|
||||
|
||||
3: b6c3129d2 ! 3: a447880cb row 217: early-review coverage and availability RED cases (Untested)
|
||||
@@ Commit message
|
||||
row 217: early-review coverage and availability RED cases (Untested)
|
||||
|
||||
## BUILD-LOG.md ##
|
||||
-@@ BUILD-LOG.md: Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
|
||||
+@@ BUILD-LOG.md: remain; no live state, schema or bus policy change.
|
||||
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 takeover, before implementation | Clean base 45dca69f6 on beakley/row217. Recovering Launchpad handoff for citation guard, live settings, page store and runner retry. Acceptance is C2-06/design 5.2 plus sage-3595/3599/3602. Tracking agents/beakley/work/queue-217/BUILD.md; no product changes or Node/tests; Sage CLEAR required.
|
||||
|
||||
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 untested draft checkpoint | Continued Launchpad e25ef0373; added runner retry/text/note implementation, pure protocol constants and sage-3640 approved single read-only mount, CLI documentation and new acceptance test files. Static checks caught missing projects compatibility, async request mutation and Git pathspec matching; bounded fixes and regression tests authored. git diff --check clean. No Node/test/mutant executed; Sage holds CLEAR. 283/216/285 dependency checks and all acceptance execution remain open. Packet agents/beakley/work/queue-217/MANIFEST.md.
|
||||
4: dde5b8496 = 4: 2655ec010 row 217: keep fixture status on its current served tip (Untested)
|
||||
@@ -0,0 +1,30 @@
|
||||
# Row217 round2 test receipts
|
||||
|
||||
Sage CLEAR sage-4042/4043, 2026-10-24. Source revision 2d274a5b6. All runs
|
||||
serialized; source files stayed unchanged through the whole-suite and mutant pass.
|
||||
Commands used TMPDIR=/mnt/storage/scratch/tmp/tNoueZ and timeout -k 10.
|
||||
|
||||
- npm ci --ignore-scripts --no-audit --no-fund at the worktree root (300s bound),
|
||||
then packages/webui (300s). Actual directories, no canonical links.
|
||||
- node --test packages/bus/tests/citation-availability.test.mjs (180s).
|
||||
- python3 agents/beakley/work/queue-217/mutants.py <m5-red> M5-hide-library-unavailable
|
||||
(420s). Green control, the authorized pre-fix RED stand-in, restore+green.
|
||||
- node --test --test-concurrency=1 with the seven new test files (900s):
|
||||
library/guard, business/reply-limits, cli/reply-limits, bus/citation-availability,
|
||||
bus/citation-guard, bus/citation-runtime and harness/citation-runner. Whole files.
|
||||
- First attempt 56/57: new runtime fixture invalid-authority. Only that fixture
|
||||
was corrected to the established broker projection. runtime-fixture-retry.log
|
||||
is its 1/1 rerun (180s); touched-rerun.log is the whole-file 57/57 rerun.
|
||||
- npm test in packages/library, then business (900s each), bus and cli (1200s
|
||||
each), harness and seat (900s each). Never overlapped package runs.
|
||||
- python3 agents/beakley/work/queue-217/mutants.py <mutants> (900s).
|
||||
The driver wraps each Node run in timeout -k 10 120. Each negative exits1,
|
||||
is preceded by a green control, restores its product bytes/hashes and then
|
||||
reruns the restored control green. No checkout product file is mutated.
|
||||
|
||||
summary.json holds complete counts; summary.txt is the short version. M1 anchors
|
||||
are recorded in m1-anchors.txt. The five *-restored.sha256 files match current
|
||||
product files. Full outputs are retained, including the first new-test failure.
|
||||
Negative logs intentionally keep Node's whitespace-only diagnostic padding;
|
||||
standard git diff --check flags that padding, not source code. No binary logs.
|
||||
No old test failed or was edited. Sage runs scripts/test-*.sh at landing.
|
||||
@@ -0,0 +1,11 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5142.976705ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (33.874404ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (33.746516ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5307.376982
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,280 @@
|
||||
npm notice run @mosaic/[email protected] test
|
||||
npm notice run node --test tests/
|
||||
✔ a grant's level is collaborator or viewer (2.351697ms)
|
||||
✔ any other level refuses with the key path named (1.505284ms)
|
||||
✔ covers tells a viewer grant from a collaborator grant (0.605812ms)
|
||||
✔ covers refuses a call with no level, so a viewer grant can't slip past a collaborator check (0.247487ms)
|
||||
✔ a viewer grant still needs its business, project, instance and expiry to cover (0.247397ms)
|
||||
✔ a valid document passes and comes back frozen (4.146183ms)
|
||||
✔ checkAccess does not freeze or alias the caller's document (0.552481ms)
|
||||
✔ LEVELS is the single allowed level set, collaborator and viewer (row 193a) (0.259109ms)
|
||||
✔ accessFilePath is access.json in the config directory (0.495701ms)
|
||||
✔ top level: object, unknown keys, accessVersion, revision, arrays (2.131141ms)
|
||||
✔ users: unknown keys and the id rule (0.609866ms)
|
||||
✔ users: human, tester and any business's human are refused (0.455944ms)
|
||||
✔ users: home is a given peer or local (0.372673ms)
|
||||
✔ users: subjects, provider, sub, uniqueness and owner subjects (2.126611ms)
|
||||
✔ grants: unknown keys, id, user and business (0.897075ms)
|
||||
✔ grants: projects is ["*"] or distinct lowercase UUIDs (1.199677ms)
|
||||
✔ grants: instances is ["*"] or distinct declared instances of that business (0.889396ms)
|
||||
✔ grants: any level other than collaborator or viewer refuses, naming grants[i].level (1.13158ms)
|
||||
✔ grants: expires is optional ISO time, and a past expiry is not refused (0.979808ms)
|
||||
✔ the context is required and checked (1.713394ms)
|
||||
✔ loadAccess reads a valid file and checks it (0.755936ms)
|
||||
✔ loadAccess refuses a missing file, a symlink and a directory with exit 4 (0.519376ms)
|
||||
✔ loadAccess refuses another owner and group or other write (0.581157ms)
|
||||
✔ loadAccess refuses bad JSON and a bad document with exit 2 (1.841309ms)
|
||||
✔ covers: * and listed ids match (0.340508ms)
|
||||
✔ covers: an unlisted project, instance or business doesn't match (0.20254ms)
|
||||
✔ covers: an expired grant doesn't match, at and after the instant (0.698261ms)
|
||||
✔ covers: level is the least level asked for (row 193a) (0.213107ms)
|
||||
✔ covers: a missing or unknown level throws (row 193a, N1) (0.385188ms)
|
||||
✔ covers: a grant with an unknown level covers nothing (row 193a) (0.191435ms)
|
||||
✔ covers: now must be a valid Date (0.21008ms)
|
||||
✔ today's files load unchanged: identity, publish and remote are optional (11.61814ms)
|
||||
✔ a granted agent loads with its identity, grant and the project's upstream (4.906248ms)
|
||||
✔ N10: publish needs identity and a gitea definition, and the error names the grant (9.522958ms)
|
||||
✔ publish names declared projects that have a remote, each once (7.911234ms)
|
||||
✔ identity fields are checked (14.073433ms)
|
||||
✔ D9c: the upstream must be its own normalized form, exactly /OWNER/REPO (29.451403ms)
|
||||
✔ D9c: one .git is stripped, and the fork is built from the stripped name (1.733511ms)
|
||||
✔ D9c: gitea.url names the instance only (10.698646ms)
|
||||
✔ the upstream's host and port must be the agent's Gitea's (4.064711ms)
|
||||
✔ remote-not-fork: an upstream the agent's own user owns, compared without case (10.115713ms)
|
||||
✔ store references: own path only (design D1, D3a) (0.50611ms)
|
||||
✔ the role file loads and resolves through loadBusiness (18.165733ms)
|
||||
✔ the shipped file is exactly the approved list (1.392044ms)
|
||||
✔ the assistant's authority is message.send and record.write within role (4.312182ms)
|
||||
✔ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.978023ms)
|
||||
✔ a business grant through limits does not widen the role (6.715605ms)
|
||||
✔ a launch block whose by is the assistant is refused (3.553443ms)
|
||||
✔ the instance takes no credential references and no tracker (9.975785ms)
|
||||
✔ an arbiter whose role definition declares messaging lists is refused, for each kind (8.992963ms)
|
||||
✔ an arbiter without messaging lists is accepted beside the limited assistant (3.104416ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.788753ms)
|
||||
✔ REQ-HARN-2: the assistant, companion and research hold the web grant, and no other role file does (1.683056ms)
|
||||
✔ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (31.538067ms)
|
||||
✔ C2-04: an otherwise valid publish grant cannot name a library repo (4.356759ms)
|
||||
✔ config directory and file path follow MOSAIC_CONFIG (0.467526ms)
|
||||
✔ the fixture business validates and comes back frozen (4.106001ms)
|
||||
✔ two instances may share a definition (2.097865ms)
|
||||
✔ top-level refusals (6.537312ms)
|
||||
✔ arbiters and projects (24.968268ms)
|
||||
✔ role instances (9.057417ms)
|
||||
✔ Vikunja bots (29.820456ms)
|
||||
✔ a role without Vikunja takes no tracker block (9.629575ms)
|
||||
✔ credential references match the definition's services (11.680645ms)
|
||||
✔ launch (31.945766ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses (34.141496ms)
|
||||
✔ loadBusiness: file checks (12.995702ms)
|
||||
✔ loadBusiness: not a regular file (111.510997ms)
|
||||
✔ loading writes nothing (1.787307ms)
|
||||
✔ names that are Object.prototype properties don't count as declared (5.786433ms)
|
||||
✔ the shipped example refuses as written and validates once filled in (2.551686ms)
|
||||
✔ persona vars: agent layer, creative definition only, both or neither (26.121282ms)
|
||||
✔ row 194 keep, noHire and retired validate, and a file without them loads as before (4.607715ms)
|
||||
✔ row 194 each bad keep, noHire or retired entry refuses (19.966409ms)
|
||||
✔ row 323: committed Case 1 document loads unchanged through the business loader (10.685638ms)
|
||||
✔ row 323: real business validate accepts the Case 1 fixture without tracker credentials (226.106251ms)
|
||||
✔ row 323: real factory install accepts the document's scratch input and writes it unchanged (294.937207ms)
|
||||
✔ row 323: the Case 1 business boots without a tracker or @sync credential (60.850375ms)
|
||||
✔ row 323: factory install refuses bus.tester false before writing (39.485453ms)
|
||||
✔ row 323: literal library key refuses through final business validation (176.986326ms)
|
||||
✔ row 323: a library project refuses through the factory profile plan (36.685902ms)
|
||||
✔ row 325: the Case 1 project's sole key matches the repository project id (7.063988ms)
|
||||
✔ row 325: real business validate reports the copied repository project valid (203.591785ms)
|
||||
✔ row 325: the old mosaic-stack project key refuses against those same project bytes (227.47487ms)
|
||||
✔ the role file loads and resolves through loadBusiness (22.21166ms)
|
||||
✔ the shipped file is exactly the approved list (0.355811ms)
|
||||
✔ the resolved record matches the approved list, so a widened file fails (4.433743ms)
|
||||
✔ the chief of staff holds role.launch, message.send, role.end, organization.change, record.write, instance.name, role.hire and role.fire; every other action is gated (6.025087ms)
|
||||
✔ the pm does not hold role.launch (7.814194ms)
|
||||
✔ a business grant of a gated action through limits does not widen the role (5.309288ms)
|
||||
✔ a limit that drops role.launch leaves the chief of staff with no launch block (5.488981ms)
|
||||
✔ a launch block whose by is the pm instance is refused (4.031539ms)
|
||||
✔ the contract names no duty to talk to the human and the pm contract no launch (1.077581ms)
|
||||
✔ the instance takes no credential references and no tracker (7.111256ms)
|
||||
✔ C2-04: business validate and resolve skip a library before code projects and credential checks (581.819531ms)
|
||||
✔ usage errors exit 4 (330.347677ms)
|
||||
✔ validate: a good business exits 0 and prints instance digests (80.712835ms)
|
||||
✔ validate: project files (358.68705ms)
|
||||
✔ validate: missing files and a broken system config (272.747082ms)
|
||||
✔ validate: credential reference problems exit 2 and name each one (75.47514ms)
|
||||
✔ validate: a not-issued store agent dated today passes, and an issued one with a past expiry fails (152.378642ms)
|
||||
✔ validate: a token file inside the repository is refused (76.459487ms)
|
||||
✔ validate: role definitions come from MOSAIC_ROLES_DIR (210.323656ms)
|
||||
✔ resolve: prints one instance's record (260.543734ms)
|
||||
✔ resolve: refusals (375.881441ms)
|
||||
✔ parse: exactly one of file or env, plus the service's date (3.551882ms)
|
||||
✔ check: a good file has no problems (1.2198ms)
|
||||
✔ check never opens the file: a write-only token passes (0.477268ms)
|
||||
✔ check: file problems (1.60977ms)
|
||||
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.508979ms)
|
||||
✔ check: dates and environment references (0.686041ms)
|
||||
✔ each definition loads, with a contract file, and is exactly the approved list (4.83466ms)
|
||||
✔ an organization file that declares them passes, and each resolves to least privilege (10.307081ms)
|
||||
✔ the chief of staff launches each one; no other role does (11.082937ms)
|
||||
✔ a C-suite role can't be made the launcher: launch.by must hold role.launch (5.052598ms)
|
||||
✔ limits can narrow a C-suite role but never widen it (4.524289ms)
|
||||
✔ any unknown key, tool, action or version in a role file is refused (4.904699ms)
|
||||
✔ row 298: checkGrantScope takes the D2 shape and refuses anything else (2.752624ms)
|
||||
✔ row 298: a missing scope is empty and a bad one falls back to empty, which covers nothing (0.482721ms)
|
||||
✔ row 298: a Gitea grant is in scope only when its agent and every project are listed (0.341447ms)
|
||||
✔ row 298 (sage-3594 Q2): host commands match whole argv tokens, and enrollment is never in scope (0.467524ms)
|
||||
✔ row 298: a bad grantScope in the file still loads, and the read shows the fallback (18.355754ms)
|
||||
✔ row 298: the writer checks the scope, logs the change and removes the key for {} (142.313501ms)
|
||||
✔ row 298 (sage-3594 Q1): the general writers can't write grantScope (25.353249ms)
|
||||
✔ REQ-WAIT-1: GATED_ONLY is exactly the hard waits; the new actions are in the vocabulary (1.86466ms)
|
||||
✔ REQ-ROLE-2: END_PROTECTED includes the assistant's thread definition (0.234237ms)
|
||||
✔ REQ-COS-3: role.hire and role.fire are routed, not hard waits (0.300439ms)
|
||||
✔ REQ-WAIT-3: every action is routed or a hard wait, never both, and the routed list names only actions (0.967731ms)
|
||||
✔ REQ-WAIT-3: instance.name is routed under REQ-MSG-2 (1.5), not a hard wait (row 141, sage-0589) (1.313696ms)
|
||||
✔ REQ-WAIT-2: a role listing a hard wait at either class is refused; the actions that left can be granted (5.343386ms)
|
||||
✔ S1: the business file refuses hosts at the top level and in a role, and host.<id> references (56.500208ms)
|
||||
✔ T1: address, user and host key checks, which the enrollment raise uses (11.374442ms)
|
||||
✔ T26: no wildcard and no shell, runner or interpreter as a prefix (2.722004ms)
|
||||
✔ D6: argv shape and the literal prefix match (0.922698ms)
|
||||
✔ C2-04: instance settings skip a library while checking a later code project (98.596648ms)
|
||||
✔ row 165 read lists each instance and the definitions add accepts (16.615936ms)
|
||||
✔ row 165 add: two instances join, validate exits 0, the log names the human, and the writer asks for no restart (row 194, D3) (308.207149ms)
|
||||
✔ row 165 change: definition, vars and launch membership log before and after; none asks for a restart, the bus reloads (row 194, D3) (180.35178ms)
|
||||
✔ row 165 refuses each validation failure and leaves the file's bytes alone (44.040709ms)
|
||||
✔ T19 add of a credentialed definition writes store references dated today, names its bot, and validate exits 0 (101.60641ms)
|
||||
✔ T19 add of a definition that declares host writes references for its token services only (12.273146ms)
|
||||
✔ row 165 refuses a definition change to or from one that declares credentials (6.011439ms)
|
||||
✔ row 165 a vars or launch change on an instance with its binding succeeds and keeps the binding (87.081633ms)
|
||||
✔ row 165 refuses an END_PROTECTED definition and every change to a protected instance (9.425714ms)
|
||||
✔ row 165 refuses while a project file doesn't resolve, as validate would (21.508015ms)
|
||||
✔ row 165 refuses a stale revision and leaves the file's bytes alone (13.794117ms)
|
||||
✔ row 65 an instance write refuses harness-off, thinking-unsupported, an unknown model and a partial fallback (38.79158ms)
|
||||
✔ row 213 a creative instance is the human's: add, vars and definition changes, persona digest only (105.47603ms)
|
||||
✔ row 256 T1-T3, T5 the human deletes a creative instance, no other actor does, and the assistant stays (106.119319ms)
|
||||
✔ row 256 T4 the human's delete of the only listed instance, a creative one, is the last-listed refusal (3.635722ms)
|
||||
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (27.767213ms)
|
||||
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (1.218782ms)
|
||||
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (65.935591ms)
|
||||
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (42.79247ms)
|
||||
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (20.740471ms)
|
||||
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (73.013224ms)
|
||||
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (5.530215ms)
|
||||
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (31.489428ms)
|
||||
✔ Q169 assistants: vars and definition change and log the actor; tracker null leaves no tracker (74.510885ms)
|
||||
✔ Q169 assistants: refuses other instances, other definitions, holder and credentials, empty entries, a tracker the definition has no credential for and a stale revision (46.648048ms)
|
||||
✔ Q169 assistants: a definition with a vikunja credential takes a tracker change and refuses its removal or a used bot (28.59175ms)
|
||||
✔ row 65 routes lists every instance with harness, model, thinking and fallback, the switched-on harnesses, the model keys and each harness's levels (4.496203ms)
|
||||
✔ row 65 setRoutes saves a harness, model, thinking and whole fallback, reads each back from the file, logs the actor, and a null fallback clears all three (28.880486ms)
|
||||
✔ row 65 setRoutes refuses a partial fallback, a switched-off harness, a level the harness doesn't apply, an unloaded module, an unknown model or instance, a malformed route, no change and a stale revision, with the file unchanged (33.617502ms)
|
||||
✔ row 65 a file with harness vars and no plugins.harness loads and its views read; a model change still saves, and a route change refuses harness-off (15.471063ms)
|
||||
✔ B7 kept instance: the models keys it names and the instances key are the human's (32.512862ms)
|
||||
✔ B7 kept instance: its route is the human's (13.733689ms)
|
||||
✔ M10 creative instance: models keys it names, the instances key and its route are the human's (41.972839ms)
|
||||
✔ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (75.744651ms)
|
||||
✔ C2-04 M9: code projects, additions, deletions and array changes are protected too (73.935851ms)
|
||||
✔ C2-04 M9: canonical key order is allowed and a non-project setter still records its change (53.997154ms)
|
||||
✔ C2-04 M9: a serialization hook cannot change projects after the canonical comparison (29.857522ms)
|
||||
✔ M7/M8: library seats come from library businesses, creative seats from their creative definitions (20.881147ms)
|
||||
✔ M8: a business with no library project puts no seat in either set (19.190367ms)
|
||||
✔ M7/M8: a business file that is not valid JSON throws; no empty set (7.135915ms)
|
||||
✔ M7/M8: an absent businesses directory gives empty sets (1.688414ms)
|
||||
✔ M7/M8: a businesses path that is not a directory throws; no empty set (4.583153ms)
|
||||
✔ M7/M8: a businesses symlink to a missing target throws; no empty set (2.208552ms)
|
||||
✔ M7/M8: a config directory under a regular file throws (lstat fails with ENOTDIR); no empty set (0.601986ms)
|
||||
✔ M7/M8: a business file that can't be read throws; no empty set (12.318486ms)
|
||||
✔ T6 delete refuses in the design's order and leaves the file's bytes alone (104.896221ms)
|
||||
✔ T6 delete refuses the last listed instance (7.490182ms)
|
||||
✔ T6 a clean delete removes the entry and its own references, adds retired, and the name can't come back (213.690055ms)
|
||||
✔ T7 veto: keep, unkeep, nohire and hire each write once, a repeat is no change, and the log names the human (171.894178ms)
|
||||
✔ T7 veto refuses an unknown or END_PROTECTED keep, a bad request, a stale revision and a business without launch (26.238526ms)
|
||||
✔ D2.2 hire adds and lists a new instance, logs the chief's context, and sets only model and limits (92.062371ms)
|
||||
✔ D2.2 hire refuses noHire, a kept name and a listed name, and re-lists a fired one only as it was (29.586421ms)
|
||||
✔ D2.2 fire delists and keeps the entry, a repeat is done with no write, and each refusal holds (88.451593ms)
|
||||
✔ row 256 checkDelete passes the human's creative delete and refuses another actor's; fire still refuses (5.294872ms)
|
||||
✔ row 239 T1 a fire on a revision that goes stale before the writer's lock refuses there and revokes nothing (5.756058ms)
|
||||
✔ row 239 T2 a write after the revoke refused before publication carries revoked, and the file keeps the edit (7.753057ms)
|
||||
✔ row 239 T3 any error after the revoke carries revoked, for a fire and a delete; a revoke that throws carries nothing (32.403692ms)
|
||||
✔ Q247: omitting tracker loads as null and preserves independent role bots (10.793015ms)
|
||||
✔ Q247: real business validate passes without a sync credential reference or file (168.186763ms)
|
||||
✔ Q247: present tracker retains its values and validation without mutating the input (9.372029ms)
|
||||
✔ Q247: present null, undefined and malformed trackers still refuse (18.314895ms)
|
||||
✔ Q247: absent business tracker does not waive a role's Vikunja bot requirement (2.859494ms)
|
||||
✔ REQ-PLUG-1 schema: plugins is optional, names known kinds and checked module names (40.546579ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 the setter writes plugins through the organization writer and logs old and new names (92.751615ms)
|
||||
✔ vikunja:19/309 a patch changes only the kinds it names; null removes one kind, explicitly and in the trail (100.494327ms)
|
||||
✔ vikunja:19/309 an unknown kind (set or removed), a bad module, an empty patch and null on the wrong shape are refused, nothing written (9.927988ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 the writer checks the bytes it writes: a Date in plugins is refused there too (19.985103ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 a refused setter write throws a code, changes nothing and logs nothing (4.143331ms)
|
||||
✔ vikunja:19/305 with an exists check, the setter refuses a module no tree file answers to, before anything is written (18.273494ms)
|
||||
✔ optional project repo is preserved and must be a normalized absolute path (32.895793ms)
|
||||
✔ loaded business project repo cannot be nested in any declared code checkout (6.807091ms)
|
||||
✔ path and load (3.706713ms)
|
||||
✔ refusals (1.897981ms)
|
||||
✔ record.write is within-role for the eleven approved v2 agents, never gated-only (3.85853ms)
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (98.331729ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (55.766765ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (17.47904ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (16.039248ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (17.259974ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (19.048524ms)
|
||||
✔ C2-04: a library first in projects does not hide a later project layer (21.880181ms)
|
||||
✔ systemVars flattens the validated config (1.820728ms)
|
||||
✔ precedence: system, business, project, project role, agent (10.989776ms)
|
||||
✔ limits narrow the definition and never widen it (8.400306ms)
|
||||
✔ role.launch stays within-role only for the instance the launch block names (18.468852ms)
|
||||
✔ limits.authority without role.launch leaves the launcher with no launch block (13.185333ms)
|
||||
✔ limits.authority narrows cross-role actions too (2.699689ms)
|
||||
✔ classify (6.249491ms)
|
||||
✔ the record carries what the broker and launcher need (4.7922ms)
|
||||
✔ digest: key order doesn't matter, any value change does (12.395981ms)
|
||||
✔ refusals (3.790542ms)
|
||||
✔ the four shipped version 2 roles load (5.229965ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (2.178782ms)
|
||||
✔ shipped authority follows the note's table (13.291498ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (2.100731ms)
|
||||
✔ version 1 files keep loading with no authority (1.521097ms)
|
||||
✔ the conductor policy isn't a role (0.302424ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.564721ms)
|
||||
✔ version 2 refusals (3.788273ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (3.881951ms)
|
||||
✔ messaging: optional, role definitions and human, never the tester (1.949889ms)
|
||||
✔ row 64 skills: optional, distinct skill directory names, none on version 1 (2.255987ms)
|
||||
✔ credentials: Gitea scopes (1.343013ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.709889ms)
|
||||
✔ credentials: services (0.759517ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.838905ms)
|
||||
✔ the six creative roles load: read tools, message.send only, messaging with the human only (0.693212ms)
|
||||
✔ M1: a creative role file wider than read and answer is refused (1.897832ms)
|
||||
✔ hireable: HIRE_ALLOWED is exactly the six plain work actions, and every one is routed (0.173708ms)
|
||||
✔ hireable: a list naming a definition that holds any refused action refuses at load, one case per action (18.133668ms)
|
||||
✔ hireable: a mixed definition refuses, a clean list loads, and the named file's own hireable isn't read (1.146869ms)
|
||||
✔ hireable: a missing, version 1, duplicate or badly named entry refuses, and the key is optional (3.734122ms)
|
||||
✔ the pm and the cto check a saved spec against settled decisions and work in flight, and answer with a BLOCK or an accept (11.616694ms)
|
||||
✔ the chief of staff relays a BLOCK or an accept to the assistant as a reply to the message that carried it (1.277302ms)
|
||||
✔ the assistant summarizes a spec review in its own words and doesn't repeat the chief of staff's text (0.70976ms)
|
||||
✔ REQ-LIMIT-3 schema: each subject kind, limit 0 and no ceiling are accepted; bad limits, times, keys and undeclared instances refuse (49.216064ms)
|
||||
✔ REQ-LIMIT-3 the writer stamps since: a new limit gets now, a changed one keeps its time, a different time is refused (85.0223ms)
|
||||
✔ REQ-LIMIT-3 only tokens.* at schema depth holds token limits: agent limits named `limit` save as sent, and a token limit named `limit` is stamped and reset (67.775561ms)
|
||||
✔ REQ-LIMIT-3 a reset moves since to now, is logged with its actor, and refuses a path that is not a limit (34.296428ms)
|
||||
✔ REQ-LIMIT-2 schema: launch.account names a credential set; a daily quota needs a reset and an IANA zone and names the account (33.871693ms)
|
||||
✔ REQ-LIMIT-2 the writer stamps a daily quota's since, keeps its reset and zone, and a reset moves since (33.845575ms)
|
||||
✔ every key names known layers and a merge rule (1.726458ms)
|
||||
✔ unknown keys and wrong layers refuse (1.084085ms)
|
||||
✔ types (3.275461ms)
|
||||
✔ merge: defaults, then the most specific layer wins (0.419589ms)
|
||||
✔ merge: limits only narrow, and provenance lists each source (0.757115ms)
|
||||
✔ merge doesn't change its inputs (0.206627ms)
|
||||
✔ web.searxng takes https anywhere and http on loopback, so Jason's 127.0.0.1:8888 passes (0.262745ms)
|
||||
✔ both definitions load, with a contract file, and are exactly the approved lists (5.013658ms)
|
||||
✔ security-reviewer has no write or shell tool and classifies only the verdict as its own (9.066819ms)
|
||||
✔ merge-gate's only grant is git.merge.protected cross-role, and it has no write tool (3.470869ms)
|
||||
✔ neither role can be an arbiter: its messaging lists refuse the business file (row 133) (8.498658ms)
|
||||
✔ every role contract carries the proactive line in its Authority section (1.099286ms)
|
||||
✔ pm and chief-of-staff say where a raise goes under REQ-DEC-2 (0.851681ms)
|
||||
✔ the merge landing takes two runs: merge-gate, cto and chief-of-staff each say their part (0.660442ms)
|
||||
ℹ tests 270
|
||||
ℹ suites 0
|
||||
ℹ pass 270
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2873.146579
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,182 @@
|
||||
npm notice run test
|
||||
npm notice run node --test tests/*.test.mjs
|
||||
✔ sessionModel: the route is the instance's own vars, and a missing one refuses no-route by name (37.340888ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (10.573131ms)
|
||||
✔ a chief-of-staff bundle lists launch among the bus tools and the manifest's typed tools (4.412373ms)
|
||||
✔ a chief-of-staff bundle lists end_run, change_organization and set_name as typed and nothing untyped, for either harness (8.193363ms)
|
||||
✔ the stop line names end_run for a role.end holder, and every other role keeps today's text (13.648406ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (7.875641ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (22.548582ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (13.290253ms)
|
||||
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (15.420914ms)
|
||||
✔ the prompt explains landing to a role with land_work, and to no other (9.918107ms)
|
||||
✔ the prompt tells the pm to ask the chief of staff for sessions, and no other role (18.356673ms)
|
||||
✔ a bundle is written once: an existing file refuses (3.135889ms)
|
||||
✔ a path with a single quote can't go into the hook command (3.86973ms)
|
||||
✔ web grant on pi: the bundle has web_search and web_fetch, and no built-in web tool (4.927453ms)
|
||||
✔ no web grant on pi: no web tool in the bundle, and the gate blocks a web call (4.535472ms)
|
||||
✔ web grant on claude-code: the bundle has web_search and web_fetch, and no built-in web tool (4.409455ms)
|
||||
✔ no web grant on claude-code: no web tool in the bundle, and the gate blocks a web call (7.034483ms)
|
||||
✔ a granted session's gate lets the typed web tools through and still blocks the built-ins (6.471178ms)
|
||||
✔ search-source searxng-url: a granted role gets the typed broker tools and no harness web tool; an ungranted role gets nothing (12.185523ms)
|
||||
✔ search-source searxng-bundled: a granted role gets the typed broker tools and no harness web tool; an ungranted role gets nothing (11.958441ms)
|
||||
✔ search-source harness-builtin: claude-code gets WebSearch and WebFetch and no typed web tool, only when granted (12.933659ms)
|
||||
✔ search-source harness-builtin is refused for a granted pi role, which has no built-in web tool (4.064682ms)
|
||||
✔ C2-06 RESULT reply-invalid retries once, delivers only the valid answer and names reason (351.396996ms)
|
||||
✔ C2-06 RESULT citation-missing retries once, delivers only the valid answer and names reason (256.673143ms)
|
||||
✔ C2-06 RESULT citations-over-limit retries once, delivers only the valid answer and names reason (222.852677ms)
|
||||
✔ C2-06 RESULT citation-invalid retries once, delivers only the valid answer and names reason (261.389143ms)
|
||||
✔ C2-06 RESULT playground-cited-as-canon retries once, delivers only the valid answer and names reason (268.849657ms)
|
||||
✔ C2-06 RESULT citation-cross-universe retries once, delivers only the valid answer and names reason (231.674929ms)
|
||||
✔ C2-06 RESULT citation-unfetched retries once, delivers only the valid answer and names reason (216.064282ms)
|
||||
✔ C2-06 RESULT two guard refusals use the citation fixed text and exactly one retry (188.874138ms)
|
||||
✔ C2-06 RESULT JSON 34000 characters retries with a shorter valid answer (236.68605ms)
|
||||
✔ C2-06 RESULT JSON 70000 characters retries with a shorter valid answer (234.722358ms)
|
||||
✔ C2-06 RESULT two oversized JSON bodies use the length fixed text with citations required (193.029509ms)
|
||||
✔ C2-06 multibyte plain bodies also receive the one request-size retry (223.988818ms)
|
||||
✔ C2-06 two multibyte plain bodies use the length fallback (205.649902ms)
|
||||
✔ C2-06 RESULT a length refusal then citation refusal shares one retry and citation fallback (206.128944ms)
|
||||
✔ C2-06 fixed empty/failed replies defer unslop note until the next cited reply (295.722008ms)
|
||||
✔ C2-06 cited JSON above the old 32000 clip retains its citations and text (189.724785ms)
|
||||
✔ C2-06 a flagged cited answer keeps the note inside text and citations whole (192.59344ms)
|
||||
✔ C2-06 send_message typed tool runs every guard code before the valid RESULT (262.703841ms)
|
||||
✔ C2-06 outside library the old failed text, clip and unslop behavior hold (208.584382ms)
|
||||
✔ C2-06 outside library a refused send keeps the existing no-retry behavior (159.048751ms)
|
||||
✔ C2-06 the retry turn delivers citation-unavailable reason to the agent (190.203749ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (211.158287ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (113.877105ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1104.288151ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (1070.940549ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (568.174554ms)
|
||||
✔ claude: a second turn resumes the first turn's session (940.097062ms)
|
||||
✔ claude adapter: --restricted is always passed (6.108878ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (19.555633ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (6.450898ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (912.426727ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (8.255308ms)
|
||||
✔ claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after (1813.719749ms)
|
||||
✔ claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers (861.243377ms)
|
||||
✔ M2: the gate refuses write, edit and bash to every creative role on pi and claude-code (58.57362ms)
|
||||
✔ M6: no creative session gets raise_decision; the assistant and the chief of staff keep it (27.01682ms)
|
||||
✔ web: research and companion get the broker's typed web tools, and a harness-builtin source refuses web-source-not-allowed (34.87358ms)
|
||||
✔ persona: the block sits between the contract and ## This session, fenced, with the name and digest (10.05189ms)
|
||||
✔ persona: each unsafe or unpinned file refuses before the bundle directory is made (30.976676ms)
|
||||
✔ adapters pass the prompt file's absolute path; the persona and the prompt text never sit in argv (48.053085ms)
|
||||
✔ the pi adapter refuses a missing, unreadable or directory prompt file before pi starts (13.20329ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (4.524582ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.1263ms)
|
||||
✔ file tool paths must resolve inside the workspace (4.02957ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.586245ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.300474ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (5.091178ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (29.6566ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (2.651598ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (9.557314ms)
|
||||
✔ claude path fields per tool (1.146391ms)
|
||||
✔ glob patterns stay inside the workspace (1.220629ms)
|
||||
✔ a path that can't be checked is blocked (0.713439ms)
|
||||
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (358.466429ms)
|
||||
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (177.220281ms)
|
||||
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1810.093816ms)
|
||||
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (303.717292ms)
|
||||
✔ land_work sends one land line and resolves the host's started answer (3.694332ms)
|
||||
✔ initialize, ping and tools/list (73.055562ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (66.566638ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (60.558157ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (52.967178ms)
|
||||
✔ a missing argument is a usage error (35.244954ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (676.072372ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (532.762084ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (479.264215ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (479.008336ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (495.496517ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (458.873867ms)
|
||||
✔ pi: a missing extension refuses before any model call (6.358156ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (380.653357ms)
|
||||
✔ pi: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after (1353.463628ms)
|
||||
✔ pi: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers (748.010347ms)
|
||||
✔ raise_decision offers domain as technical or delivery only, optional, and says where a raise goes (23.00088ms)
|
||||
✔ raise_decision forwards domain to decision.raise, and a call without it sends none (100.23044ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (3.039179ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.355597ms)
|
||||
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.317095ms)
|
||||
✔ turnRequest says a user's message is a granted user's and never the owner's instruction (0.277911ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.227187ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.334368ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (257.255462ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (272.592717ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (239.571073ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (148.998515ms)
|
||||
✔ a decision the human resolves becomes a turn citing the decision and the answer (196.043584ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (103.372577ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (91.622163ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (336.226718ms)
|
||||
✔ end_run, organization_revision and change_organization reach the broker with the tool's arguments, and refusals keep their codes (429.635331ms)
|
||||
✔ set_name reaches the broker's name.set with the tool's arguments, and refusals keep their codes (182.337097ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1269.003752ms)
|
||||
✔ the bus's launch notice is one turn with no reply, and is not taken again (269.091562ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (122.399792ms)
|
||||
✔ founder credentials stop before the claim (20) (162.909348ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (165.231771ms)
|
||||
✔ the launch ending under a running session exits 22 (134.851141ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (234.508622ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (78.168597ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (194.303544ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (120.576748ms)
|
||||
✔ REQ-LIMIT-4 runner brackets its actual harness turn before the reply and read receipt (194.53726ms)
|
||||
✔ REQ-LIMIT-4 a runner at its limit stops at the end of its turn: RESULT and read first, then exit 22 and a limit-check revoke (168.261307ms)
|
||||
✔ landingRequest is the fixed template, with the decision and task ref and nothing else (0.147237ms)
|
||||
✔ each landing entry is one turn before any message, with the state block and the fixed request; it sends nothing and reads nothing (226.715761ms)
|
||||
✔ an empty landing takes no landing turn: the message is turn 1 (195.475495ms)
|
||||
✔ a run in a landing turn isn't released for a second held decision until its landing turns end and it has read its messages (596.516739ms)
|
||||
✔ a malformed landing exits 2 before the claim, with no turn (568.856934ms)
|
||||
✔ row 65 a session.json without a turnEnd of marker or exit, or a harnessEnv of strings, exits 2 before the claim (269.597797ms)
|
||||
✔ row 65 harnessEnv reaches the adapter, and an exit harness needs no turn marker (141.169295ms)
|
||||
✔ row 66 the adapter gets the run's unslop log, and a flagged reply's note rides that reply's RESULT, once (300.426996ms)
|
||||
✔ row 66 a turn with no reply holds its note for the next reply; a broken check is told once (345.164969ms)
|
||||
✔ row 66 unslopNotes keeps a note until sent(), reads only whole lines, and unslopCounts reads a log after the session (1.68702ms)
|
||||
✔ row 66 a clipped answer with both note parts stays under the bus's body limit, and the note under 600 (161.518813ms)
|
||||
✔ row 66 a refused send keeps the note: it rides the next RESULT the session sends (264.81696ms)
|
||||
✔ each repo role's prompt carries exactly its listed skills, each file as the bundle copy and the source (69.791416ms)
|
||||
✔ a skill's files go into the prompt in sorted path order, sub-directories included (9.238961ms)
|
||||
✔ a role with no skills gets no Skills section, no skills directory and an empty manifest list (11.39746ms)
|
||||
✔ an enabled skill the role doesn't list stays out of the prompt (8.706347ms)
|
||||
✔ the bundle copy outlives the source: deactivating a skill after the build leaves the run's copy (4.473957ms)
|
||||
✔ a listed skill that isn't enabled refuses skill-not-enabled, naming the activate command (9.541232ms)
|
||||
✔ a skill directory that isn't plain refuses: a symlinked skill, SKILL.md or sub-file, or a non-file entry (2.643822ms)
|
||||
✔ a skill file that can't go into the prompt refuses: a bad name, non-UTF-8 bytes, or the end marker (3.518581ms)
|
||||
✔ a role's skills over SKILL_BYTES together refuse, naming the skill that crossed it (10.193929ms)
|
||||
✔ each adapter passes the skills only in the prompt file, with skill discovery off (59.00799ms)
|
||||
✔ every role sees read_spec, record writer follows its grant, citation schemas stay strict (25.138022ms)
|
||||
✔ typed socket forwards read content/history and agent record writes, and cannot write a spec (172.529974ms)
|
||||
✔ the PM gets its task verbs and the reads, and no launch (11.972118ms)
|
||||
✔ send_message offers the BLOCK class, beside the classes it already had (3.837774ms)
|
||||
✔ the chief of staff gets launch, send_message, end_run, hire, fire, change_organization and set_name, and no task verbs (4.871494ms)
|
||||
✔ hire and fire send instance.hire and instance.fire under role.hire and role.fire, only for a holder of the grant (22.751702ms)
|
||||
✔ request_staff is the pm's alone: staff.request over message.send, roles and counts bounded (15.291362ms)
|
||||
✔ end_run sends role and reason, no decision; change_organization sends revision and the organization keys (2.689052ms)
|
||||
✔ set_name sends instance and name to name.set under instance.name, only for a holder of that grant (12.695737ms)
|
||||
✔ the organization keys change_organization takes are the broker's ORGANIZATION_KEYS and the settings' SETTINGS_KEYS (0.178758ms)
|
||||
✔ a role without role.end or organization.change gets none of the three tools, and each follows its own grant (12.115972ms)
|
||||
✔ no role's tools send instances or instances.set, and change_organization takes no instance write keys (10.7825ms)
|
||||
✔ the assistant gets send_message, the reads and the web tools (it holds the web grant), no launch (2.17462ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (3.939398ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.125698ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (2.258296ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (3.280204ms)
|
||||
✔ an action outside the instance's authority has no tool (1.994949ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (7.387015ms)
|
||||
✔ land_work is there only with land, after push_working, with the task and the decision (4.431191ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (16.610729ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (4.144909ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (3.985977ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (1.008682ms)
|
||||
✔ web_search's query maxLength is the web layer's SEARCH_MAX_QUERY_CHARS (4.810935ms)
|
||||
ℹ tests 172
|
||||
ℹ suites 0
|
||||
ℹ pass 172
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 12131.328093
|
||||
@@ -0,0 +1,153 @@
|
||||
npm notice run @mosaic/[email protected] test
|
||||
npm notice run node --test tests/*.test.mjs
|
||||
✔ C2-06 reply schema: free bodies, exact objects and safe refusal reasons (31.081491ms)
|
||||
✔ C2-06 citation setting and all four exact fixed texts (27.31137ms)
|
||||
✔ C2-06 default citation cap passes 20 and refuses 21, even with optional citations (144.940253ms)
|
||||
✔ C2-06 real Git citation scope, reachability, lines and normalized quotes (71.988432ms)
|
||||
✔ C2-06 playground cites pass but cannot state canon (42.141068ms)
|
||||
✔ C2-06 independent universes refuse; a hybrid and only its sources pass (69.134397ms)
|
||||
✔ C2-06 web cites require allowed definition, this session fetch and kept quote (17.090852ms)
|
||||
✔ C2-06 file origin is the path commit and trailer, while last follows served changes (51.160201ms)
|
||||
✔ C2-06 origin uses literal paths even when a filename contains a glob (35.841257ms)
|
||||
✔ C2-06 cited-commit guard reads exactly the C2-05a indexed file set with supporting scope (79.711572ms)
|
||||
✔ C2-06 reply limits fall back independently without coercion or inherited keys (0.78046ms)
|
||||
✔ the same commit, repo path and mapping return the same index, and only mapped files are in it (53.330474ms)
|
||||
✔ a different repo path, mapping or commit gets its own index (73.465944ms)
|
||||
✔ the cache holds at most eight indexes and evicts the least recently used (93.355098ms)
|
||||
✔ a failed read is not kept in the cache (19.15839ms)
|
||||
✔ a missing or unchecked mapping is refused before any read (18.591276ms)
|
||||
✔ a 3,000-file rebuild completes and its time is reported (3339.384537ms)
|
||||
ℹ 3,000-file index rebuild: 3093 ms
|
||||
✔ resolver step 1: a path from the repo root, with or without .md (2.003164ms)
|
||||
✔ resolver step 2: the same path with vaultPrefix/ stripped (0.323373ms)
|
||||
✔ resolver step 3: a path relative to the linking file, never above the repo root (0.278534ms)
|
||||
✔ resolver step 4: a base name that matches exactly one indexed file (0.322938ms)
|
||||
✔ a link into another of her repos resolves to nothing (0.213594ms)
|
||||
✔ wikilink forms, embeds, and links skipped inside code (1.24016ms)
|
||||
✔ only a body embed whose target has an extension other than .md, in any case, is an attachment (0.455416ms)
|
||||
✔ the fixture mapping checks and comes back frozen (2.830981ms)
|
||||
✔ C2-01c: a prefix inside or holding another entry's book refuses library-playground-other-universe (1.34922ms)
|
||||
✔ C2-01c: a prefix equal to a book folder, or holding its own, refuses library-mapping-invalid (0.74756ms)
|
||||
✔ C2-01c: universe-level prefixes lie outside every book, in a plain universe and in a hybrid with no books (1.203696ms)
|
||||
✔ C2-01c: a draft takes kind, name and playground only, and comes back frozen with books [] (1.782129ms)
|
||||
✔ C2-01d: supporting prefixes at book level, universe level, in a hybrid and in a draft, [] when absent (0.643444ms)
|
||||
✔ C2-01d: each supporting prefix rule refuses with its code (2.880363ms)
|
||||
✔ C2-01d: between the lists, a supporting prefix may equal its own playground, and a playground may hold its own supporting (0.978814ms)
|
||||
✔ C2-01d: editor is optional, kept when given, and refuses anything but a plain name and email (5.417269ms)
|
||||
✔ C2-01d: an accepted editor round-trips through a real git commit unchanged (22.81842ms)
|
||||
✔ no playground prefix holds another, and no book folder holds another (1.309845ms)
|
||||
✔ shape refusals: kind, remote, branch, unknown keys, types, manuscript, folder names (1.68793ms)
|
||||
✔ C2-03a sage-2021: user is optional, kept when given, and must be a plain account name (0.986251ms)
|
||||
✔ a hybrid universe is a shape the query code can take (1.4), with sources that must exist (1.600445ms)
|
||||
✔ a caller-supplied fail receives the code, as business file loading will pass it (C2-04) (0.396233ms)
|
||||
✔ scope comes from the path alone (design 1.2) (2.453311ms)
|
||||
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (82.599485ms)
|
||||
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (49.399699ms)
|
||||
✔ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (174.577156ms)
|
||||
✔ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (20.556264ms)
|
||||
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (34.414614ms)
|
||||
✔ C2-04 state: missing state is empty only while the served ref is absent (54.101069ms)
|
||||
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (57.832223ms)
|
||||
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (158.515834ms)
|
||||
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (57.609887ms)
|
||||
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (113.829438ms)
|
||||
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (124.255784ms)
|
||||
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (314.221744ms)
|
||||
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5038.833094ms)
|
||||
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (54.543028ms)
|
||||
✔ C2-04 lock: a missing flock binary becomes a handled refusal (49.73045ms)
|
||||
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (20.869629ms)
|
||||
✔ the query reply cap equals the harness reply cap (1.544541ms)
|
||||
✔ find is bound to its universe and refuses a call without one (70.281377ms)
|
||||
✔ every result carries its scope, and playground appears only when asked for (39.878964ms)
|
||||
✔ unmapped files never appear in any reply (35.123505ms)
|
||||
✔ a hybrid sees its sources canon, minus exclusions, and no playground (39.679182ms)
|
||||
✔ a hybrid gets a source file it can see, and not an excluded or foreign one (54.602693ms)
|
||||
✔ get lists visible links both ways and drops a target another universe cannot see (50.638492ms)
|
||||
✔ a long list pages by position, never cuts an item, and returns 50 per page (193.227492ms)
|
||||
✔ a page stays under the reply cap when items are large (91.885035ms)
|
||||
✔ a search line too long to fit alone comes back marked, with no excerpt, and the other hits still return (33.20163ms)
|
||||
✔ a find item that cannot fit alone is refused, not cut (46.447292ms)
|
||||
✔ get pages its lines, and the next line picks up where the page stopped (39.12526ms)
|
||||
✔ get refuses a file whose front matter alone is over the cap, and a line alone is over the cap (36.804238ms)
|
||||
✔ get pages the links of a hub note, every page carries the front matter, and the pages add up (1107.995321ms)
|
||||
✔ get rejects a bad links_from (34.377162ms)
|
||||
✔ search returns scoped line hits, case-insensitively, and refuses empty text (30.729667ms)
|
||||
✔ a manuscript call returns one chapter, at most 400 lines, and names its next line (47.791378ms)
|
||||
✔ a manuscript call without a chapter returns the first chapter in path order and names the next (26.366902ms)
|
||||
✔ a chapter name that matches two visible files is refused (25.448584ms)
|
||||
✔ a hybrid reads a source chapter through its declared sources only (39.969561ms)
|
||||
✔ universe info reports a hybrid sources, constraints and notes, and refuses an unknown universe (15.657923ms)
|
||||
✔ an exclusion hides what it names from a hybrid and from nothing else (39.404555ms)
|
||||
✔ PRD 1.14: a lookup that names a book leaves book-null files out (34.647951ms)
|
||||
✔ PRD 1.14: library_manuscript never reads a book-null file (32.637362ms)
|
||||
✔ PRD 1.14: a draft universe has no canon, so its lookups return files only with scope playground (32.338176ms)
|
||||
✔ PRD 1.14: library_universe on a draft returns its kind, name and prefixes (15.442774ms)
|
||||
✔ a hybrid gets a source playground file as null, while the source gets it with scope playground (42.674554ms)
|
||||
✔ the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed (63.033383ms)
|
||||
✔ R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed (95.339567ms)
|
||||
✔ R2: unmapped paths are reported and not indexed (31.564751ms)
|
||||
✔ R3: front matter not fully parsed stays raw, with the line where parsing stopped (36.896696ms)
|
||||
✔ front matter forms the reader accepts and refuses (1.596508ms)
|
||||
✔ R4: a link to no file, to more than one, or into another repo is reported and not followed (35.504393ms)
|
||||
✔ a block list link carries its item line, a scalar or flow link its key line, and problems follow (37.229678ms)
|
||||
✔ an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves (33.783255ms)
|
||||
✔ an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed (33.230236ms)
|
||||
✔ R5: a canon file that refers into the playground is reported, and the link still goes by path (29.709099ms)
|
||||
✔ R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources (30.970646ms)
|
||||
✔ R7: two paths that differ only in case are both left out (41.126208ms)
|
||||
✔ the reader reads the commit, never the working tree, and refuses a bad commit id (41.98282ms)
|
||||
✔ a bare mirror reads the same as the clone (53.445984ms)
|
||||
✔ renderItems snapshot: fact (1.786386ms)
|
||||
✔ renderItems snapshot: finding (0.299155ms)
|
||||
✔ renderItems snapshot: correction (0.259329ms)
|
||||
✔ renderItems snapshot: question (0.218182ms)
|
||||
✔ renderItems snapshot: idea (0.223821ms)
|
||||
✔ renderItems numbers several items and takes a parsed body as well as a string (1.090019ms)
|
||||
✔ without options a path cite has no badge and is never marked changed (0.280013ms)
|
||||
✔ renderItems snapshot: notice no-cited-answer (0.237711ms)
|
||||
✔ renderItems snapshot: notice no-answer (0.198705ms)
|
||||
✔ renderItems snapshot: notice turn-failed (0.337126ms)
|
||||
✔ a body without the reply shape shows a fixed line and none of its text (0.613863ms)
|
||||
✔ C2-01c: each level gives its universe, and its book or null (4.092943ms)
|
||||
✔ C2-01c: prefixes come first: in a book that holds a prefix, a file outside it is canon and one under it playground (0.607188ms)
|
||||
✔ C2-01c: a canon file linking into a draft gives R6, and into its own universe-level prefix gives R5 (65.023589ms)
|
||||
✔ C2-01d: each supporting level gives its universe, and its book or null (0.8244ms)
|
||||
✔ C2-01d: supporting comes before playground; a folder in both lists is supporting, one in the playground only stays playground (0.633065ms)
|
||||
✔ C2-01d: only .md files are supporting; a .canvas or .base under a supporting prefix is unmapped (0.517375ms)
|
||||
✔ C2-01d: a path passes at book level, universe level, in a draft and in a folder in both lists (2.312377ms)
|
||||
✔ C2-01d: a path of exactly 1024 bytes passes, and 1025 bytes made of multi-byte characters refuses (0.98988ms)
|
||||
✔ C2-01d: a path outside the universe's supporting folders refuses (0.763326ms)
|
||||
✔ C2-01d: malformed paths refuse by class, in order (0.710036ms)
|
||||
✔ C2-01d: a symlink or gitlink on the way, or at the path itself, refuses (0.383033ms)
|
||||
✔ C2-01d: a name that differs from a tree name only in case or Unicode form refuses, at the file or any folder (0.99427ms)
|
||||
✔ the survey prints folder names, counts, key sets and link-form counts, and never text (65.469359ms)
|
||||
✔ folder names print DEL, C1 and bidi controls, U+061C among them, as escapes (37.571162ms)
|
||||
✔ an embed of an attachment counts as an embed and as an attachment; a plain link to one does not (24.053267ms)
|
||||
✔ with --mapping the survey adds scope, resolver step and problem counts (51.551822ms)
|
||||
✔ C2-01d: with --mapping the survey counts supporting files (49.232972ms)
|
||||
✔ survey exit codes: usage 4, bad mapping 3, unreadable repo 2 (19.679523ms)
|
||||
✔ scripts/mosaic library survey reaches the survey (97.237921ms)
|
||||
✔ C2-03a argv: the fixed sequence, one https allow, no other protocol key (2.539459ms)
|
||||
✔ C2-03a argv: only an https URL with a plain host, no userinfo, and a plain user (1.147619ms)
|
||||
✔ C2-03a helper: answers only the exact prompts for the configured host and user (dewey-0765) (43.662638ms)
|
||||
✔ C2-03a first sync: served is the fetched tip, HEAD is served, the survey reads it, and the token stays in the slot (199.624214ms)
|
||||
✔ C2-03a fast-forward: served moves, problems are rebuilt, and a repeat changes only lastFetch (272.12795ms)
|
||||
✔ C2-03a Filbert N2: a read that took served before a sync still finds its objects after served moves (255.892244ms)
|
||||
✔ C2-03a overlap: a reader during a sync sees the old pair or the new one, never a mix (220.077465ms)
|
||||
✔ C2-03a diverged: a non-fast-forward keeps serving and holds until a human accepts the tip (511.485441ms)
|
||||
✔ C2-03a accept starts the record over, so a changed branch gets a fresh state (204.895458ms)
|
||||
✔ C2-03a fetch failure: a wrong token keeps serving and records the error, lastFetch unchanged (210.171392ms)
|
||||
✔ C2-03a a failed first fetch records the error with nothing served (57.52009ms)
|
||||
✔ C2-03a a 302 to another host fails the fetch and the other host sees nothing (68.211135ms)
|
||||
✔ C2-03a the slot: missing or unsafe refuses before git, and sync never opens it (65.636096ms)
|
||||
✔ C2-03a sage-2021: an entry with no user refuses library-remote-invalid and runs no git (34.004871ms)
|
||||
✔ C2-03a one sync per mirror: an overlapping call refuses library-sync-busy (129.134012ms)
|
||||
ℹ tests 142
|
||||
ℹ suites 0
|
||||
ℹ pass 142
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 6490.060843
|
||||
@@ -0,0 +1,2 @@
|
||||
1 match: replyLimitsFence(old, document, actor);
|
||||
1 match: replyLimitsFence(old, written, actor);
|
||||
@@ -0,0 +1 @@
|
||||
M5-hide-library-unavailable: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
@@ -0,0 +1,11 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5151.319291ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (37.53806ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (35.257703ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5310.30031
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5147.642347ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (32.256875ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.701926ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5295.499085
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,68 @@
|
||||
✖ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5108.905478ms)
|
||||
✖ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (40.518617ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (37.68422ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5261.169843
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/citation-availability.test.mjs:13:1
|
||||
✖ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5108.905478ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'citation-invalid'
|
||||
- 'citation-unavailable'
|
||||
^
|
||||
|
||||
at Object.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/bus/tests/citation-availability.test.mjs:29:14)
|
||||
at expectedException (node:assert:547:17)
|
||||
at expectsError (node:assert:672:21)
|
||||
at strict.rejects (node:assert:738:3)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/bus/tests/citation-availability.test.mjs:28:5
|
||||
at async withLibraryLock (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/library/src/mirror.mjs:171:18)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/bus/tests/citation-availability.test.mjs:27:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'citation-invalid',
|
||||
expected: 'citation-unavailable',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/citation-availability.test.mjs:40:1
|
||||
✖ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (40.518617ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
Comparison {
|
||||
+ code: 'citation-invalid'
|
||||
- code: 'citation-unavailable'
|
||||
}
|
||||
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/bus/tests/citation-availability.test.mjs:45:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: Error: citation-invalid: citation 1 (Book One/Characters/Mara Vell.md) isn't an indexed file at a commit reachable from the served commit
|
||||
at refuse (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/library/src/guard.mjs:24:42)
|
||||
at checkReply (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/library/src/guard.mjs:80:25)
|
||||
at async waitForActual (node:assert:615:5)
|
||||
at async strict.rejects (node:assert:738:25)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-pd3x2htc/packages/bus/tests/citation-availability.test.mjs:45:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7),
|
||||
expected: { code: 'citation-unavailable' },
|
||||
operator: 'rejects',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
M5-hide-library-unavailable: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
@@ -0,0 +1,5 @@
|
||||
M1-writer-fence: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M2-incoming-human: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M3-split-check-transaction: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M5-hide-library-unavailable: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M4-drop-client-reason: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (29.278355ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (22.040025ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (8.805323ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (8.829275ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (9.171416ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (10.423621ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 139.016448
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (27.397407ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (23.565905ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (8.312613ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (8.050944ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (8.746752ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (11.061782ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 137.787038
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,73 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (26.703775ms)
|
||||
✖ C2-06 M11 agents and tester cannot change settings, including by changing human (12.156717ms)
|
||||
✖ C2-06 M11 serialization cannot replace settings after the first fence (11.431272ms)
|
||||
✖ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (14.308135ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (9.043165ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (9.576698ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 3
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 131.91294
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/reply-limits.test.mjs:34:1
|
||||
✖ C2-06 M11 agents and tester cannot change settings, including by changing human (12.156717ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at refused (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:22:10)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:37:5)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'organization-reply-limits-human-only' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/reply-limits.test.mjs:43:1
|
||||
✖ C2-06 M11 serialization cannot replace settings after the first fence (11.431272ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at refused (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:22:10)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:45:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'organization-reply-limits-human-only' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/reply-limits.test.mjs:52:1
|
||||
✖ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (14.308135ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at refused (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:22:10)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-m6rnq3wd/packages/business/tests/reply-limits.test.mjs:54:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'organization-reply-limits-human-only' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (31.955623ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (22.832309ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (9.274775ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (8.69039ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (8.733202ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (10.441869ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 143.570268
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (29.948602ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (22.139351ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (9.054641ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (8.381635ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (9.081946ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (10.33004ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 141.370383
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,35 @@
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (28.334896ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (22.541196ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (8.304131ms)
|
||||
✖ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (11.712463ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (8.875517ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (9.882976ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 140.418146
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/reply-limits.test.mjs:52:1
|
||||
✖ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (11.712463ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at refused (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-_aqjby1c/packages/business/tests/reply-limits.test.mjs:22:10)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-_aqjby1c/packages/business/tests/reply-limits.test.mjs:54:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'organization-reply-limits-human-only' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (118.943843ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (94.790271ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (65.901789ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (41.908138ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (48.179621ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (41.94764ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (56.521104ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (88.283371ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (55.47311ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.321347ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (77.5591ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (24.639839ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (42.159103ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 866.146033
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (162.15169ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (151.110586ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (100.984805ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (81.389166ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (92.148118ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (78.197117ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (168.055107ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (271.850306ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (92.491602ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (61.315017ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (126.633466ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (61.436932ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (118.486786ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1649.634687
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,41 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (121.738375ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (94.199334ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (59.307121ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (41.252933ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (48.700382ms)
|
||||
✖ C2-06 check evidence and insert are atomic when storing evidence fails (42.434516ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (56.552861ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (88.426285ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (54.352185ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.064514ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (81.176348ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (25.324993ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (42.147552ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 12
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 872.02572
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/citation-guard.test.mjs:71:1
|
||||
✖ C2-06 check evidence and insert are atomic when storing evidence fails (42.434516ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-etkv3bqf/packages/bus/tests/citation-guard.test.mjs:76:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (107.311079ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (92.235404ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (59.017311ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (41.107066ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (47.696908ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (41.214066ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (54.665908ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (86.001777ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (55.168942ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.744539ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (79.212003ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (24.722314ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (41.933148ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 841.716764
|
||||
@@ -0,0 +1,21 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (110.781773ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (91.548051ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (57.119223ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (40.819326ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (47.776616ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (41.268825ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (53.375873ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (82.822565ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (54.131458ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.350984ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (80.819295ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (26.401011ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (41.116925ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 839.688386
|
||||
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,47 @@
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (109.280676ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (94.019544ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (58.84355ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (42.275234ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (48.954969ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (41.628985ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (54.367118ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (85.049228ms)
|
||||
✖ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (53.346085ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.218776ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (78.373306ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (24.622347ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (41.8227ms)
|
||||
ℹ tests 13
|
||||
ℹ suites 0
|
||||
ℹ pass 12
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 840.560306
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/citation-guard.test.mjs:116:1
|
||||
✖ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (53.346085ms)
|
||||
AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false
|
||||
|
||||
Caught error:
|
||||
|
||||
Error: citation-invalid
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-db3vgbd1/packages/bus/tests/citation-guard.test.mjs:121:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: Error: citation-invalid
|
||||
at Socket.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-db3vgbd1/packages/bus/src/client.mjs:45:17)
|
||||
at Socket.emit (node:events:514:20)
|
||||
at addChunk (node:internal/streams/readable:575:12)
|
||||
at readableAddChunkPushByteMode (node:internal/streams/readable:526:3)
|
||||
at Readable.push (node:internal/streams/readable:406:5)
|
||||
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23),
|
||||
operator: 'rejects',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5143.590096ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (33.560438ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.849587ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5290.343804
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5131.56715ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (32.560014ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.756895ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5271.350019
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
eeca81ab965f9e94f930ee5f51d16f019af6368bc6c3994895e204f7a54ec8d2 packages/bus/src/broker.mjs
|
||||
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||
36b05dd3119e5929211aa1cc5d45c5043f5a1c34567ddc7cac8f002835d44abe packages/bus/src/runtime.mjs
|
||||
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||
2e05074cd9d15d810103658af3858161cee2af78b0a2e1ba910222fab3e84401 packages/bus/tests/citation-guard.test.mjs
|
||||
2c7a2102e2e0244876e41b664f4f706baefe3375100b956655551db1230f5da3 packages/bus/tests/citation-helpers.mjs
|
||||
9768b6dc5ae078c5cc4f76473097287b74c5bc5853d39e1f57f153e646f861af packages/bus/tests/citation-runtime.test.mjs
|
||||
abaeac64f2c3bf8ba83c9a4488edd9105c4707a6564560c5333d5421445653af packages/business/src/business.mjs
|
||||
3a6044de5d1ca2cc35c5285b460c3e402b4274837e5d88b97f489797f6004903 packages/business/src/organization.mjs
|
||||
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||
a2bb98734bcc2556f8b2168f0faf8a89f7064c3f0aa49feb7d1975d596192f09 packages/cli/README.md
|
||||
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||
f710d9600a17f6e2989a289cfe84e58f161d86c7306605946ce05fa71b364eb2 packages/library/src/guard.mjs
|
||||
c0d630798914e300e461c247340533328e97119e54909c6a9406340e61fed8b7 packages/library/src/index.mjs
|
||||
98ef45ed61144aaea44d8aea1ac43082b6d340297e46846d01772da38728e530 packages/library/src/limits.mjs
|
||||
7b46f47b5e9d6b554b5d39bce2cd95c4610f5f35b8ab8fdc5a2864b3214be809 packages/library/src/reply-protocol.mjs
|
||||
406146ebbce3710f308243b684a1c17e7626c1b65a4468489364317e31537a48 packages/library/tests/guard.test.mjs
|
||||
39c8e6c8250a13fa43015fb2c407195e30ee642f729a9f6a644c38b4ea758a72 packages/seat/src/sandbox.mjs
|
||||
@@ -0,0 +1,70 @@
|
||||
✖ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5104.72293ms)
|
||||
✖ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (35.117198ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.286656ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5250.151124
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/citation-availability.test.mjs:13:1
|
||||
✖ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5104.72293ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'citation-invalid'
|
||||
- 'citation-unavailable'
|
||||
^
|
||||
|
||||
at Object.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/bus/tests/citation-availability.test.mjs:29:14)
|
||||
at expectedException (node:assert:547:17)
|
||||
at expectsError (node:assert:672:21)
|
||||
at strict.rejects (node:assert:738:3)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/bus/tests/citation-availability.test.mjs:28:5
|
||||
at async withLibraryLock (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/library/src/mirror.mjs:171:18)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/bus/tests/citation-availability.test.mjs:27:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'citation-invalid',
|
||||
expected: 'citation-unavailable',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/citation-availability.test.mjs:40:1
|
||||
✖ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (35.117198ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
Comparison {
|
||||
+ code: 'citation-invalid'
|
||||
- code: 'citation-unavailable'
|
||||
}
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/bus/tests/citation-availability.test.mjs:45:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: Error: citation-invalid: citation 1 (Book One/Characters/Mara Vell.md) isn't an indexed file at a commit reachable from the served commit
|
||||
at refuse (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/library/src/guard.mjs:24:42)
|
||||
at checkReply (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/library/src/guard.mjs:80:25)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async waitForActual (node:assert:615:5)
|
||||
at async strict.rejects (node:assert:738:25)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/tNoueZ/row217-mutant-xglaeqeb/packages/bus/tests/citation-availability.test.mjs:45:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7),
|
||||
expected: { code: 'citation-unavailable' },
|
||||
operator: 'rejects',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
M1-writer-fence: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M2-incoming-human: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M3-split-check-transaction: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M5-hide-library-unavailable: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
M4-drop-client-reason: killed (control green, mutant exit 1, hashes restored, restored green)
|
||||
@@ -0,0 +1,3 @@
|
||||
npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
|
||||
added 128 packages in 2s
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
added 2 packages in 205ms
|
||||
@@ -0,0 +1,9 @@
|
||||
✔ C2-06 runtime reads changed and unreadable reply limits without restarting (118.083931ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 227.817729
|
||||
@@ -0,0 +1,234 @@
|
||||
npm notice run @mosaic/[email protected] test
|
||||
npm notice run node --test tests/
|
||||
✔ C2-04: ensureCheckouts skips a library before a real clone and leaves no books path (66.766764ms)
|
||||
✔ a first launch clones the root at its branch, with the root's path as origin (47.521672ms)
|
||||
✔ the clone is not at its place until git clone has finished (31.999361ms)
|
||||
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (87.563022ms)
|
||||
✔ a clone ahead of the root keeps its commits and counts as current (53.447311ms)
|
||||
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
|
||||
✔ a modified file (53.258759ms)
|
||||
✔ an untracked file (52.286642ms)
|
||||
✔ a staged file (55.132154ms)
|
||||
✔ another branch (53.48074ms)
|
||||
✔ a detached HEAD (54.504119ms)
|
||||
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (269.989404ms)
|
||||
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (53.461206ms)
|
||||
▶ a clone kept before the branch check, or for a failed status, gets no fetch
|
||||
✔ not-a-clone (27.485728ms)
|
||||
✔ hooks-present (31.094616ms)
|
||||
✔ submodule (38.060187ms)
|
||||
✔ config-changed (36.195255ms)
|
||||
✔ root-unreadable (36.385271ms)
|
||||
✔ root-detached (39.627905ms)
|
||||
✔ status-failed, on the root's branch and clean (42.704327ms)
|
||||
✔ status-failed, dirty (44.881211ms)
|
||||
✔ a clone kept before the branch check, or for a failed status, gets no fetch (297.899967ms)
|
||||
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (47.755395ms)
|
||||
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (57.603139ms)
|
||||
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (89.466874ms)
|
||||
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
|
||||
✔ user.name (28.628791ms)
|
||||
✔ user.email (30.423895ms)
|
||||
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (59.627127ms)
|
||||
✔ a commit git can't read after the fast-forward is null (57.084762ms)
|
||||
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (21.081119ms)
|
||||
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||
✔ core.fsmonitor (39.453854ms)
|
||||
✔ core.hooksPath (34.616469ms)
|
||||
✔ a hook file (35.090442ms)
|
||||
✔ include.path (34.527401ms)
|
||||
✔ includeIf (35.13899ms)
|
||||
✔ a filter driver (34.195382ms)
|
||||
✔ origin moved (37.391948ms)
|
||||
✔ a second origin url (35.731817ms)
|
||||
✔ the origin url twice, both the root (33.999145ms)
|
||||
✔ an ext:: origin (31.652835ms)
|
||||
✔ a branch tracking another remote (33.3147ms)
|
||||
✔ a branch merging a tag (33.72502ms)
|
||||
✔ a fetch line onto local branches (34.126505ms)
|
||||
✔ no origin (32.973963ms)
|
||||
✔ a config file over the size cap (30.967833ms)
|
||||
✔ config a symlink to /dev/zero (31.51332ms)
|
||||
✔ the clone a symlink to the bait (32.976685ms)
|
||||
✔ the clone a file (39.528201ms)
|
||||
✔ the clone a plain directory (32.193931ms)
|
||||
✔ .git a gitdir: file (31.732811ms)
|
||||
✔ .git a symlink (36.499471ms)
|
||||
✔ .git/logs a symlink (34.327705ms)
|
||||
✔ .git/refs/heads a symlink (30.308688ms)
|
||||
✔ .git/objects a symlink (28.895664ms)
|
||||
✔ a reflog hard-linked to a file outside (28.33422ms)
|
||||
✔ HEAD a fifo (29.791372ms)
|
||||
✔ a commondir file (30.182546ms)
|
||||
✔ an alternates file (30.414863ms)
|
||||
✔ a gitlink staged, no .gitmodules (42.003032ms)
|
||||
✔ a gitlink and .gitmodules staged (47.484582ms)
|
||||
✔ a gitlink to a repository with its own gitlink (53.625899ms)
|
||||
✔ a gitlink and .gitmodules committed (45.90129ms)
|
||||
✔ a gitlink and submodule.active in config (43.005636ms)
|
||||
✔ a .git/modules with no gitlink (31.240083ms)
|
||||
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1201.693467ms)
|
||||
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||
✔ missing (9.543898ms)
|
||||
✔ not a repository (9.15995ms)
|
||||
✔ a plain directory inside another repository (13.291966ms)
|
||||
✔ unreadable (10.409599ms)
|
||||
✔ on no branch (11.130486ms)
|
||||
✔ missing an object (14.936384ms)
|
||||
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (69.273079ms)
|
||||
✔ a clone whose root is on no branch, or gone, is kept and named (28.705151ms)
|
||||
✔ no credential or forge remote of the root reaches a clone (46.990023ms)
|
||||
✔ every project in the business file gets its own clone at its own branch (32.655381ms)
|
||||
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.003804ms)
|
||||
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1016.843724ms)
|
||||
✔ a timeout signals git's process group, so a process git started dies with it (515.051887ms)
|
||||
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (514.556145ms)
|
||||
✔ the budget is the setting's whole number of bytes from 1 MiB to 1 GiB, and the default for anything else (1.567727ms)
|
||||
✔ a stream past the default budget is not-collected with a lower bound, and a later collection catches up (65.101994ms)
|
||||
✔ a budget smaller than the streams counts over several collections, once each, and is known when it catches up (2.599724ms)
|
||||
✔ a line split across reads counts once (9.33078ms)
|
||||
✔ Claude history from before the ledger makes the first Pi collection harness-unsupported, for good (1.440992ms)
|
||||
✔ an empty claude-projects/ is no Claude history, and one filled after the ledger exists isn't checked (1.428192ms)
|
||||
✔ a claude-projects/ that can't be read records nothing, and the next collection checks again (1.330216ms)
|
||||
✔ the stream cap is the setting's whole number from 32 to 65536, and the default for anything else (0.16791ms)
|
||||
✔ more streams than the cap is not-collected, with cursors only for the streams opened, and a larger cap catches up (2.344595ms)
|
||||
✔ the default cap opens STREAMS streams and writes no more cursor lines than that (52.65919ms)
|
||||
✔ row 307 the known ledger row 270 wrote reads the same count (4.720302ms)
|
||||
✔ row 307 after the known ledger, one collection through pi's module answers the next count (3.122956ms)
|
||||
✔ row 307 the store-incomplete ledger row 270 wrote reads the same count (1.172174ms)
|
||||
✔ row 307 after the store-incomplete ledger, one collection through pi's module answers the next count (1.473641ms)
|
||||
✔ row 307 the store-unreadable ledger row 270 wrote reads the same count (1.233218ms)
|
||||
✔ row 307 after the store-unreadable ledger, one collection through pi's module answers the next count (1.478165ms)
|
||||
✔ row 307 the not-collected ledger row 270 wrote reads the same count (1.059533ms)
|
||||
✔ row 307 after the not-collected ledger, one collection through pi's module answers the next count (2.381792ms)
|
||||
✔ row 307 the claude-code ledger row 270 wrote reads the same count (1.16785ms)
|
||||
✔ row 307 after the claude-code ledger, one collection through pi's module answers the next count (1.422304ms)
|
||||
✔ row 307 the claude-history ledger row 270 wrote reads the same count (2.943093ms)
|
||||
✔ row 307 after the claude-history ledger, one collection through pi's module answers the next count (1.5333ms)
|
||||
✔ row 307 the torn ledger row 270 wrote reads the same count (1.006203ms)
|
||||
✔ row 307 after the torn ledger, one collection through pi's module answers the next count (2.284545ms)
|
||||
✔ row 307 core refuses an answer outside the contract and appends nothing (8.468181ms)
|
||||
✔ row 307 the module gets the store's two native directories, a copy of the cursors, the budget, the cap and first (2.320411ms)
|
||||
✔ an empty store, a store with no compaction and a failed start count 0, known (7.138038ms)
|
||||
✔ one and three completed compactions (2.441636ms)
|
||||
✔ a compaction copied into a branched stream counts once, under the stream it was first seen in (3.056ms)
|
||||
✔ one collection over two streams holding the same copy counts it once and writes it once (1.831681ms)
|
||||
✔ the same entry id at another time in another stream is another compaction (1.582065ms)
|
||||
✔ resume: each collection reads past its cursor and writes a key once (2.114301ms)
|
||||
✔ a partial trailing line waits for its newline (1.624959ms)
|
||||
✔ a missing ledger is not-collected and an unreadable one store-unreadable, never 0 (1.322325ms)
|
||||
✔ a symlinked ledger is store-unreadable and is not written through (1.293154ms)
|
||||
✔ a torn last ledger line makes the count unknown, a lower bound, and stays so (2.167108ms)
|
||||
✔ a bad ledger line in the middle makes the count unknown (3.331232ms)
|
||||
▶ a stream that shrank, changed under its cursor or went away is store-incomplete for good
|
||||
✔ shrink (2.038847ms)
|
||||
✔ rewrite (1.477185ms)
|
||||
✔ remove (1.974024ms)
|
||||
✔ a stream that shrank, changed under its cursor or went away is store-incomplete for good (6.254361ms)
|
||||
✔ a stream that isn't a regular file or a Pi session is store-unreadable, and that passes (1.252704ms)
|
||||
✔ a first line that isn't a session header is store-unreadable (1.120253ms)
|
||||
✔ a Claude run is harness-unsupported, and stays so after a Pi run (1.273691ms)
|
||||
✔ storeDir is the store sessionStore makes, without making it (1.538256ms)
|
||||
✔ the ledger is under no sandbox mount (5.456112ms)
|
||||
✔ rootHits: a top-level file, a new top-level directory, src/ and v1/ are the root; paths under known directories aren't (1.861197ms)
|
||||
✔ gateEnv keeps the named variables that are strings and drops everything else (0.310846ms)
|
||||
✔ isMosaicStack: a checkout whose HEAD descends from the root commit and has node_modules; nothing else (76.743834ms)
|
||||
✔ gateSteps: each package with tests but queue in name order, then each scripts/test-*.sh (2.377262ms)
|
||||
✔ runGate: every step passes, each with its own log and a summary line, under the environment it was given (211.160741ms)
|
||||
✔ runGate: the first failing step stops the gate and names its log (269.569796ms)
|
||||
✔ runGate: an abort stops the running step and everything in its group, and runs nothing after it (185.986993ms)
|
||||
✔ the CLI: root-file prints the hits and exits 2, or exits 0; gate exits 0, or 2 with the step and its log; anything else is usage (661.443234ms)
|
||||
✔ T3: publish pushes the work commit by oid to <user>/<branch> on the fork, with the token only through the helper (318.365034ms)
|
||||
✔ T3: the token is never in git's argv, the fork URL included; it reaches git on fd 3 only (157.63501ms)
|
||||
✔ T3: a moved fork branch refuses non-fast-forward; no + and no force (173.077055ms)
|
||||
✔ T3: D9a: an empty fork and an unrelated fork refuse remote-unrelated; a missing fork refuses fork-unavailable (379.595919ms)
|
||||
✔ T3: a fork past main refuses remote-unrelated until root catches up (152.254688ms)
|
||||
✔ T3: a wrong token refuses fork-unavailable; git's stderr isn't in the refusal (102.972548ms)
|
||||
✔ T3: a 302 is not followed: fork-unavailable, and the target sees no request (113.352932ms)
|
||||
✔ T3: the helper answers only the fork's host and port: a fork on another port gets no token (95.099891ms)
|
||||
✔ T3: a fork that echoes the token refuses credential-leak (131.28193ms)
|
||||
✔ T3: no tls parameter, no trust: the scratch CA reaches git only through it (91.935601ms)
|
||||
✔ D9 steps 3 and 4: ref-moved and identity-mismatch refuse before any request (143.070058ms)
|
||||
✔ publishRemote refuses a fork, user or destination that isn't shell- and ref-safe before any git (88.646013ms)
|
||||
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (7.032234ms)
|
||||
✔ sandboxProblem names a missing bwrap and a bwrap that fails (15.78888ms)
|
||||
✔ every bind says why; the bundle and session file stay read-only over the run directory (2.196421ms)
|
||||
✔ row 65 a harness module's mounts bind read-only after the adapter, each with the module's reason (2.143393ms)
|
||||
✔ REQ-THREAD-2 a thread store binds its session directory and Claude transcripts, and nothing more (1.644658ms)
|
||||
✔ the host's git socket is bound only when given, read-only and before the writable binds (1.090126ms)
|
||||
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.690862ms)
|
||||
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (2106.854649ms)
|
||||
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2924.249794ms)
|
||||
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (300.518853ms)
|
||||
✔ resolveSeat: by name under --repo resolves the repo layout (2.419588ms)
|
||||
✔ resolveSeat: by path resolves the fleet layout (0.765582ms)
|
||||
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.626754ms)
|
||||
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (1.236572ms)
|
||||
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.593741ms)
|
||||
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (2.34318ms)
|
||||
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (2.203796ms)
|
||||
✔ CLI launch: registers, execs the fake launch script, and passes args through (66.404825ms)
|
||||
✔ CLI launch: --harness lands in the record (50.392752ms)
|
||||
✔ CLI launch: the launch script's own exit code passes through (46.659659ms)
|
||||
✔ CLI launch: relaunching a seat rewrites the one registration record (89.26575ms)
|
||||
✔ CLI launch: omitting --task records an empty string, not null (39.318048ms)
|
||||
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (96.331713ms)
|
||||
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (164.27632ms)
|
||||
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.422092ms)
|
||||
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.590318ms)
|
||||
✔ validateRegistration/readRegistration: taskSetBy is optional; a record without it (written before #1511) still loads unchanged; an invalid one is refused; the version does not change (0.734057ms)
|
||||
✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (0.906133ms)
|
||||
✔ CLI seat task: --by beats MOSAIC_AGENT_NAME, the environment beats nothing, empty environment is unknown, invalid --by or environment refuses with exit 4 and leaves the record byte for byte (330.90649ms)
|
||||
✔ REQ-THREAD-2 sessionStore: a legacy session keeps the thread's store, any other gets sessions/<s>/, each segment checked first (5.360486ms)
|
||||
✔ REQ-THREAD-2 sessionStore refuses legacy-store-unmapped for a thread whose old store no legacy session maps (1.608175ms)
|
||||
✔ REQ-THREAD-2 sessionWorkspace: private directories per session, unsafe segments and overlaps refused (3.523108ms)
|
||||
✔ REQ-THREAD-2 lockSessionWorkspace: one writer; a live host or run holds it, a stale lock is taken over, an unreadable one holds (15.189866ms)
|
||||
✔ REQ-THREAD-2 sessions.json v2: entries carry session and workspace, running() keys on the session, a v1 file still reads (1.889362ms)
|
||||
✔ REQ-LIMIT-1: family substring matching is no longer exported (1.378459ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (1.063207ms)
|
||||
✔ ZAI_API_KEY passes the session allowlist, and ZAI_TOKEN does not (0.239826ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.405172ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (2.727102ms)
|
||||
✔ session file and launch log: 0600, the session file written once (2.111664ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.220582ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (2.216711ms)
|
||||
✔ a read-only Registry never writes sessions.json, refuses add() and leaves the file on remove() (2.154331ms)
|
||||
✔ REQ-THREAD-1 Registry.running keys a thread run by business, instance and thread (1.027065ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 threadStore checks both segments before the join and refuses an unsafe directory (2.628544ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.552738ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (293.088316ms)
|
||||
✔ workRef and validBranch take only what a ref name can hold (2.280976ms)
|
||||
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (187.443563ms)
|
||||
✔ a rewritten branch refuses non-fast-forward and the work ref stays (124.192671ms)
|
||||
✔ a branch with no history in common with main refuses unrelated-history (60.579235ms)
|
||||
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (51.079109ms)
|
||||
✔ the root is fetched again before each push and main follows it, so a clone of a newer root pushes (129.47937ms)
|
||||
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (78.854404ms)
|
||||
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (86.468293ms)
|
||||
✔ only a v2 bundle with the named branch is read (64.914471ms)
|
||||
✔ a branch named main, or named for another task, moves only this task's work ref (102.371349ms)
|
||||
✔ a record that throws moves the work ref back, or removes a new one (141.771502ms)
|
||||
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (362.776758ms)
|
||||
✔ a root that can't be read refuses root-unreadable (28.853641ms)
|
||||
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2132.860162ms)
|
||||
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1025.936026ms)
|
||||
✔ a working repository path that isn't a directory refuses (23.030329ms)
|
||||
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (120.899663ms)
|
||||
✔ review of a project with no working repository refuses unknown-commit (14.031146ms)
|
||||
✔ the runner's git tools hold the host's limits and branch rule (1.219295ms)
|
||||
✔ a reseed moves root but leaves a landed main, and main forwards once root contains it (184.469774ms)
|
||||
✔ after a landing, a push built on the root still lands and main stays where the landing put it (126.530409ms)
|
||||
✔ once the root shares no history with a landed main, a push is checked against main, not the root (179.004093ms)
|
||||
✔ a landing squashes the reviewed diff onto main as the bus host and moves main only at finish (105.224989ms)
|
||||
✔ a landing refuses ref-moved, base-conflict and root-file before it makes a commit (308.132399ms)
|
||||
✔ a landing whose applied diff isn't the reviewed one refuses patch-drift and leaves no worktree (121.415385ms)
|
||||
✔ finish refuses base-moved when main moved, and a record that throws moves main back (242.643351ms)
|
||||
✔ halted push receipt rolls back only its unrecorded ref and cleans temporary refs (198.696687ms)
|
||||
ℹ tests 218
|
||||
ℹ suites 0
|
||||
ℹ pass 218
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 6318.071226
|
||||
@@ -0,0 +1,202 @@
|
||||
{
|
||||
"base": "e0c6142f3",
|
||||
"testedRevision": "2d274a5b6c35c3e74b07206ffd724a6579bb149d",
|
||||
"tmpdir": "/mnt/storage/scratch/tmp/tNoueZ",
|
||||
"wholeSuites": {
|
||||
"library": {
|
||||
"tests": 142,
|
||||
"pass": 142,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"business": {
|
||||
"tests": 270,
|
||||
"pass": 270,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"bus": {
|
||||
"tests": 974,
|
||||
"pass": 974,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"cli": {
|
||||
"tests": 988,
|
||||
"pass": 988,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"harness": {
|
||||
"tests": 172,
|
||||
"pass": 172,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"seat": {
|
||||
"tests": 218,
|
||||
"pass": 218,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
},
|
||||
"touched": {
|
||||
"touched-first": {
|
||||
"tests": 57,
|
||||
"pass": 56,
|
||||
"fail": 1,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"touched-rerun": {
|
||||
"tests": 57,
|
||||
"pass": 57,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"availability-green": {
|
||||
"tests": 3,
|
||||
"pass": 3,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"runtime-fixture-retry": {
|
||||
"tests": 1,
|
||||
"pass": 1,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
},
|
||||
"mutants": {
|
||||
"M1-writer-fence": {
|
||||
"control": {
|
||||
"tests": 6,
|
||||
"pass": 6,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"negative": {
|
||||
"tests": 6,
|
||||
"pass": 3,
|
||||
"fail": 3,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"restored": {
|
||||
"tests": 6,
|
||||
"pass": 6,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"productHashesRestored": true
|
||||
},
|
||||
"M2-incoming-human": {
|
||||
"control": {
|
||||
"tests": 6,
|
||||
"pass": 6,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"negative": {
|
||||
"tests": 6,
|
||||
"pass": 5,
|
||||
"fail": 1,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"restored": {
|
||||
"tests": 6,
|
||||
"pass": 6,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"productHashesRestored": true
|
||||
},
|
||||
"M3-split-check-transaction": {
|
||||
"control": {
|
||||
"tests": 13,
|
||||
"pass": 13,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"negative": {
|
||||
"tests": 13,
|
||||
"pass": 12,
|
||||
"fail": 1,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"restored": {
|
||||
"tests": 13,
|
||||
"pass": 13,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"productHashesRestored": true
|
||||
},
|
||||
"M4-drop-client-reason": {
|
||||
"control": {
|
||||
"tests": 13,
|
||||
"pass": 13,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"negative": {
|
||||
"tests": 13,
|
||||
"pass": 12,
|
||||
"fail": 1,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"restored": {
|
||||
"tests": 13,
|
||||
"pass": 13,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"productHashesRestored": true
|
||||
},
|
||||
"M5-hide-library-unavailable": {
|
||||
"control": {
|
||||
"tests": 3,
|
||||
"pass": 3,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"negative": {
|
||||
"tests": 3,
|
||||
"pass": 1,
|
||||
"fail": 2,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"restored": {
|
||||
"tests": 3,
|
||||
"pass": 3,
|
||||
"fail": 0,
|
||||
"cancelled": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"productHashesRestored": true
|
||||
}
|
||||
},
|
||||
"totalWholeSuiteTests": 2764
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
library: 142/142 pass; fail=0 skipped=0 cancelled=0
|
||||
business: 270/270 pass; fail=0 skipped=0 cancelled=0
|
||||
bus: 974/974 pass; fail=0 skipped=0 cancelled=0
|
||||
cli: 988/988 pass; fail=0 skipped=0 cancelled=0
|
||||
harness: 172/172 pass; fail=0 skipped=0 cancelled=0
|
||||
seat: 218/218 pass; fail=0 skipped=0 cancelled=0
|
||||
Touched rerun 57/57; first run 56/57, new runtime fixture corrected.
|
||||
M1-M5 killed; each control/restored run green, hashes restored.
|
||||
No old test failed or was edited. scripts/test-*.sh left to Sage at landing.
|
||||
@@ -0,0 +1,80 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5129.213097ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (32.312603ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.769445ms)
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (104.132156ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (90.030314ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (57.202238ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (41.82067ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (47.52786ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (45.049883ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (54.4217ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (86.893454ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (55.019525ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.383322ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (76.778515ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (24.114008ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (47.161109ms)
|
||||
✖ C2-06 runtime reads changed and unreadable reply limits without restarting (78.402278ms)
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (26.436831ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (21.452407ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (8.669666ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (8.204041ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (9.079346ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (10.338142ms)
|
||||
✔ C2-06 CLI replies reads revision and sends only the JSON settings to the human verb (1.898838ms)
|
||||
✔ C2-06 CLI replies requires a file and refuses tester writes before a bus call (0.465972ms)
|
||||
✔ C2-06 RESULT reply-invalid retries once, delivers only the valid answer and names reason (222.089187ms)
|
||||
✔ C2-06 RESULT citation-missing retries once, delivers only the valid answer and names reason (200.104611ms)
|
||||
✔ C2-06 RESULT citations-over-limit retries once, delivers only the valid answer and names reason (197.834891ms)
|
||||
✔ C2-06 RESULT citation-invalid retries once, delivers only the valid answer and names reason (210.156515ms)
|
||||
✔ C2-06 RESULT playground-cited-as-canon retries once, delivers only the valid answer and names reason (213.835593ms)
|
||||
✔ C2-06 RESULT citation-cross-universe retries once, delivers only the valid answer and names reason (209.912186ms)
|
||||
✔ C2-06 RESULT citation-unfetched retries once, delivers only the valid answer and names reason (198.705684ms)
|
||||
✔ C2-06 RESULT two guard refusals use the citation fixed text and exactly one retry (182.386586ms)
|
||||
✔ C2-06 RESULT JSON 34000 characters retries with a shorter valid answer (213.370025ms)
|
||||
✔ C2-06 RESULT JSON 70000 characters retries with a shorter valid answer (199.782915ms)
|
||||
✔ C2-06 RESULT two oversized JSON bodies use the length fixed text with citations required (184.820165ms)
|
||||
✔ C2-06 multibyte plain bodies also receive the one request-size retry (201.829013ms)
|
||||
✔ C2-06 two multibyte plain bodies use the length fallback (182.03426ms)
|
||||
✔ C2-06 RESULT a length refusal then citation refusal shares one retry and citation fallback (183.540139ms)
|
||||
✔ C2-06 fixed empty/failed replies defer unslop note until the next cited reply (274.786836ms)
|
||||
✔ C2-06 cited JSON above the old 32000 clip retains its citations and text (167.141798ms)
|
||||
✔ C2-06 a flagged cited answer keeps the note inside text and citations whole (168.83631ms)
|
||||
✔ C2-06 send_message typed tool runs every guard code before the valid RESULT (230.565935ms)
|
||||
✔ C2-06 outside library the old failed text, clip and unslop behavior hold (197.472747ms)
|
||||
✔ C2-06 outside library a refused send keeps the existing no-retry behavior (151.296315ms)
|
||||
✔ C2-06 the retry turn delivers citation-unavailable reason to the agent (184.635398ms)
|
||||
✔ C2-06 reply schema: free bodies, exact objects and safe refusal reasons (14.759264ms)
|
||||
✔ C2-06 citation setting and all four exact fixed texts (9.899391ms)
|
||||
✔ C2-06 default citation cap passes 20 and refuses 21, even with optional citations (76.751112ms)
|
||||
✔ C2-06 real Git citation scope, reachability, lines and normalized quotes (41.360225ms)
|
||||
✔ C2-06 playground cites pass but cannot state canon (23.671961ms)
|
||||
✔ C2-06 independent universes refuse; a hybrid and only its sources pass (41.572641ms)
|
||||
✔ C2-06 web cites require allowed definition, this session fetch and kept quote (10.468249ms)
|
||||
✔ C2-06 file origin is the path commit and trailer, while last follows served changes (31.989388ms)
|
||||
✔ C2-06 origin uses literal paths even when a filename contains a glob (20.084358ms)
|
||||
✔ C2-06 cited-commit guard reads exactly the C2-05a indexed file set with supporting scope (44.687642ms)
|
||||
✔ C2-06 reply limits fall back independently without coercion or inherited keys (0.462948ms)
|
||||
ℹ tests 57
|
||||
ℹ suites 0
|
||||
ℹ pass 56
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 11101.06711
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/citation-runtime.test.mjs:12:1
|
||||
✖ C2-06 runtime reads changed and unreadable reply limits without restarting (78.402278ms)
|
||||
Error: invalid-authority
|
||||
at fail (file:///home/jwoltje/.t3/worktrees/mosaic-stack/beakley-row217/packages/bus/src/broker.mjs:215:9)
|
||||
at checkBusiness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/beakley-row217/packages/bus/src/broker.mjs:366:7)
|
||||
at new Broker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/beakley-row217/packages/bus/src/broker.mjs:537:63)
|
||||
at startBroker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/beakley-row217/packages/bus/src/runtime.mjs:274:14)
|
||||
at async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/beakley-row217/packages/bus/tests/citation-runtime.test.mjs:18:19)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
code: 'invalid-authority'
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
✔ C2-06 held library lock gives the agent citation-unavailable and its retry reason (5131.834326ms)
|
||||
✔ C2-06 malformed sync state is availability failure, never a fabricated invalid cite (31.609704ms)
|
||||
✔ C2-06 no mirror or freshly initialized empty mirror still means no served citation (31.510292ms)
|
||||
✔ C2-06 coder send_message guard covers every table refusal; inserts nothing (102.606635ms)
|
||||
✔ C2-06 research send_message guard covers every table refusal; inserts nothing (91.30209ms)
|
||||
✔ C2-06 stored check carries the file author trailer at an unrelated cited tip (55.686451ms)
|
||||
✔ C2-06 supporting cite cannot state canon, including a folder also marked playground (42.44166ms)
|
||||
✔ C2-06 supporting file stores supporting scope and its real agent trailer (48.386414ms)
|
||||
✔ C2-06 check evidence and insert are atomic when storing evidence fails (39.194827ms)
|
||||
✔ C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts (54.284625ms)
|
||||
✔ C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect (85.004961ms)
|
||||
✔ C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical (54.117068ms)
|
||||
✔ C2-06 non-library businesses retain role.claim shape and unguarded free sends (26.131034ms)
|
||||
✔ C2-06 thread messages carry file/last evidence with one read per distinct path (76.437567ms)
|
||||
✔ C2-06 guarded request snapshot cannot change between check and insert (23.908928ms)
|
||||
✔ C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged (39.656735ms)
|
||||
✔ C2-06 runtime reads changed and unreadable reply limits without restarting (113.293022ms)
|
||||
✔ C2-06 M11 human replacement works; omitted settings return to defaults (36.305063ms)
|
||||
✔ C2-06 M11 agents and tester cannot change settings, including by changing human (21.839826ms)
|
||||
✔ C2-06 M11 serialization cannot replace settings after the first fence (8.253422ms)
|
||||
✔ C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits (7.674495ms)
|
||||
✔ C2-06 invalid setter values preserve the business and changes.jsonl (8.65299ms)
|
||||
✔ C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files (9.800617ms)
|
||||
✔ C2-06 CLI replies reads revision and sends only the JSON settings to the human verb (1.78485ms)
|
||||
✔ C2-06 CLI replies requires a file and refuses tester writes before a bus call (0.520156ms)
|
||||
✔ C2-06 RESULT reply-invalid retries once, delivers only the valid answer and names reason (220.792912ms)
|
||||
✔ C2-06 RESULT citation-missing retries once, delivers only the valid answer and names reason (195.400791ms)
|
||||
✔ C2-06 RESULT citations-over-limit retries once, delivers only the valid answer and names reason (196.181772ms)
|
||||
✔ C2-06 RESULT citation-invalid retries once, delivers only the valid answer and names reason (211.797981ms)
|
||||
✔ C2-06 RESULT playground-cited-as-canon retries once, delivers only the valid answer and names reason (212.360651ms)
|
||||
✔ C2-06 RESULT citation-cross-universe retries once, delivers only the valid answer and names reason (212.667005ms)
|
||||
✔ C2-06 RESULT citation-unfetched retries once, delivers only the valid answer and names reason (184.261348ms)
|
||||
✔ C2-06 RESULT two guard refusals use the citation fixed text and exactly one retry (184.957084ms)
|
||||
✔ C2-06 RESULT JSON 34000 characters retries with a shorter valid answer (216.343048ms)
|
||||
✔ C2-06 RESULT JSON 70000 characters retries with a shorter valid answer (198.338992ms)
|
||||
✔ C2-06 RESULT two oversized JSON bodies use the length fixed text with citations required (183.309434ms)
|
||||
✔ C2-06 multibyte plain bodies also receive the one request-size retry (198.411283ms)
|
||||
✔ C2-06 two multibyte plain bodies use the length fallback (182.244769ms)
|
||||
✔ C2-06 RESULT a length refusal then citation refusal shares one retry and citation fallback (184.948978ms)
|
||||
✔ C2-06 fixed empty/failed replies defer unslop note until the next cited reply (294.575418ms)
|
||||
✔ C2-06 cited JSON above the old 32000 clip retains its citations and text (169.454956ms)
|
||||
✔ C2-06 a flagged cited answer keeps the note inside text and citations whole (164.824669ms)
|
||||
✔ C2-06 send_message typed tool runs every guard code before the valid RESULT (213.365137ms)
|
||||
✔ C2-06 outside library the old failed text, clip and unslop behavior hold (198.225522ms)
|
||||
✔ C2-06 outside library a refused send keeps the existing no-retry behavior (155.955999ms)
|
||||
✔ C2-06 the retry turn delivers citation-unavailable reason to the agent (188.198987ms)
|
||||
✔ C2-06 reply schema: free bodies, exact objects and safe refusal reasons (17.723832ms)
|
||||
✔ C2-06 citation setting and all four exact fixed texts (11.590649ms)
|
||||
✔ C2-06 default citation cap passes 20 and refuses 21, even with optional citations (79.713842ms)
|
||||
✔ C2-06 real Git citation scope, reachability, lines and normalized quotes (41.208361ms)
|
||||
✔ C2-06 playground cites pass but cannot state canon (24.250695ms)
|
||||
✔ C2-06 independent universes refuse; a hybrid and only its sources pass (42.707909ms)
|
||||
✔ C2-06 web cites require allowed definition, this session fetch and kept quote (10.901286ms)
|
||||
✔ C2-06 file origin is the path commit and trailer, while last follows served changes (37.369269ms)
|
||||
✔ C2-06 origin uses literal paths even when a filename contains a glob (20.592546ms)
|
||||
✔ C2-06 cited-commit guard reads exactly the C2-05a indexed file set with supporting scope (51.682837ms)
|
||||
✔ C2-06 reply limits fall back independently without coercion or inherited keys (0.499884ms)
|
||||
ℹ tests 57
|
||||
ℹ suites 0
|
||||
ℹ pass 57
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 11134.473726
|
||||
@@ -0,0 +1,124 @@
|
||||
# Row 217 (Case 2 C2-06): build notes
|
||||
|
||||
Brief: docs/plans/2026-10-20_case2-rows.md, section C2-06, at base 45dca69f6.
|
||||
Builder: Launchpad until 2026-10-23 19:09Z. Then Mos moved coding to Codex seats (mos-1500, Q245) and
|
||||
the row went to Beakley on beakley/row217.
|
||||
|
||||
## State at handoff
|
||||
|
||||
All code in this commit is UNTESTED. No node, no test and no mutant has run on it. No test file is written.
|
||||
`packages/harness/src/runner.mjs` is not started.
|
||||
|
||||
Done, untested:
|
||||
- `packages/library/src/limits.mjs` (new, pure): `REPLY_LIMITS`, `resolveReplyLimits(read)` gives
|
||||
`{requireCitation, citationsPerReply, fallback, unreadable}`, `checkReplyLimits(value)` gives a clone or null.
|
||||
An unreadable business file forces `requireCitation` true. A key falls back to its default when
|
||||
`replyLimits` is not an object or the key holds an invalid value.
|
||||
- `packages/library/src/guard.mjs` (new): `GUARD_CODES` in design 5.2's table order, `FIXED_TEXTS`,
|
||||
`WEB_CITE_DEFINITIONS`, `REASON_MAX` (300), `GuardError`, `parseReply`, `checkReply`, `libraryReader`,
|
||||
`lastCommits`, `guardReply`.
|
||||
- `packages/library/src/index.mjs`: exports for both.
|
||||
- `packages/business/src/business.mjs`: `replyLimits` is a known top key. The validated output doesn't carry it.
|
||||
- `packages/business/src/organization.mjs`: the M11 fence `replyLimitsFence(old, document, actor)` compares
|
||||
against `old.human`, refuses the tester and any `<definition>.<instance>` of `old.roles`, and is called for
|
||||
both `document` and `written`. `setReplyLimits` and `readRaw` are exported.
|
||||
- `packages/bus/src/broker.mjs`: the guard call, the page store, the check event, `library.limits.set`,
|
||||
`library.status` gains `replyLimits`, `role.claim` gains `library`, `thread.messages` gains `check` and `last`.
|
||||
- `packages/bus/src/server.mjs`, `client.mjs`: `BusError.reason` (sage-3599).
|
||||
- `packages/bus/src/runtime.mjs`: guard deps and the `library.limits.set` dispatch.
|
||||
- `packages/cli/src/limits.mjs`: `limits replies --file LIMITS`.
|
||||
|
||||
Left:
|
||||
- `packages/harness/src/runner.mjs`: the C2-07 runner work (see "Runner plan" below).
|
||||
- `packages/cli/README.md`: a line for `limits replies`.
|
||||
- Every test in the brief and the rulings below, then the mutants.
|
||||
- The rebases onto rows 283, 216 and 285, with the drift checks below.
|
||||
- This file's gate results and the manifest.
|
||||
|
||||
## Rulings in force
|
||||
|
||||
sage-3595:
|
||||
1. Supporting scope: a stub classify test now. Before in-review, rebase onto row 283's landing
|
||||
(honker/row283-build bd2c1c4ea when ruled) and add two real-fixture tests: a supporting cite with
|
||||
`as: "canon"` refuses `playground-cited-as-canon`, and the stored check result names `supporting`.
|
||||
2. runtime.mjs is approved for the guard wiring and the one `library.limits.set` branch. List its hunks here,
|
||||
name it in the manifest, and drift-check it after the 216 and 285 rebases.
|
||||
3. The check result is an `action.allowed` event with `action: "message.checked"`. The events kind CHECK at
|
||||
`packages/bus/src/migrations/3b.sql:9` has no `message.checked`, so a new kind would be a schema step.
|
||||
The event and the message INSERT share one transaction: a mutant that splits them must fail.
|
||||
4. readLibrary at the cited commit stands in for the C2-05a index. On the 216 rebase, compare 216's index
|
||||
set with readLibrary's for the fixture and record the result here.
|
||||
5. The retry is for a library business only. Tests: a JSON body between 32,768 characters and 64 KiB gets
|
||||
the retry; outside a library business nothing changes.
|
||||
6. Old `role.claim` pins stay as they are.
|
||||
7. `render.mjs:6` and `render.test.mjs:58` belong to row 225. Leave them.
|
||||
8. `thread.messages` rows that carry `check` get `last` per path cite: the file's last commit at the served
|
||||
tip, one git call per distinct path per read. Test with a fixture file committed after the cite.
|
||||
|
||||
sage-3599: `BusError` gets an optional `reason`. Only the citation guard's refusals set it. The envelope
|
||||
carries `reason` only when it is set. It is the guard's own text, never a quote, file text, url or a path
|
||||
other than the cited one, at most 300 characters. Tests: a guard refusal reaches the runner with code and
|
||||
reason; a refusal without a reason has an envelope byte-identical to today's, checked against an existing
|
||||
pin; a mutant that drops the client.mjs pass-through fails.
|
||||
|
||||
sage-3602: store `file` per path cite from the same git log call as the trailer. Test with a cited commit
|
||||
that didn't touch the file, so `file` differs from the cited commit, and `last` equals `file`.
|
||||
|
||||
## The check-result contract with row 225 (launchpad-0118, launchpad-0120, morgana-0200, sage-3596)
|
||||
|
||||
- Stored on the `message.checked` event: `{citations: [...]}` in body order.
|
||||
- A path cite: `{scope, agent, file}`. `scope` is the classify scope. `agent` is the cite's file's last
|
||||
commit's `Mosaic-Agent` trailer at the cited commit, or null. `file` is that commit's 40-hex id, or null.
|
||||
- A url cite: `{scope: "web", agent: null}`.
|
||||
- On `thread.messages`, each path cite gains `last`: the file's last commit at the served tip (40-hex or null).
|
||||
The Console compares `last` with `file`.
|
||||
- Rows without `check` are unchanged. No `changed` boolean. The webui passes rows through unchanged.
|
||||
|
||||
## Hunks outside the brief's file list
|
||||
|
||||
- `packages/bus/src/runtime.mjs`: imports `setReplyLimits`; the library deps gain `guard`, `replyLimits`
|
||||
(organizationWriter `readRaw`, or `{readable: false}` with no organization), `last` and `GuardError`;
|
||||
the organization dispatch gains `library.limits.set`.
|
||||
- `packages/bus/src/server.mjs`: a `refusal(e, other)` helper above serve's doc comment builds every
|
||||
BusError envelope and adds `reason` only when set. The synchronous request path accepts a Promise result
|
||||
(the guard is async), with a 120 s timeout, and releases `own` when it settles.
|
||||
- `packages/bus/src/client.mjs`: `new BusError(r.error, reason)` with `reason` a string cut at 300.
|
||||
|
||||
## Decisions and their tradeoffs
|
||||
|
||||
- The guard runs async outside the SQL transaction, then re-enters request() through `#guardPass`, so
|
||||
message.send's insert and the check event stay one synchronous transaction.
|
||||
- The guard runs before message.send's own checks, so a guard code can come before an authority code.
|
||||
- The check event id is derived from the message id (a UUID-shaped sha256), so `thread.messages` finds it
|
||||
through the unique index on `events.id`.
|
||||
- With no organization configured, `replyLimits` reads as unreadable, so it fails closed.
|
||||
- With more than one library project, the guard uses the first in the business file's order.
|
||||
- A library business without guard deps refuses guarded sends with `project-not-configured`.
|
||||
- The page store is per cap: 20 pages of 200 KB, kept only in a library business, dropped at session
|
||||
delete or disconnect and at run end.
|
||||
- The CLI `replies` verb reads the revision itself from `limits`.
|
||||
- Guard reasons never carry a url or a quote (sage-3599).
|
||||
|
||||
## Runner plan (not built)
|
||||
|
||||
`role.claim`'s reply is discarded at `runner.mjs:462`; keep it and read `library === true`. In a library
|
||||
business, at the reply site (`runner.mjs:582-587`):
|
||||
- Fixed texts `(the session gave no answer)` and `(the turn failed)`; the reason stays in the run log.
|
||||
A fixed text goes with no note, and `unslop.sent()` is not called, so the note waits.
|
||||
- A body that parses as a JSON object with a string `text` gets `unslop.note()` appended to `text`, is
|
||||
serialized again and is never clipped. A plain body keeps `clip(body) + unslop.note()`.
|
||||
- On a refusal whose code is in `GUARD_CODES`, or `request-too-large` from the client, or the broker's
|
||||
`invalid-request` from `string(a.body, 32768)` for a body over 32,768 characters (ruling 5; the runner
|
||||
can tell it by the body length it sent), run one new turn in the same
|
||||
session naming the code and `err.reason`, or the 64 KiB limit. Bracket it with usage like other turns.
|
||||
- After the retry: a table code sends `(no answer passed the citation check)`, too large sends
|
||||
`(the answer was too long to send)`. One retry per answer, whatever the cause.
|
||||
- `runTurn`'s output buffer holds up to 4 * BODY_MAX characters, so a reply over 64 KiB can reach the send.
|
||||
- Outside a library business nothing changes.
|
||||
|
||||
## Mutants planned
|
||||
|
||||
- The M11 writer check removed.
|
||||
- `document.human` compared instead of `old.human`.
|
||||
- The check event split from the insert transaction.
|
||||
- The client.mjs reason pass-through dropped.
|
||||
@@ -1626,3 +1626,9 @@ are never rewritten or removed; corrections are new entries.
|
||||
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 | Beakley (88b97f65, Codex gpt-6.1-sol) | Row 217 #1718 takeover under Q245 | Registered beakley/row217; read brief/design and standing Sage rulings; awaiting Launchpad ref before implementation. Queue next refused linked worktree; reported to Sage. Untested, no Node/test run pending CLEAR.
|
||||
|
||||
2026-10-24 | Beakley (88b97f65) | Row 217 round 2 under sage-4037 | New beakley/row217-r2 rebased onto e0c6142f3, retained both log histories, E4 product fix and supporting/index tests drafted. No Node/npm/tests/mutants run; ready for CLEAR; local-only dependency installs required on CLEAR, old-test red goes to Sage.
|
||||
|
||||
2026-10-24T10:21Z | Beakley (88b97f65) | Row217 #1718 round2 slot | All touched57 and six whole suites2764 pass, zero skips/cancels; M1-M5 killed/restored, E4 RED stand-in observed. First-new runtime fixture red recorded and corrected; no old-test change/failure. Evidence in agents/beakley/work/queue-217/run-r2. Verified for review on beakley/row217-r2; Sage landing scripts/review remain.
|
||||
+134
-7
@@ -204,15 +204,26 @@ const canonical = (x) =>
|
||||
Number.isFinite(Date.parse(x)) &&
|
||||
new Date(x).toISOString() === x;
|
||||
export class BusError extends Error {
|
||||
constructor(code) {
|
||||
// C2-06 (sage-3599): only the citation guard's refusals carry a reason, and the envelope passes it on.
|
||||
constructor(code, reason) {
|
||||
super(code);
|
||||
this.code = code;
|
||||
if (reason !== undefined) this.reason = reason;
|
||||
}
|
||||
}
|
||||
const fail = (code) => {
|
||||
throw new BusError(code);
|
||||
};
|
||||
const object = (x) => x !== null && typeof x === 'object' && !Array.isArray(x);
|
||||
// C2-06 (design 5.2, B7): the page store keeps a session's last 20 broker fetches, 200 KB of text each.
|
||||
const PAGE_COUNT = 20;
|
||||
const PAGE_BYTES = 200 * 1024;
|
||||
// C2-06: the guard's check event takes an id derived from the message's, so a thread read finds it
|
||||
// through the unique index on events.id (there is none on subject).
|
||||
const checkEventId = (mid) => {
|
||||
const h = createHash('sha256').update('message.checked:' + mid).digest('hex');
|
||||
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
||||
};
|
||||
const text = (x, max = 4096) => typeof x === 'string' && x.length > 0 && x.length <= max && !x.includes('\0');
|
||||
const id = (x) => text(x, 160) && /^[a-zA-Z0-9][a-zA-Z0-9_.:/-]*$/.test(x);
|
||||
function keys(x, allowed, required = []) {
|
||||
@@ -393,6 +404,11 @@ export class Broker {
|
||||
#credentialVerbs;
|
||||
#project;
|
||||
#library;
|
||||
// C2-06 (design 5.2, B7): cap -> the session's kept web.fetch pages, in a library business only.
|
||||
// In memory, dropped with the session.
|
||||
#pages = new Map();
|
||||
// C2-06: { s, check } while a guarded message.send or a thread.messages re-enters request().
|
||||
#guardPass = null;
|
||||
#usage;
|
||||
#limits;
|
||||
#harnessOn;
|
||||
@@ -1412,7 +1428,10 @@ export class Broker {
|
||||
this.#launcherReleased.delete(key);
|
||||
this.#accessRevoked.delete(key);
|
||||
for (const [cap, s] of this.#sessions)
|
||||
if (!s.human && !s.reader && s.business === record.business && s.run === record.run) this.#sessions.delete(cap);
|
||||
if (!s.human && !s.reader && s.business === record.business && s.run === record.run) {
|
||||
this.#sessions.delete(cap);
|
||||
this.#pages.delete(cap);
|
||||
}
|
||||
return this.#store.transaction(() => {
|
||||
const h = this.#own(session);
|
||||
const released = h?.op === 'claim' && h.holder_run === session.run;
|
||||
@@ -1494,6 +1513,7 @@ export class Broker {
|
||||
}
|
||||
disconnect(cap) {
|
||||
this.#sessions.delete(cap);
|
||||
this.#pages.delete(cap);
|
||||
}
|
||||
identity(cap) {
|
||||
const s = this.#session(cap);
|
||||
@@ -4758,12 +4778,21 @@ export class Broker {
|
||||
return this.#requestProjectFile(s, { verb: request.verb, args: structuredClone(args) });
|
||||
if (LIBRARY_VERBS.includes(request.verb))
|
||||
return this.#requestLibrary(s, { verb: request.verb, args: structuredClone(args) });
|
||||
// C2-06 (design 5.2): an agent's message to the human in a library business passes the citation
|
||||
// guard first, outside any transaction, and thread.messages adds each path cite's `last`. Both
|
||||
// then re-enter request() for the synchronous path below.
|
||||
if (this.#guardPass?.s !== s) {
|
||||
if (request.verb === 'message.send' && this.#guarded(s, args.to))
|
||||
return this.#guardedSend(cap, s, request, structuredClone(args));
|
||||
if (request.verb === 'thread.messages' && this.#libraryProject(s.business) && this.#library?.last)
|
||||
return this.#withLast(cap, s, request);
|
||||
}
|
||||
if (CREDENTIAL_VERBS.includes(request.verb)) return this.#requestCredential(s, request.verb, structuredClone(args));
|
||||
if (request.verb === 'instance.hire' || request.verb === 'instance.fire')
|
||||
return this.#chiefLifecycle(s, request.verb, structuredClone(args));
|
||||
if (request.verb === 'grants.scope' || request.verb === 'grants.scope.set')
|
||||
return this.#grantScopeVerb(s, request.verb, structuredClone(args));
|
||||
if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set", "veto.set"].includes(request.verb)) {
|
||||
if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set", "veto.set", "library.limits.set"].includes(request.verb)) {
|
||||
// Row 123 (#1617, REQ-COS-1, REQ-LIMIT-1): organization.set is limits.set for a role whose
|
||||
// file lists organization.change. It takes the same keys, runs the same write path and
|
||||
// logs the same receipt; only the actor differs: the role definition and instance.
|
||||
@@ -4809,6 +4838,12 @@ export class Broker {
|
||||
keys(args, VETO_KEYS, VETO_KEYS);
|
||||
this.#human(s);
|
||||
this.#mayHuman(s, "veto.set", args.name);
|
||||
}
|
||||
// C2-06 M11: the human's replyLimits verb. The tester is refused by the gate above and an agent
|
||||
// session by human-required; the writer's fence refuses any other actor.
|
||||
else if (request.verb === "library.limits.set") {
|
||||
keys(args, ["revision", "replyLimits"], ["revision", "replyLimits"]);
|
||||
this.#human(s);
|
||||
} else {
|
||||
keys(args, ORGANIZATION_KEYS, ["revision"]);
|
||||
// Row 194 (D2.4, B7): the chief can't change a kept instance's model, as that replaces it at
|
||||
@@ -5263,7 +5298,14 @@ export class Broker {
|
||||
const context = { business: this.#business(s.business), project: args.project };
|
||||
let result;
|
||||
if (verb === 'library.file') result = await this.#library.readFile({ ...context, path: args.path });
|
||||
else if (verb === 'library.status') result = await this.#library.status(context);
|
||||
else if (verb === 'library.status') {
|
||||
result = await this.#library.status(context);
|
||||
// C2-06: the reply limits in force, read from the file now, and the keys that fell back.
|
||||
if (this.#library.replyLimits) {
|
||||
const { requireCitation, citationsPerReply, fallback, unreadable } = this.#library.replyLimits(s.business);
|
||||
result = { ...result, replyLimits: { requireCitation, citationsPerReply, fallback, problem: unreadable ? 'reply-limits-unreadable' : null } };
|
||||
}
|
||||
}
|
||||
else {
|
||||
// A fetch that did not move served still answers with the code, so the refusal is recorded.
|
||||
const { state, code } = await this.#library.sync(context, { accept: args.accept ?? null });
|
||||
@@ -5277,6 +5319,78 @@ export class Broker {
|
||||
throw this.#refused(s, Failure && error instanceof Failure ? new BusError(error.code) : error, request);
|
||||
}
|
||||
}
|
||||
// C2-06: the business's library project, the first in file order, or null.
|
||||
#libraryProject(business) {
|
||||
return Object.entries(this.#business(business).projects ?? {}).find(([, p]) => p.kind === 'library')?.[0] ?? null;
|
||||
}
|
||||
// C2-06 (design 5.2): every agent session's message to the human in a business with a library project
|
||||
// is guarded, whatever the sender's definition.
|
||||
#guarded(s, to) {
|
||||
return to === 'human' && !s.human && !s.tester && !s.user && !s.reader && !s.console && this.#libraryProject(s.business) !== null;
|
||||
}
|
||||
#reenter(cap, s, request, check) {
|
||||
this.#guardPass = { s, check };
|
||||
try {
|
||||
return this.request(cap, request);
|
||||
} finally {
|
||||
this.#guardPass = null;
|
||||
}
|
||||
}
|
||||
// As #requestProject: the git reads run outside any SQL transaction. A guard refusal is recorded with
|
||||
// its code and reaches the caller with the guard's reason; the send then runs, and stores the check
|
||||
// result in its own transaction.
|
||||
async #guardedSend(cap, s, request, args) {
|
||||
let check;
|
||||
try {
|
||||
this.#store.transaction(() => this.#agent(s));
|
||||
if (!this.#library?.guard || !this.#library?.replyLimits) fail('project-not-configured');
|
||||
if (typeof args.body !== 'string') fail('invalid-request');
|
||||
const business = this.#business(s.business);
|
||||
check = await this.#library.guard({ business, project: this.#libraryProject(s.business) }, args.body, {
|
||||
limits: this.#library.replyLimits(s.business),
|
||||
definition: business.roles[s.role]?.definition ?? null,
|
||||
pages: this.#pages.get(cap) ?? [],
|
||||
});
|
||||
} catch (e) {
|
||||
const Guard = this.#library?.GuardError;
|
||||
throw this.#refused(s, Guard && e instanceof Guard ? new BusError(e.code, e.reason) : e, request);
|
||||
}
|
||||
return this.#reenter(cap, s, { verb: 'message.send', args }, check);
|
||||
}
|
||||
// C2-06 (sage-3595, morgana-0200): each path cite of a checked row gains `last`, the file's last commit
|
||||
// at the served tip, one git call per distinct path. Rows without `check` are unchanged.
|
||||
async #withLast(cap, s, request) {
|
||||
const rows = this.#reenter(cap, s, request, null);
|
||||
if (!rows.some((m) => m.check)) return rows;
|
||||
const cites = (m) => {
|
||||
try {
|
||||
const c = JSON.parse(m.body).citations;
|
||||
return Array.isArray(c) ? c : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
let last = new Map();
|
||||
try {
|
||||
const paths = rows.filter((m) => m.check).flatMap((m) => cites(m).filter((c) => typeof c?.path === 'string').map((c) => c.path));
|
||||
last = await this.#library.last({ business: this.#business(s.business), project: this.#libraryProject(s.business) }, paths);
|
||||
} catch {
|
||||
last = new Map();
|
||||
}
|
||||
return rows.map((m) => {
|
||||
if (!m.check) return m;
|
||||
const body = cites(m);
|
||||
return { ...m, check: { ...m.check, citations: m.check.citations.map((c, i) =>
|
||||
c.scope === 'web' ? c : { ...c, last: last.get(body[i]?.path) ?? null }) } };
|
||||
});
|
||||
}
|
||||
// C2-06 (B7): a fetch's text, kept for url cites in a library business only.
|
||||
#keepPage(cap, s, url, page) {
|
||||
if (!this.#sessions.has(cap) || !this.#libraryProject(s.business) || typeof page?.text !== 'string') return;
|
||||
const text = Buffer.from(page.text).subarray(0, PAGE_BYTES).toString();
|
||||
const kept = { url, finalUrl: typeof page.finalUrl === 'string' ? page.finalUrl : null, text };
|
||||
this.#pages.set(cap, [...(this.#pages.get(cap) ?? []), kept].slice(-PAGE_COUNT));
|
||||
}
|
||||
// The eight task verbs reach the trusted S3 handler here. It runs outside any transaction, gets the
|
||||
// cap to authorize and record through authorize() and recordTask(); refusals are recorded as in request().
|
||||
async requestTask(cap, request, handler) {
|
||||
@@ -5360,6 +5474,7 @@ export class Broker {
|
||||
throw refuse(new BusError('web-failed'));
|
||||
}
|
||||
complete(out.summary);
|
||||
if (verb === 'web.fetch') this.#keepPage(cap, s, args.url, out.result);
|
||||
return { ...out.result, untrusted: true, notice: WEB_UNTRUSTED, source: verb };
|
||||
}
|
||||
// Row 210 (#1707, design D5): host.run, one sudo command on a registered host over ssh, for a
|
||||
@@ -5486,7 +5601,7 @@ export class Broker {
|
||||
by,
|
||||
);
|
||||
this.#routeSession(s.session);
|
||||
return { role: s.role, run: s.run, thread: s.thread };
|
||||
return { role: s.role, run: s.run, thread: s.thread, ...(this.#libraryProject(s.business) ? { library: true } : {}) };
|
||||
}
|
||||
if (
|
||||
this.#store.get(
|
||||
@@ -5510,7 +5625,8 @@ export class Broker {
|
||||
by,
|
||||
);
|
||||
this.#routeWaiting(s.business, s.role);
|
||||
return { role: s.role, run: s.run };
|
||||
// C2-06 (C2-07): the runner's switch to the library texts and retry. Absent elsewhere.
|
||||
return { role: s.role, run: s.run, ...(this.#libraryProject(s.business) ? { library: true } : {}) };
|
||||
}
|
||||
case 'role.release': {
|
||||
keys(a, []);
|
||||
@@ -5874,6 +5990,10 @@ export class Broker {
|
||||
if (this.#userThread(s) && to === 'human') fail('user-thread-human');
|
||||
this.#checkMessaging(s.business, this.#sender(s), testerThread && to === 'tester' ? 'human' : to);
|
||||
string(a.body, 32768);
|
||||
// C2-06: a guarded send is stored only through #guardedSend's pass, with its check result.
|
||||
const guarded = this.#guarded(s, to);
|
||||
if (guarded && this.#guardPass?.s !== s) fail('storage-refused');
|
||||
const check = guarded ? this.#guardPass.check : null;
|
||||
if (
|
||||
a.class !== undefined &&
|
||||
![
|
||||
@@ -5920,6 +6040,9 @@ export class Broker {
|
||||
a.corrects ?? null,
|
||||
a.body,
|
||||
);
|
||||
// C2-06: the stored check result shares the message's transaction (sage-3595 item 3). No new
|
||||
// event kind, which would be a schema step: the events kind CHECK is migrations/3b.sql:9.
|
||||
if (check) this.#event(s, 'action.allowed', { action: 'message.checked', message: mid, check }, mid, checkEventId(mid));
|
||||
const cancelled = this.#route(s.business, mid, to, thread);
|
||||
const request = s.human
|
||||
? this.#event(s, 'human.input', { kind: 'instruction', message: mid }, mid)
|
||||
@@ -6632,11 +6755,15 @@ export class Broker {
|
||||
)?.seq;
|
||||
if (after === undefined) fail('message-not-found');
|
||||
}
|
||||
// C2-06 (row 225, morgana-0198): a row the citation guard checked carries the stored result.
|
||||
return this.#store.all(
|
||||
`SELECT m.* FROM messages m JOIN message_threads mt ON mt.message=m.id WHERE mt.session=? AND m.seq>? ORDER BY m.seq LIMIT ${limit}`,
|
||||
session,
|
||||
after,
|
||||
);
|
||||
).map((m) => {
|
||||
const e = this.#store.get("SELECT body FROM events WHERE id=? AND kind='action.allowed' AND subject=?", checkEventId(m.id), m.id);
|
||||
return e ? { ...m, check: JSON.parse(e.body).check } : m;
|
||||
});
|
||||
}
|
||||
// Row 206 (193b2, #1704, design 5.3): the owner of a thread shares it read-only, or ends a share.
|
||||
// A user's collaborator grant must still cover the thread, and a user target needs a live grant
|
||||
|
||||
@@ -41,7 +41,8 @@ export class Client {
|
||||
const r = JSON.parse(input.toString('utf8'));
|
||||
if (r.ok === true) end(null, r.result);
|
||||
else if (r.ok === false && typeof r.error === 'string' && /^[a-z-]{1,64}$/.test(r.error))
|
||||
end(new BusError(r.error));
|
||||
// C2-06 (sage-3599): the citation guard's reason, when the envelope has one.
|
||||
end(new BusError(r.error, typeof r.reason === 'string' ? r.reason.slice(0, 300) : undefined));
|
||||
else end(new BusError('invalid-response'));
|
||||
} catch {
|
||||
end(new BusError('invalid-response'));
|
||||
|
||||
@@ -14,7 +14,7 @@ import { publishRemote, workingRepo, WorkingError } from '../../seat/src/working
|
||||
import { createLoader, hasModule } from '../../plugins/src/loader.mjs';
|
||||
import { pluginSettings } from '../../business/src/plugin-settings.mjs';
|
||||
import { instanceSettings } from '../../business/src/instance-settings.mjs';
|
||||
import { organizationWriter, OrganizationError } from '../../business/src/organization.mjs';
|
||||
import { organizationWriter, OrganizationError, setReplyLimits } from '../../business/src/organization.mjs';
|
||||
import { credentialVerbs } from './credential-verbs.mjs';
|
||||
import { loadBusiness } from '../../business/src/business.mjs';
|
||||
import { loadRole } from '../../business/src/role.mjs';
|
||||
@@ -305,6 +305,13 @@ export async function startBroker({
|
||||
status: context => libraryModule.libraryStatus({ ...context, dataRoot }),
|
||||
sync: (context, { accept }) => processing.work(() => libraryModule.sync({ ...context, dataRoot }, { ...syncOptions(context.business), accept })),
|
||||
LibraryError: libraryModule.LibraryError,
|
||||
// C2-06: the citation guard, the reply limits read from the business file at each check (none
|
||||
// configured reads as unreadable, so requireCitation is true), and thread.messages' `last`.
|
||||
guard: (context, body, options) => libraryModule.guardReply({ ...context, dataRoot }, body, options),
|
||||
replyLimits: id => libraryModule.resolveReplyLimits(organization
|
||||
? organizationWriter({ dir: organization.dir, rolesDir: organization.rolesDir }).readRaw(id) : { readable: false }),
|
||||
last: (context, paths) => libraryModule.lastCommits({ ...context, dataRoot }, paths),
|
||||
GuardError: libraryModule.GuardError,
|
||||
},
|
||||
project: {
|
||||
readSpec: context => projectModule.readSpec({ ...context, dataRoot }),
|
||||
@@ -338,6 +345,8 @@ export async function startBroker({
|
||||
else if(verb==='veto.set')result=instances.veto(id,args,actor);
|
||||
// Row 298: the human's grant scope, through the instance writer.
|
||||
else if(verb==='grants.scope.set')result=instances.grantScope(id,args,actor);
|
||||
// C2-06 M11: the human's replyLimits, through the organization writer's fence.
|
||||
else if(verb==='library.limits.set')result=setReplyLimits(organizationWriter({dir:organization.dir,rolesDir:organization.rolesDir}),id,args,actor);
|
||||
// Row 194 (D2.1, D2.2): the chief's hire and fire, with the reason in the change context.
|
||||
else if(verb==='instance.hire')result=instances.hire(id,args,{actor,context});
|
||||
else if(verb==='instance.fire')result=instances.fire(id,{name:args.name},{actor,context});
|
||||
|
||||
@@ -7,6 +7,11 @@ const LIMIT = 65536;
|
||||
// Row 148: a document save may carry up to 1 MiB of text. Only a request that names that verb near
|
||||
// its start (the proof object comes first) may pass LIMIT; the parsed verb is checked again below.
|
||||
const SAVE_VERB_AT_START = new RegExp(`"verb":"${PROJECT_SAVE_VERB.replaceAll('.', '\\.')}"`);
|
||||
// The refusal envelope. C2-06 (sage-3599): `reason` is added only when the error carries one, which
|
||||
// only the citation guard's refusals do, so every other envelope is byte-identical to before.
|
||||
const refusal = (e, other) => e instanceof BusError
|
||||
? { ok: false, error: e.code, ...(typeof e.reason === 'string' ? { reason: e.reason } : {}) }
|
||||
: { ok: false, error: other };
|
||||
// One request per connection. There is deliberately no reconnect/retry or SQL verb.
|
||||
// `tasks` ({handle, timeout}) is the trusted S3 adapter; only the eight task verbs reach it.
|
||||
// `web` is the trusted web service (web.mjs), or a function from a business id to one (or null); only
|
||||
@@ -15,6 +20,7 @@ const SAVE_VERB_AT_START = new RegExp(`"verb":"${PROJECT_SAVE_VERB.replaceAll('.
|
||||
// host-not-configured.
|
||||
// A request carries exactly one of cap, human (row S4), tester (row 110), user (FED R3) and console
|
||||
// (row 252) proof.
|
||||
|
||||
export async function serve({ broker, path, authenticateHuman = null, authenticateTester = null, authenticateUser = null, authenticateConsole = null, peerReader = null, timeout = 5000, tasks = null, web = null, host = null }) {
|
||||
try {
|
||||
lstatSync(path);
|
||||
@@ -96,7 +102,7 @@ export async function serve({ broker, path, authenticateHuman = null, authentica
|
||||
.requestTask(cap, { verb: r.verb, args: r.args ?? {} }, tasks.handle)
|
||||
.then(
|
||||
(result) => finish({ ok: true, result }),
|
||||
(e) => finish({ ok: false, error: e instanceof BusError ? e.code : 'adapter-failed' }),
|
||||
(e) => finish(refusal(e, 'adapter-failed')),
|
||||
)
|
||||
.finally(() => {
|
||||
if (own) release();
|
||||
@@ -114,7 +120,7 @@ export async function serve({ broker, path, authenticateHuman = null, authentica
|
||||
.requestWeb(cap, { verb: r.verb, args: r.args ?? {} }, service, { onAdmit: () => socket.setTimeout(service.timeout) })
|
||||
.then(
|
||||
(result) => finish({ ok: true, result }),
|
||||
(e) => finish({ ok: false, error: e instanceof BusError ? e.code : 'web-failed' }),
|
||||
(e) => finish(refusal(e, 'web-failed')),
|
||||
)
|
||||
.finally(() => {
|
||||
if (own) release();
|
||||
@@ -130,7 +136,7 @@ export async function serve({ broker, path, authenticateHuman = null, authentica
|
||||
.requestHost(cap, { verb: r.verb, args: r.args ?? {} }, host, { onAdmit: (ms) => socket.setTimeout(ms + 10000) })
|
||||
.then(
|
||||
(result) => finish({ ok: true, result }),
|
||||
(e) => finish({ ok: false, error: e instanceof BusError ? e.code : 'host-failed' }),
|
||||
(e) => finish(refusal(e, 'host-failed')),
|
||||
)
|
||||
.finally(() => {
|
||||
if (own) release();
|
||||
@@ -153,14 +159,26 @@ export async function serve({ broker, path, authenticateHuman = null, authentica
|
||||
socket.setTimeout(120_000);
|
||||
Promise.resolve().then(() => broker.request(cap, { verb: r.verb, args: r.args ?? {} })).then(
|
||||
result => { if (!socket.destroyed) finish({ ok: true, result }); },
|
||||
e => { if (!socket.destroyed) finish({ ok: false, error: e instanceof BusError ? e.code : 'invalid-envelope' }); },
|
||||
e => { if (!socket.destroyed) finish(refusal(e, 'invalid-envelope')); },
|
||||
).finally(() => { if (own) release(); });
|
||||
return;
|
||||
}
|
||||
const result = broker.request(cap, { verb: r.verb, args: r.args ?? {} });
|
||||
// C2-06: a guarded message.send and a library business's thread.messages read Git first, so
|
||||
// the broker answers them with a promise. Every other verb stays synchronous.
|
||||
if (result instanceof Promise) {
|
||||
const own = transient;
|
||||
transient = null;
|
||||
socket.setTimeout(120_000);
|
||||
result.then(
|
||||
value => { if (!socket.destroyed) finish({ ok: true, result: value }); },
|
||||
e => { if (!socket.destroyed) finish(refusal(e, 'invalid-envelope')); },
|
||||
).finally(() => { if (own) release(); });
|
||||
return;
|
||||
}
|
||||
finish({ ok: true, result });
|
||||
} catch (e) {
|
||||
finish({ ok: false, error: e instanceof BusError ? e.code : 'invalid-envelope' });
|
||||
finish(refusal(e, 'invalid-envelope'));
|
||||
} finally {
|
||||
if (transient) release();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// E4/sage-3668: real flock contention must not turn a valid cite into an invalid-cite refusal.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { ensureMirror, mirrorPath, SYNC_STATE_FILE, withLibraryLock } from '../../library/src/mirror.mjs';
|
||||
import { guardReply } from '../../library/src/guard.mjs';
|
||||
import { git } from '../../library/tests/helpers.mjs';
|
||||
import { Client } from '../src/client.mjs';
|
||||
import { serve } from '../src/server.mjs';
|
||||
import { citationWorld, citedBody, fixtureCite } from './citation-helpers.mjs';
|
||||
|
||||
test('C2-06 held library lock gives the agent citation-unavailable and its retry reason', { timeout: 25000 }, async t => {
|
||||
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||
const context = { business: f.business, project: 'books', dataRoot };
|
||||
await ensureMirror(context); const mirror = mirrorPath(context);
|
||||
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served');
|
||||
writeFileSync(join(mirror, SYNC_STATE_FILE), JSON.stringify({ version: 1, branch: 'main', served: f.commit,
|
||||
lastFetch: null, lastError: null, diverged: null, problems: [] }), { mode: 0o600 });
|
||||
f.adapter.guard = (ctx, body, opts) => guardReply({ ...ctx, dataRoot }, body, opts);
|
||||
f.call(f.agent, 'role.claim');
|
||||
const socket = join(f.root, 'availability.sock'), server = await serve({ broker: f.broker, path: socket });
|
||||
t.after(() => server.close()); const client = new Client({ path: socket, cap: f.agent, timeout: 15000 });
|
||||
const args = { to: 'human', body: citedBody([fixtureCite(f.commit)]) };
|
||||
await client.call('message.send', args); // Establish that the cite and real mirror are valid.
|
||||
const before = f.store.get('SELECT count(*) n FROM messages').n;
|
||||
await withLibraryLock(mirror, async () => {
|
||||
await assert.rejects(client.call('message.send', args), e => {
|
||||
assert.equal(e.code, 'citation-unavailable');
|
||||
assert.equal(e.reason, 'The library is busy or unavailable. Retry the answer when the library is available.');
|
||||
return true;
|
||||
});
|
||||
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, before);
|
||||
// A fixed fallback carries no library cite and must not need the contested lock.
|
||||
await client.call('message.send', { to: 'human', body: '(no answer passed the citation check)' });
|
||||
});
|
||||
await client.call('message.send', args); // No lock mutation or lost valid cite after release.
|
||||
});
|
||||
|
||||
test('C2-06 malformed sync state is availability failure, never a fabricated invalid cite', async t => {
|
||||
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||
const context = { business: f.business, project: 'books', dataRoot };
|
||||
await ensureMirror(context); const mirror = mirrorPath(context);
|
||||
writeFileSync(join(mirror, SYNC_STATE_FILE), '{broken', { mode: 0o600 });
|
||||
await assert.rejects(guardReply(context, citedBody([fixtureCite(f.commit)]), {
|
||||
limits: f.limits(), definition: 'coder', pages: [],
|
||||
}), { code: 'citation-unavailable' });
|
||||
});
|
||||
|
||||
test('C2-06 no mirror or freshly initialized empty mirror still means no served citation', async t => {
|
||||
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||
const context = { business: f.business, project: 'books', dataRoot };
|
||||
const check = () => guardReply(context, citedBody([fixtureCite(f.commit)]), { limits: f.limits(), definition: 'coder', pages: [] });
|
||||
await assert.rejects(check(), { code: 'citation-invalid' });
|
||||
await ensureMirror(context); await assert.rejects(check(), { code: 'citation-invalid' });
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { connect } from 'node:net';
|
||||
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { Client } from '../src/client.mjs';
|
||||
import { serve } from '../src/server.mjs';
|
||||
import { FIXED_TEXTS, GuardError } from '../../library/src/guard.mjs';
|
||||
import { git } from '../../library/tests/helpers.mjs';
|
||||
import { organizationWriter, setReplyLimits } from '../../business/src/organization.mjs';
|
||||
import { businessDoc, rolesCopy, writeJson } from '../../business/tests/helpers.mjs';
|
||||
import { resolveReplyLimits } from '../../library/src/limits.mjs';
|
||||
import { citationWorld, citedBody, fixtureCite, guardCases } from './citation-helpers.mjs';
|
||||
|
||||
for (const definition of ['coder', 'research']) test(`C2-06 ${definition} send_message guard covers every table refusal; inserts nothing`, async t => {
|
||||
const f = citationWorld(t, { definition }); f.call(f.agent, 'role.claim');
|
||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation: true } }));
|
||||
for (const [code, body] of guardCases(f.commit)) {
|
||||
const count = f.store.get('SELECT count(*) n FROM messages').n;
|
||||
await assert.rejects(f.call(f.agent, 'message.send', { to: 'human', body }), e => e.code === code && e.reason.length <= 300);
|
||||
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, count, code);
|
||||
assert.equal(f.store.all("SELECT * FROM events WHERE json_extract(body,'$.action')='message.checked'").length, 0);
|
||||
}
|
||||
const sent = await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(f.commit)]) });
|
||||
const row = f.store.get("SELECT body,subject FROM events WHERE json_extract(body,'$.action')='message.checked'");
|
||||
assert.equal(row.subject, sent.id);
|
||||
assert.deepEqual(JSON.parse(row.body).check, { citations: [{ scope: 'canon', agent: null, file: f.commit }] });
|
||||
});
|
||||
|
||||
test('C2-06 stored check carries the file author trailer at an unrelated cited tip', async t => {
|
||||
const f = citationWorld(t, { definition: 'research' }); f.call(f.agent, 'role.claim');
|
||||
const path = 'Book One/Characters/Mara Vell.md';
|
||||
writeFileSync(join(f.repo, path), readFileSync(join(f.repo, path), 'utf8') + 'A fixture note.\n');
|
||||
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'Fixture note\n\nMosaic-Agent: acme/scribe');
|
||||
const file = git(f.repo, 'rev-parse', 'HEAD');
|
||||
writeFileSync(join(f.repo, 'Inbox Note.md'), 'Unrelated fixture.\n'); git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'unrelated');
|
||||
const cited = git(f.repo, 'rev-parse', 'HEAD'); f.setServed(cited);
|
||||
assert.notEqual(cited, file);
|
||||
const sent = await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(cited)]) });
|
||||
const event = f.store.get("SELECT body FROM events WHERE subject=? AND json_extract(body,'$.action')='message.checked'", sent.id);
|
||||
assert.deepEqual(JSON.parse(event.body).check, { citations: [{ scope: 'canon', agent: 'acme/scribe', file }] });
|
||||
const [row] = await f.call(f.human, 'thread.messages', { thread: f.thread.id });
|
||||
assert.deepEqual(row.check.citations, [{ scope: 'canon', agent: 'acme/scribe', file, last: file }]);
|
||||
});
|
||||
|
||||
const supportingMapping = entry => {
|
||||
entry.universes.harbor.supporting = ['Book One/Playground/'];
|
||||
return entry;
|
||||
};
|
||||
|
||||
test('C2-06 supporting cite cannot state canon, including a folder also marked playground', async t => {
|
||||
const f = citationWorld(t, { definition: 'research', mapChange: supportingMapping }); f.call(f.agent, 'role.claim');
|
||||
const count = f.store.get('SELECT count(*) n FROM messages').n;
|
||||
await assert.rejects(f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(f.commit, {
|
||||
path: 'Book One/Playground/What If.md', lines: [4, 4], quote: 'Mara Vell', as: 'canon',
|
||||
})]) }), { code: 'playground-cited-as-canon' });
|
||||
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, count);
|
||||
});
|
||||
|
||||
test('C2-06 supporting file stores supporting scope and its real agent trailer', async t => {
|
||||
const f = citationWorld(t, { definition: 'research', mapChange: supportingMapping }); f.call(f.agent, 'role.claim');
|
||||
const path = 'Book One/Playground/What If.md';
|
||||
writeFileSync(join(f.repo, path), readFileSync(join(f.repo, path), 'utf8') + 'Agent fixture note.\n');
|
||||
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'Supporting note\n\nMosaic-Agent: acme/scribe');
|
||||
const file = git(f.repo, 'rev-parse', 'HEAD'); f.setServed(file);
|
||||
const sent = await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(file, { path, lines: [4, 4], quote: 'Mara Vell' })]) });
|
||||
const event = f.store.get("SELECT body FROM events WHERE subject=? AND json_extract(body,'$.action')='message.checked'", sent.id);
|
||||
assert.deepEqual(JSON.parse(event.body).check, { citations: [{ scope: 'supporting', agent: 'acme/scribe', file }] });
|
||||
});
|
||||
|
||||
test('C2-06 check evidence and insert are atomic when storing evidence fails', async t => {
|
||||
const f = citationWorld(t); f.call(f.agent, 'role.claim');
|
||||
f.store.run(`CREATE TRIGGER fixture_check_refused BEFORE INSERT ON events
|
||||
WHEN json_extract(NEW.body,'$.action')='message.checked' BEGIN SELECT RAISE(ABORT,'fixture check failure'); END`);
|
||||
await assert.rejects(f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(f.commit)]) }));
|
||||
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, 0);
|
||||
});
|
||||
|
||||
test('C2-06 live limits change both directions; unreadable file keeps checks and exact fixed texts', async t => {
|
||||
const f = citationWorld(t); f.call(f.agent, 'role.claim');
|
||||
const send = body => f.call(f.agent, 'message.send', { to: 'human', body });
|
||||
await send('free');
|
||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation: true } }));
|
||||
await assert.rejects(send('free'), { code: 'citation-missing' });
|
||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation: false } })); await send('free');
|
||||
for (const invalid of ['{broken', 'null', '[]']) {
|
||||
writeFileSync(f.settings, invalid);
|
||||
const before = f.store.get('SELECT count(*) n FROM messages').n;
|
||||
await assert.rejects(send('free'), { code: 'citation-missing' });
|
||||
await assert.rejects(send(citedBody(Array(21).fill(fixtureCite(f.commit)))), { code: 'citations-over-limit' });
|
||||
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, before);
|
||||
for (const text of Object.values(FIXED_TEXTS)) await send(text);
|
||||
assert.equal((await f.call(f.human, 'library.status', { project: 'books' })).replyLimits.problem, 'reply-limits-unreadable');
|
||||
}
|
||||
rmSync(f.settings);
|
||||
await assert.rejects(send('free'), { code: 'citation-missing' });
|
||||
});
|
||||
|
||||
test('C2-06 page store is session-local, caps 20 fetches and 200 KiB, drops at disconnect', async t => {
|
||||
const f = citationWorld(t, { definition: 'research' }); f.call(f.agent, 'role.claim');
|
||||
const fetch = async (n, text = `page ${n}`) => f.broker.requestWeb(f.agent, { verb: 'web.fetch', args: { url: `https://fixture.invalid/${n}` } },
|
||||
{ handle: async () => ({ result: { text }, summary: { outcome: 'ok' } }) });
|
||||
const send = (n, quote) => f.call(f.agent, 'message.send', { to: 'human', body: citedBody([{ url: `https://fixture.invalid/${n}`, quote }]) });
|
||||
await fetch(0); await send(0, 'page 0');
|
||||
for (let n = 1; n <= 20; n++) await fetch(n);
|
||||
await assert.rejects(send(0, 'page 0'), { code: 'citation-unfetched' }); await send(1, 'page 1');
|
||||
await fetch(21, 'x'.repeat(200 * 1024) + 'AFTER_CAP');
|
||||
await send(21, 'xxxx'); await assert.rejects(send(21, 'AFTER_CAP'), { code: 'citation-unfetched' });
|
||||
f.broker.disconnect(f.agent);
|
||||
f.broker.endLaunch({ business: 'acme', run: 'coder-run', reason: 'fixture', exitCode: 0 });
|
||||
const replacement = f.broker.bindLaunch({ ...f.launch, run: 'next-run' });
|
||||
f.call(replacement, 'role.claim');
|
||||
await assert.rejects(f.call(replacement, 'message.send', { to: 'human', body: citedBody([{ url: 'https://fixture.invalid/21', quote: 'xxxx' }]) }), { code: 'citation-unfetched' });
|
||||
});
|
||||
|
||||
test('C2-06 transport passes safe capped guard reason; ordinary refusal envelope is byte-identical', async t => {
|
||||
const f = citationWorld(t, { guard: async () => { throw new GuardError('citation-invalid', 'guard reason '.repeat(50)); } });
|
||||
f.call(f.agent, 'role.claim'); const path = join(f.root, 'socket');
|
||||
const server = await serve({ broker: f.broker, path }); t.after(() => server.close());
|
||||
const client = new Client({ path, cap: f.agent });
|
||||
await assert.rejects(client.call('message.send', { to: 'human', body: 'fixture' }), e => e.code === 'citation-invalid' && e.reason === ('guard reason '.repeat(50)).slice(0, 300));
|
||||
const raw = await new Promise((resolve, reject) => {
|
||||
const socket = connect(path); let text = ''; socket.on('error', reject); socket.on('data', b => { text += b; }); socket.on('end', () => resolve(text));
|
||||
socket.on('connect', () => socket.write(JSON.stringify({ cap: f.agent, verb: 'unknown-fixture' }) + '\n'));
|
||||
});
|
||||
assert.equal(raw, '{"ok":false,"error":"unknown-verb"}\n');
|
||||
});
|
||||
|
||||
test('C2-06 non-library businesses retain role.claim shape and unguarded free sends', async t => {
|
||||
const f = citationWorld(t, { library: false });
|
||||
assert.deepEqual(f.call(f.agent, 'role.claim'), { role: 'coder', run: 'coder-run' });
|
||||
assert.ok(f.call(f.agent, 'message.send', { to: 'human', body: 'free' }).id);
|
||||
});
|
||||
|
||||
test('C2-06 thread messages carry file/last evidence with one read per distinct path', async t => {
|
||||
const f = citationWorld(t, { definition: 'research' }); f.call(f.agent, 'role.claim');
|
||||
const path = 'Book One/Characters/Mara Vell.md';
|
||||
await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(f.commit), fixtureCite(f.commit)]) });
|
||||
await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(f.commit)]) });
|
||||
const rows = await f.call(f.human, 'thread.messages', { thread: f.thread.id });
|
||||
assert.equal(rows.length, 2); assert.deepEqual(f.reads, [path]);
|
||||
for (const row of rows) for (const c of row.check.citations) {
|
||||
assert.equal(c.file, f.commit); assert.equal(c.last, f.commit); assert.equal(c.agent, null);
|
||||
}
|
||||
writeFileSync(join(f.repo, path), 'A changed fixture.\n'); git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'changed fixture');
|
||||
const changed = git(f.repo, 'rev-parse', 'HEAD'); f.setServed(changed); f.reads.length = 0;
|
||||
const newer = await f.call(f.human, 'thread.messages', { thread: f.thread.id });
|
||||
assert.deepEqual(f.reads, [path]); assert.equal(newer[0].check.citations[0].file, f.commit); assert.equal(newer[0].check.citations[0].last, changed);
|
||||
});
|
||||
|
||||
test('C2-06 guarded request snapshot cannot change between check and insert', async t => {
|
||||
let finish;
|
||||
const f = citationWorld(t, { guard: () => new Promise(r => { finish = r; }) }); f.call(f.agent, 'role.claim');
|
||||
const request = { verb: 'message.send', args: { to: 'human', body: 'checked fixture' } };
|
||||
const sent = f.broker.request(f.agent, request); request.args.body = 'unchecked replacement';
|
||||
finish(null); await sent;
|
||||
assert.equal(f.store.get("SELECT body FROM messages WHERE from_role='coder'").body, 'checked fixture');
|
||||
});
|
||||
|
||||
test('C2-06 library.limits.set is human-only and invalid settings leave file and receipts unchanged', async t => {
|
||||
let writer;
|
||||
const f = citationWorld(t, { organization: (id, verb, args, actor) => {
|
||||
assert.equal(verb, 'library.limits.set'); return setReplyLimits(writer, id, args, actor);
|
||||
} });
|
||||
const dir = join(f.root, 'config'), file = writeJson(join(dir, 'businesses/acme.json'), businessDoc(f.root));
|
||||
writer = organizationWriter({ dir, rolesDir: rolesCopy(f.root) });
|
||||
f.adapter.replyLimits = () => resolveReplyLimits(writer.readRaw('acme'));
|
||||
f.call(f.agent, 'role.claim');
|
||||
await f.call(f.human, 'library.limits.set', { revision: writer.read('acme').revision, replyLimits: { requireCitation: true } });
|
||||
await assert.rejects(f.call(f.agent, 'message.send', { to: 'human', body: 'free' }), { code: 'citation-missing' });
|
||||
const before = readFileSync(file), log = readFileSync(writer.logFile('acme'));
|
||||
const args = { revision: writer.read('acme').revision, replyLimits: { citationsPerReply: 0 } };
|
||||
for (const [cap, code] of [[f.human, 'invalid-request'], [f.agent, 'human-required'], [f.tester, 'tester-refused']]) {
|
||||
assert.throws(() => f.call(cap, 'library.limits.set', args), { code });
|
||||
assert.deepEqual(readFileSync(file), before); assert.deepEqual(readFileSync(writer.logFile('acme')), log);
|
||||
}
|
||||
assert.throws(() => f.call(f.human, 'library.limits.set', { ...args, replyLimits: { invented: true } }), { code: 'invalid-request' });
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
// C2-06: real broker, real Git reads and fixture-only library. Reused by runner acceptance tests.
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { Store } from '../src/store.mjs';
|
||||
import { Broker } from '../src/broker.mjs';
|
||||
import { checkReply, GuardError, libraryReader } from '../../library/src/guard.mjs';
|
||||
import { THREAD_DEFINITIONS } from '../../business/src/vocabulary.mjs';
|
||||
import { resolveReplyLimits } from '../../library/src/limits.mjs';
|
||||
import { mapping, repo, MARKER } from '../../library/tests/helpers.mjs';
|
||||
|
||||
export const citedBody = citations => JSON.stringify({ text: 'A fixture answer.', citations });
|
||||
export const fixtureCite = (commit, extra = {}) => ({ path: 'Book One/Characters/Mara Vell.md', commit, lines: [9, 9], quote: 'Grey eyes.', ...extra });
|
||||
|
||||
export function citationWorld(t, { definition = 'coder', library = true, guard: overrideGuard, organization = null, sendAuthority = true, mapChange } = {}) {
|
||||
const r = repo(t), root = r.root, state = join(root, 'bus'); mkdirSync(state, { mode: 0o700 });
|
||||
const store = new Store(state); t.after(() => store.close());
|
||||
const map = mapping(mapChange), settings = join(root, 'business.json');
|
||||
writeFileSync(settings, JSON.stringify({ replyLimits: { requireCitation: false, citationsPerReply: 20 } }));
|
||||
const business = { id: 'acme', human: 'jason', vars: { 'bus.tester': true },
|
||||
...(library ? { projects: { books: map } } : {}),
|
||||
arbiters: { technical: 'coder', delivery: 'coder' },
|
||||
roles: { coder: { definition, web: true, authority: { withinRole: sendAuthority ? ['message.send'] : [], crossRole: [] } } } };
|
||||
const limits = () => {
|
||||
try { return resolveReplyLimits({ readable: true, document: JSON.parse(readFileSync(settings, 'utf8')) }); }
|
||||
catch { return resolveReplyLimits({ readable: false }); }
|
||||
};
|
||||
let served = r.commit;
|
||||
const reads = [];
|
||||
const adapter = {
|
||||
GuardError,
|
||||
replyLimits: limits,
|
||||
guard: overrideGuard ?? ((context, body, opts) => checkReply(body, {
|
||||
limits: opts.limits, mapping: map, library: libraryReader(r.repo, served, map),
|
||||
web: { allowed: ['companion', 'research'].includes(opts.definition), pages: opts.pages },
|
||||
})),
|
||||
last: async (context, paths) => {
|
||||
const out = new Map(); for (const path of new Set(paths)) { reads.push(path); out.set(path, await libraryReader(r.repo, served, map).last(path)); } return out;
|
||||
},
|
||||
readFile: async () => ({}), status: async () => ({ project: 'books', served }),
|
||||
};
|
||||
const broker = new Broker({ store, businesses: { acme: business }, library: adapter, organization });
|
||||
const human = broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
const project = THREAD_DEFINITIONS.includes(definition)
|
||||
? broker.request(human, { verb: 'project.create', args: { name: 'Citation fixture' } }) : null;
|
||||
const thread = project
|
||||
? broker.request(human, { verb: 'thread.create', args: { project: project.id, name: 'Fixture thread', instance: 'coder' } }) : null;
|
||||
const launch = { business: 'acme', role: 'coder', run: 'coder-run', harness: 'pi', ...(thread ? { thread: thread.thread ?? thread.id } : {}) };
|
||||
const agent = broker.bindLaunch(launch);
|
||||
const tester = broker.bindTester({ business: 'acme' });
|
||||
const call = (cap, verb, args = {}) => broker.request(cap, { verb, args });
|
||||
return { ...r, root, store, business, broker, agent, human, tester, settings, limits, call, adapter, reads, launch, thread, setServed: commit => { served = commit; } };
|
||||
}
|
||||
|
||||
export function guardCases(commit) {
|
||||
const good = fixtureCite(commit);
|
||||
return [
|
||||
['reply-invalid', JSON.stringify({ text: 'x', citations: [], invented: true })],
|
||||
['citation-missing', 'Uncited fixture answer.'],
|
||||
['citations-over-limit', citedBody(Array(21).fill(good))],
|
||||
['citation-invalid', citedBody([{ ...good, quote: 'wrong fixture quote' }])],
|
||||
['playground-cited-as-canon', citedBody([{ ...good, path: 'Book One/Playground/What If.md', lines: [4, 4], quote: MARKER, as: 'canon' }])],
|
||||
['citation-cross-universe', citedBody([good, { ...good, path: 'Second World/Chapters/01.md', lines: [1, 1], quote: MARKER }])],
|
||||
['citation-unfetched', citedBody([{ url: 'https://fixture.invalid/page', quote: 'no fetch' }])],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// E7: exercise startBroker's real live readRaw adapter, rather than replacing the library adapter.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { startBroker } from '../src/runtime.mjs';
|
||||
import { validateBusinessDocument } from '../../business/src/business.mjs';
|
||||
import { businessDoc, rolesCopy, systemFor, writeJson } from '../../business/tests/helpers.mjs';
|
||||
import { mappingEntry } from '../../library/tests/helpers.mjs';
|
||||
|
||||
test('C2-06 runtime reads changed and unreadable reply limits without restarting', async t => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'citation-runtime-')); t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
const doc = businessDoc(root), rolesDir = rolesCopy(root), dir = join(root, 'config'), dataRoot = join(root, 'data');
|
||||
doc.projects.books = mappingEntry(); doc.replyLimits = { requireCitation: false, citationsPerReply: 3 };
|
||||
const file = writeJson(join(dir, 'businesses/acme.json'), doc);
|
||||
const checked = validateBusinessDocument(doc, file, { rolesDir }); mkdirSync(dataRoot, { mode: 0o700 });
|
||||
// Runtime takes the host's broker projection, not the business validator's unresolved role entries.
|
||||
// No agent action is needed here: use the same empty-authority shape as limits-runtime.test.mjs.
|
||||
const business = { id: 'acme', human: 'jason', projects: checked.projects,
|
||||
arbiters: { technical: 'coder', delivery: 'coder' },
|
||||
roles: { coder: { definition: 'coder', authority: { withinRole: [], crossRole: [] } } } };
|
||||
const runtime = await startBroker({ dataRoot, businesses: { acme: business }, organization: { dir, rolesDir, system: systemFor(root) } });
|
||||
t.after(() => runtime.close());
|
||||
const human = runtime.broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
const status = () => runtime.broker.request(human, { verb: 'library.status', args: { project: 'books' } });
|
||||
assert.deepEqual((await status()).replyLimits, { requireCitation: false, citationsPerReply: 3, fallback: [], problem: null });
|
||||
doc.replyLimits = { requireCitation: true, citationsPerReply: 7 }; writeJson(file, doc);
|
||||
assert.deepEqual((await status()).replyLimits, { requireCitation: true, citationsPerReply: 7, fallback: [], problem: null });
|
||||
writeFileSync(file, '{broken');
|
||||
assert.deepEqual((await status()).replyLimits, { requireCitation: true, citationsPerReply: 20, fallback: [], problem: 'reply-limits-unreadable' });
|
||||
rmSync(file);
|
||||
assert.equal((await status()).replyLimits.problem, 'reply-limits-unreadable');
|
||||
});
|
||||
@@ -21,7 +21,7 @@ const BOT_NAME = /^bot-[a-z0-9][a-z0-9._-]{0,60}$/;
|
||||
// How long a thread's session stays up with no turn before the launcher stops
|
||||
// it, when launch.threadIdleSeconds is absent (REQ-THREAD-2, row 121).
|
||||
export const THREAD_IDLE_SECONDS = 900;
|
||||
const TOP_KEYS = ["businessVersion", "id", "human", "arbiters", "projects", "vars", "tracker", "roles", "launch", "plugins", "hosts", "retired"];
|
||||
const TOP_KEYS = ["businessVersion", "id", "human", "arbiters", "projects", "vars", "tracker", "roles", "launch", "plugins", "hosts", "retired", "replyLimits"];
|
||||
|
||||
// The config directory: next to the system config, $MOSAIC_CONFIG or
|
||||
// ~/.config/mosaic-dev/config.json, the same rule mosaic-config.mjs uses.
|
||||
|
||||
@@ -8,6 +8,7 @@ import { join, dirname } from "node:path";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { businessFilePath, validateBusinessDocument } from "./business.mjs";
|
||||
import { requireId, sha256, canonicalJson } from "./util.mjs";
|
||||
import { checkReplyLimits } from "../../library/src/limits.mjs";
|
||||
|
||||
export class OrganizationError extends Error {
|
||||
constructor(code, message = code) {
|
||||
@@ -80,6 +81,33 @@ function changes(old, next, path = "") {
|
||||
}];
|
||||
}
|
||||
|
||||
// C2-06 M11: replyLimits is the human's alone. Any setter that changes it refuses unless the actor is
|
||||
// the business's human as the file stood before the write, and no agent or the tester under that
|
||||
// name. Compared as M9 compares projects, on the document and again on what lands on disk.
|
||||
function replyLimitsFence(old, document, actor) {
|
||||
if (canonicalJson(document?.replyLimits) === canonicalJson(old.replyLimits)) return;
|
||||
const agents = Object.entries(old.roles ?? {}).map(([instance, entry]) => `${entry?.definition}.${instance}`);
|
||||
if (actor === old.human && actor !== "tester" && !agents.includes(actor)) return;
|
||||
// The reason names each changed key and its value in force (the brief's M11).
|
||||
const plain = v => v !== null && typeof v === "object" && !Array.isArray(v);
|
||||
const before = old.replyLimits, after = document?.replyLimits;
|
||||
const named = plain(before) && plain(after)
|
||||
? [...new Set([...Object.keys(before), ...Object.keys(after)])].filter(k => canonicalJson(before[k]) !== canonicalJson(after[k]))
|
||||
.map(k => `replyLimits.${k} is ${canonicalJson(before[k]) ?? "unset"}`)
|
||||
: [`replyLimits is ${canonicalJson(before) ?? "unset"}`];
|
||||
fail("organization-reply-limits-human-only", `${named.join(", ").slice(0, 240)}; only the human changes it`);
|
||||
}
|
||||
|
||||
// C2-06: library.limits.set, the human's verb. It replaces the whole replyLimits object; a key left out
|
||||
// is its default. An unknown key or a value outside the table refuses invalid-request, and the write's
|
||||
// M11 fence refuses any actor but the human.
|
||||
export function setReplyLimits(writer, id, args, actor) {
|
||||
const replyLimits = checkReplyLimits(args?.replyLimits);
|
||||
if (replyLimits === null || typeof args?.revision !== "string") fail("invalid-request", "replyLimits takes requireCitation and citationsPerReply only");
|
||||
const { document } = writer.read(id);
|
||||
return writer.write(id, { ...document, replyLimits }, { revision: args.revision, actor });
|
||||
}
|
||||
|
||||
export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
const evidence = join(dir, "businesses", ".history");
|
||||
const logFile = id => join(evidence, id, "changes.jsonl");
|
||||
@@ -93,6 +121,19 @@ export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
return { document, revision: sha256(bytes) };
|
||||
}
|
||||
|
||||
// C2-06: the citation guard reads replyLimits fresh on every check, from the raw file, so a business
|
||||
// file that fails validation still yields its limits. { readable: false } when the file is missing,
|
||||
// unreadable, not JSON or not an object.
|
||||
function readRaw(id) {
|
||||
try {
|
||||
const document = JSON.parse(regular(businessFilePath(id, dir)));
|
||||
return document !== null && typeof document === "object" && !Array.isArray(document)
|
||||
? { readable: true, document } : { readable: false };
|
||||
} catch {
|
||||
return { readable: false };
|
||||
}
|
||||
}
|
||||
|
||||
function withLock(id, fn) {
|
||||
const parent = join(dir, "businesses");
|
||||
directory(evidence);
|
||||
@@ -198,6 +239,7 @@ export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
// mapping, including library playgrounds and hybrid universes.
|
||||
if (canonicalJson(document?.projects) !== canonicalJson(old.projects))
|
||||
fail('organization-projects-protected');
|
||||
replyLimitsFence(old, document, actor);
|
||||
validateBusinessDocument(document, file, { rolesDir });
|
||||
const bytes = JSON.stringify(document, null, 2) + "\n";
|
||||
// Check and log exactly what lands on disk, for every setter: a value JSON
|
||||
@@ -205,6 +247,7 @@ export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
const written = JSON.parse(bytes);
|
||||
if (canonicalJson(written.projects) !== canonicalJson(old.projects))
|
||||
fail('organization-projects-protected');
|
||||
replyLimitsFence(old, written, actor);
|
||||
validateBusinessDocument(written, file, { rolesDir });
|
||||
if (beforeWrite) {
|
||||
beforeWrite();
|
||||
@@ -260,5 +303,5 @@ export function organizationWriter({ dir, rolesDir, barrier = () => {} }) {
|
||||
});
|
||||
}
|
||||
|
||||
return { logFile, read, write, record, recover: id => withLock(id, () => recover(id)) };
|
||||
return { logFile, read, readRaw, write, record, recover: id => withLock(id, () => recover(id)) };
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { organizationWriter, setReplyLimits } from '../src/organization.mjs';
|
||||
import { validateBusinessDocument } from '../src/business.mjs';
|
||||
import { businessDoc, rolesCopy, tmp, writeJson } from './helpers.mjs';
|
||||
|
||||
function fixture(t, human = 'jason') {
|
||||
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
const doc = businessDoc(root); doc.human = human; doc.replyLimits = { requireCitation: false, citationsPerReply: 20 };
|
||||
const dir = join(root, 'config'), file = writeJson(join(dir, 'businesses/acme.json'), doc), rolesDir = rolesCopy(root);
|
||||
const writer = organizationWriter({ dir, rolesDir });
|
||||
const next = structuredClone(doc); next.vars['tracker.pollSeconds'] = 61;
|
||||
writer.write('acme', next, { revision: writer.read('acme').revision, actor: human });
|
||||
return { root, file, writer, rolesDir, log: writer.logFile('acme') };
|
||||
}
|
||||
|
||||
function refused(f, actor, change) {
|
||||
const before = readFileSync(f.file), log = readFileSync(f.log), { document, revision } = f.writer.read('acme');
|
||||
change(document);
|
||||
assert.throws(() => f.writer.write('acme', document, { revision, actor }), { code: 'organization-reply-limits-human-only' });
|
||||
assert.deepEqual(readFileSync(f.file), before); assert.deepEqual(readFileSync(f.log), log);
|
||||
}
|
||||
|
||||
test('C2-06 M11 human replacement works; omitted settings return to defaults', t => {
|
||||
const f = fixture(t);
|
||||
setReplyLimits(f.writer, 'acme', { revision: f.writer.read('acme').revision, replyLimits: { requireCitation: true } }, 'jason');
|
||||
assert.deepEqual(f.writer.read('acme').document.replyLimits, { requireCitation: true });
|
||||
setReplyLimits(f.writer, 'acme', { revision: f.writer.read('acme').revision, replyLimits: {} }, 'jason');
|
||||
assert.deepEqual(f.writer.read('acme').document.replyLimits, {});
|
||||
});
|
||||
|
||||
test('C2-06 M11 agents and tester cannot change settings, including by changing human', t => {
|
||||
const f = fixture(t);
|
||||
for (const actor of ['chief-of-staff.chief', 'tester', 'coder.coder'])
|
||||
refused(f, actor, d => { d.replyLimits.requireCitation = true; });
|
||||
refused(f, 'chief-of-staff.chief', d => { d.human = 'chief-of-staff.chief'; d.replyLimits.requireCitation = true; });
|
||||
const dotted = fixture(t, 'coder.coder');
|
||||
refused(dotted, 'coder.coder', d => { d.replyLimits.citationsPerReply = 1; });
|
||||
});
|
||||
|
||||
test('C2-06 M11 serialization cannot replace settings after the first fence', t => {
|
||||
const f = fixture(t);
|
||||
refused(f, 'chief-of-staff.chief', d => {
|
||||
Object.defineProperty(d.replyLimits, 'toJSON', { value: () => ({ requireCitation: true }) });
|
||||
});
|
||||
});
|
||||
|
||||
// E1: unlike tester/role actors, mallory passes the other actor-shape exclusions.
|
||||
// Comparing document.human instead of old.human must fail this case.
|
||||
test('C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits', t => {
|
||||
const f = fixture(t);
|
||||
refused(f, 'mallory', d => { d.human = 'mallory'; d.replyLimits.requireCitation = true; });
|
||||
});
|
||||
|
||||
test('C2-06 invalid setter values preserve the business and changes.jsonl', t => {
|
||||
const f = fixture(t), before = readFileSync(f.file), log = readFileSync(f.log);
|
||||
for (const replyLimits of [{ citationsPerReply: 0 }, { citationsPerReply: 1001 }, { requireCitation: 'true' }, { invented: true }, []]) {
|
||||
assert.throws(() => setReplyLimits(f.writer, 'acme', { revision: f.writer.read('acme').revision, replyLimits }, 'jason'), { code: 'invalid-request' });
|
||||
assert.deepEqual(readFileSync(f.file), before); assert.deepEqual(readFileSync(f.log), log);
|
||||
}
|
||||
});
|
||||
|
||||
test('C2-06 validator accepts hand-edited settings; raw reader fails closed on invalid files', t => {
|
||||
const f = fixture(t), doc = f.writer.read('acme').document;
|
||||
for (const replyLimits of ['true', 1, null, [true], {}, { requireCitation: 'true' }, { citationsPerReply: 0 }]) {
|
||||
const next = { ...doc, replyLimits };
|
||||
assert.doesNotThrow(() => validateBusinessDocument(next, f.file, { rolesDir: f.rolesDir }));
|
||||
writeJson(f.file, next); assert.deepEqual(f.writer.readRaw('acme').document.replyLimits, replyLimits);
|
||||
}
|
||||
for (const bad of ['{broken', 'null', '[]', '1']) {
|
||||
writeFileSync(f.file, bad); assert.deepEqual(f.writer.readRaw('acme'), { readable: false });
|
||||
}
|
||||
rmSync(f.file); assert.deepEqual(f.writer.readRaw('acme'), { readable: false });
|
||||
});
|
||||
@@ -959,6 +959,12 @@ writer adds `since`, the time the limit was set, and keeps it on later saves.
|
||||
A patch that sends a different `since` is refused. Only these paths are token
|
||||
limits; the agent limits above are never read as one, whatever their names.
|
||||
|
||||
`mosaic limits replies --file LIMITS [--business ID]` replaces the citation settings
|
||||
through the human-only `library.limits.set` verb. LIMITS is a JSON object with optional
|
||||
`requireCitation` (boolean, default false) and `citationsPerReply` (integer 1–1000,
|
||||
default 20). Omitted keys use their defaults; unknown keys and invalid values refuse.
|
||||
The next library reply uses the new settings without restarting the bus.
|
||||
|
||||
`mosaic limits reset WORD... [--business ID]` moves one token limit's `since`
|
||||
to now, so its count restarts. The path is one word per level, as the plain
|
||||
output prints it: `mosaic limits reset tokens models anthropic claude-opus-5-5`.
|
||||
|
||||
@@ -7,9 +7,10 @@ import { CliError } from "./errors.mjs";
|
||||
import { migrateLaunch } from "../../business/src/migrate-launch.mjs";
|
||||
|
||||
// Row 155 (#1649, Q169): a PATCH may carry `assistants`, the assistant's vars, tracker and definition.
|
||||
// C2-06 M11: `replies --file LIMITS` replaces replyLimits with the file's object, at the current revision.
|
||||
export const LIMITS_USAGE = "mosaic limits [--business ID] [--json] [--tester] | set --file PATCH [--business ID] | " +
|
||||
"reset PATH... [--business ID] | migrate --file ORGANIZATION --models MAPPING [--roles-dir DIR]; " +
|
||||
"PATCH keys: revision, limits, models, instances, reset, assistants";
|
||||
"reset PATH... [--business ID] | replies --file LIMITS [--business ID] | migrate --file ORGANIZATION --models MAPPING [--roles-dir DIR]; " +
|
||||
"PATCH keys: revision, limits, models, instances, reset, assistants; LIMITS keys: requireCitation, citationsPerReply";
|
||||
|
||||
export const DAILY_NOTE = "Daily quotas count only this stack's own use of the account, not its use anywhere else.";
|
||||
|
||||
@@ -67,8 +68,9 @@ export async function limitsCommand(args, io, callFor) {
|
||||
io.stdout.write(JSON.stringify(converted, null, 2) + "\n");
|
||||
return;
|
||||
}
|
||||
if (!["view", "set", "reset"].includes(verb) || values["--models"] || values["--roles-dir"] ||
|
||||
(verb !== "set" && values["--file"]) || (verb === "set" && !values["--file"]) ||
|
||||
const fileVerb = verb === "set" || verb === "replies";
|
||||
if (!["view", "set", "reset", "replies"].includes(verb) || values["--models"] || values["--roles-dir"] ||
|
||||
(!fileVerb && values["--file"]) || (fileVerb && !values["--file"]) ||
|
||||
(verb === "reset" && parsed.positional.length < 2)) {
|
||||
throw new CliError(LIMITS_USAGE, 4);
|
||||
}
|
||||
@@ -78,6 +80,11 @@ export async function limitsCommand(args, io, callFor) {
|
||||
// result's `tokens` lists each token limit: counted, not-enforced or check-failed.
|
||||
let result;
|
||||
if (verb === "set") result = await call("limits.set", JSON.parse(readFileSync(values["--file"], "utf8")));
|
||||
else if (verb === "replies") {
|
||||
const replyLimits = JSON.parse(readFileSync(values["--file"], "utf8"));
|
||||
const { revision } = await call("limits", {});
|
||||
result = await call("library.limits.set", { revision, replyLimits });
|
||||
}
|
||||
else if (verb === "reset") {
|
||||
const { revision } = await call("limits", {});
|
||||
result = await call("limits.set", { revision, reset: [parsed.positional.slice(1)] });
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { limitsCommand } from '../src/limits.mjs';
|
||||
|
||||
test('C2-06 CLI replies reads revision and sends only the JSON settings to the human verb', async t => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'reply-cli-')); t.after(() => rmSync(dir, { recursive: true, force: true }));
|
||||
const file = join(dir, 'settings.json'); writeFileSync(file, JSON.stringify({ requireCitation: true, citationsPerReply: 7 }));
|
||||
const calls = [], output = [];
|
||||
await limitsCommand(['replies', '--file', file, '--business', 'acme'], { stdout: { write: s => output.push(s) } }, parsed => {
|
||||
assert.equal(parsed.values['--business'], 'acme');
|
||||
return async (verb, args) => { calls.push([verb, args]); return verb === 'limits' ? { revision: 'r1' } : { revision: 'r2' }; };
|
||||
});
|
||||
assert.deepEqual(calls, [['limits', {}], ['library.limits.set', { revision: 'r1', replyLimits: { requireCitation: true, citationsPerReply: 7 } }]]);
|
||||
assert.match(output.join(''), /r2/);
|
||||
});
|
||||
|
||||
test('C2-06 CLI replies requires a file and refuses tester writes before a bus call', async () => {
|
||||
for (const args of [['replies'], ['replies', '--tester', '--file', 'unused'], ['replies', '--file', 'unused', '--models', 'unused']])
|
||||
await assert.rejects(limitsCommand(args, { stdout: { write() {} } }, () => { throw Error('bus touched'); }), { exitCode: 4 });
|
||||
});
|
||||
@@ -38,6 +38,7 @@ import { createServer, connect } from "node:net";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { Client } from "../../bus/src/client.mjs";
|
||||
import { GUARD_CODES, FIXED_TEXTS } from "../../library/src/reply-protocol.mjs";
|
||||
import { fetchReview, landWork, pushWorking } from "./git-tools.mjs";
|
||||
|
||||
export const EXIT = Object.freeze({ stopped: 0, founder: 20, claim: 21, session: 22, broker: 23, usage: 2 });
|
||||
@@ -458,8 +459,9 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
return EXIT.founder;
|
||||
}
|
||||
const client = new Client({ path: session.brokerSocket, cap, timeout: 30_000 });
|
||||
let library = false;
|
||||
try {
|
||||
await client.call("role.claim");
|
||||
library = (await client.call("role.claim"))?.library === true;
|
||||
} catch (e) {
|
||||
// No answer is not a refusal: the broker is down or the outcome is unknown.
|
||||
if (e.code === "outcome-unknown") {
|
||||
@@ -582,9 +584,60 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`);
|
||||
try {
|
||||
if (m.class !== "RESULT") {
|
||||
const body = turn.ok ? turn.text || "(the session gave no answer)" : `The turn failed: ${turn.reason}`;
|
||||
await client.call("message.send", { to: m.from_role, body: clip(body) + unslop.note(), class: "RESULT", in_reply_to: m.id });
|
||||
unslop.sent();
|
||||
if (!library) {
|
||||
const body = turn.ok ? turn.text || "(the session gave no answer)" : `The turn failed: ${turn.reason}`;
|
||||
await client.call("message.send", { to: m.from_role, body: clip(body) + unslop.note(), class: "RESULT", in_reply_to: m.id });
|
||||
unslop.sent();
|
||||
} else {
|
||||
const send = async (answer) => {
|
||||
const raw = answer.ok ? answer.text || FIXED_TEXTS.empty : FIXED_TEXTS.failed;
|
||||
const fixed = Object.values(FIXED_TEXTS).includes(raw);
|
||||
let body = raw;
|
||||
if (!fixed) {
|
||||
let value;
|
||||
try { value = JSON.parse(raw); } catch {}
|
||||
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
||||
// Malformed objects still reach the guard and its reply-invalid refusal.
|
||||
if (typeof value.text === "string") value.text += unslop.note();
|
||||
body = JSON.stringify(value);
|
||||
} else body = clip(raw) + unslop.note();
|
||||
}
|
||||
try {
|
||||
await client.call("message.send", { to: m.from_role, body, class: "RESULT", in_reply_to: m.id });
|
||||
if (!fixed) unslop.sent();
|
||||
} catch (error) {
|
||||
// The existing broker body cap is smaller than the socket's 64 KiB cap.
|
||||
// Both follow the one-retry rule, without changing non-library sends.
|
||||
if (error.code === "invalid-request" && body.length > 32768) {
|
||||
error = Object.assign(new Error("request-too-large"), { code: "request-too-large" });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
try { await send(turn); }
|
||||
catch (error) {
|
||||
if (!GUARD_CODES.includes(error.code) && error.code !== "request-too-large") throw error;
|
||||
log(`runner: reply to ${m.id} refused: ${error.code}${error.reason ? `: ${error.reason}` : ""}`);
|
||||
const reason = error.code === "request-too-large"
|
||||
? "The message exceeds the 64 KiB request limit or the 32768-character body limit. Send a shorter answer."
|
||||
: error.reason ?? "The reply did not pass the citation check.";
|
||||
++n;
|
||||
try { await client.call("usage.begin", { turn: `${m.id}:retry` }); }
|
||||
catch (e) { log(`runner: usage baseline unavailable: ${e.code ?? "failed"}`); }
|
||||
const retry = await runTurn({ session, request: `${block ?? ""}\n${turnRequest(m)}\n\nYour answer was refused with ${error.code}: ${reason}\nReply again in this same session.`, n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
unslop.read();
|
||||
try { await client.call("usage.end", { turn: `${m.id}:retry` }); }
|
||||
catch (e) { log(`runner: usage outcome unavailable: ${e.code ?? "failed"}`); }
|
||||
if (stopping) break loop;
|
||||
if (!retry.ok) log(`runner: retry turn ${n} for message ${m.id} failed: ${retry.reason}`);
|
||||
try { await send(retry); }
|
||||
catch (again) {
|
||||
if (!GUARD_CODES.includes(again.code) && again.code !== "request-too-large") throw again;
|
||||
log(`runner: retry to ${m.id} refused: ${again.code}`);
|
||||
await send({ ok: true, text: again.code === "request-too-large" ? FIXED_TEXTS.tooLong : FIXED_TEXTS.uncited });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.call("message.read", { id: m.id });
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { serve } from '../../bus/src/server.mjs';
|
||||
import { FIXED_TEXTS } from '../../library/src/reply-protocol.mjs';
|
||||
import { GuardError } from '../../library/src/guard.mjs';
|
||||
import { citationWorld, citedBody, fixtureCite, guardCases } from '../../bus/tests/citation-helpers.mjs';
|
||||
|
||||
const RUNNER = new URL('../src/runner.mjs', import.meta.url).pathname;
|
||||
const ADAPTER = new URL('./fixtures/citation-adapter.mjs', import.meta.url).pathname;
|
||||
async function until(fn) {
|
||||
const stop = Date.now() + 15000;
|
||||
while (Date.now() < stop) { const value = fn(); if (value) return value; await new Promise(r => setTimeout(r, 15)); }
|
||||
throw Error('fixture timed out');
|
||||
}
|
||||
|
||||
async function setup(t, stepsFor, { library = true, requireCitation = true, sendAuthority = true, guard } = {}) {
|
||||
const f = citationWorld(t, { library, sendAuthority, guard }), runDir = join(f.root, 'run'), bundle = join(runDir, 'bundle');
|
||||
mkdirSync(bundle, { recursive: true }); mkdirSync(join(f.root, 'ws'));
|
||||
const steps = stepsFor(f.commit), brokerSocket = join(f.root, 'broker.sock');
|
||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation } }));
|
||||
const server = await serve({ broker: f.broker, path: brokerSocket });
|
||||
writeFileSync(join(runDir, 'fixture-steps.json'), JSON.stringify(steps));
|
||||
const adapter = join(runDir, 'adapter.sh'); writeFileSync(adapter, `exec '${process.execPath}' '${ADAPTER}'\n`);
|
||||
writeFileSync(join(bundle, 'prompt.md'), 'Fixture only.\n');
|
||||
writeFileSync(join(bundle, 'tools.json'), JSON.stringify([{ name: 'send_message', verb: 'message.send', action: 'message.send' }]));
|
||||
writeFileSync(join(bundle, 'policy.json'), JSON.stringify({ needs: [], credentials: [] }));
|
||||
writeFileSync(join(runDir, 'session.json'), JSON.stringify({ business: 'acme', instance: 'coder', run: 'coder-run', harness: 'pi',
|
||||
provider: 'fixture', model: 'fixture', brokerSocket, workspace: join(f.root, 'ws'), sessionDir: join(runDir, 'session'), adapter,
|
||||
bundle: { prompt: join(bundle, 'prompt.md'), tools: join(bundle, 'tools.json'), policy: join(bundle, 'policy.json') },
|
||||
toolSocket: join(runDir, 'tools.sock'), turnMarker: join(runDir, 'turn.done'), turnEnd: 'marker', harnessEnv: {}, pollInterval: 20, turnTimeout: 10 }));
|
||||
const child = spawn(process.execPath, [RUNNER, runDir], { stdio: ['pipe', 'ignore', 'pipe'], env: { PATH: process.env.PATH } });
|
||||
let log = ''; child.stderr.on('data', b => { log += b; }); child.stdin.on('error', () => {}); child.stdin.end(JSON.stringify({ cap: f.agent }) + '\n');
|
||||
const exited = once(child, 'exit');
|
||||
t.after(async () => { child.kill('SIGTERM'); await exited; await server.close(); });
|
||||
await until(() => f.store.get("SELECT 1 FROM role_claims WHERE role='coder' AND op='claim'"));
|
||||
const ask = async () => {
|
||||
const sent = await f.call(f.human, 'message.send', { to: 'coder', body: 'Fixture question.', class: 'REQUEST' });
|
||||
const reply = await until(() => f.store.get('SELECT * FROM messages WHERE in_reply_to=?', sent.id));
|
||||
await until(() => f.store.get("SELECT 1 FROM deliveries WHERE message=? AND op='read'", sent.id));
|
||||
return reply;
|
||||
};
|
||||
return { ...f, runDir, ask, log: () => log, count: () => Number(readFileSync(join(runDir, 'fixture-count'), 'utf8')) };
|
||||
}
|
||||
|
||||
for (const code of ['reply-invalid', 'citation-missing', 'citations-over-limit', 'citation-invalid', 'playground-cited-as-canon', 'citation-cross-universe', 'citation-unfetched']) {
|
||||
test(`C2-06 RESULT ${code} retries once, delivers only the valid answer and names reason`, async t => {
|
||||
const f = await setup(t, commit => [{ text: guardCases(commit).find(c => c[0] === code)[1] }, { text: citedBody([fixtureCite(commit)]) }]);
|
||||
const reply = await f.ask(); assert.equal(reply.body, citedBody([fixtureCite(f.commit)])); assert.equal(f.count(), 2);
|
||||
assert.equal(f.store.get("SELECT count(*) n FROM messages WHERE from_role='coder'").n, 1);
|
||||
const requests = readFileSync(join(f.runDir, 'fixture-requests.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
|
||||
assert.match(requests[1], new RegExp(code)); assert.match(requests[1], /refused with/);
|
||||
assert.match(f.log(), new RegExp(code));
|
||||
});
|
||||
}
|
||||
|
||||
test('C2-06 RESULT two guard refusals use the citation fixed text and exactly one retry', async t => {
|
||||
const f = await setup(t, () => [{ text: 'uncited' }, { text: 'uncited again' }]);
|
||||
assert.equal((await f.ask()).body, FIXED_TEXTS.uncited); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
for (const size of [34000, 70000]) {
|
||||
test(`C2-06 RESULT JSON ${size} characters retries with a shorter valid answer`, async t => {
|
||||
const f = await setup(t, commit => [{ text: JSON.stringify({ text: 'x'.repeat(size), citations: [fixtureCite(commit)] }) }, { text: citedBody([fixtureCite(commit)]) }]);
|
||||
assert.equal((await f.ask()).body, citedBody([fixtureCite(f.commit)])); assert.equal(f.count(), 2);
|
||||
assert.match(readFileSync(join(f.runDir, 'fixture-requests.jsonl'), 'utf8'), /64 KiB/);
|
||||
});
|
||||
}
|
||||
|
||||
test('C2-06 RESULT two oversized JSON bodies use the length fixed text with citations required', async t => {
|
||||
const f = await setup(t, commit => Array(2).fill({ text: JSON.stringify({ text: 'x'.repeat(70000), citations: [fixtureCite(commit)] }) }));
|
||||
assert.equal((await f.ask()).body, FIXED_TEXTS.tooLong); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
test('C2-06 multibyte plain bodies also receive the one request-size retry', async t => {
|
||||
const f = await setup(t, commit => [{ text: '界'.repeat(32000) }, { text: citedBody([fixtureCite(commit)]) }]);
|
||||
assert.equal((await f.ask()).body, citedBody([fixtureCite(f.commit)])); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
test('C2-06 two multibyte plain bodies use the length fallback', async t => {
|
||||
const f = await setup(t, () => Array(2).fill({ text: '界'.repeat(32000) }));
|
||||
assert.equal((await f.ask()).body, FIXED_TEXTS.tooLong); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
test('C2-06 RESULT a length refusal then citation refusal shares one retry and citation fallback', async t => {
|
||||
const f = await setup(t, commit => [{ text: JSON.stringify({ text: 'x'.repeat(70000), citations: [fixtureCite(commit)] }) }, { text: 'uncited' }]);
|
||||
assert.equal((await f.ask()).body, FIXED_TEXTS.uncited); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
test('C2-06 fixed empty/failed replies defer unslop note until the next cited reply', async t => {
|
||||
const f = await setup(t, commit => [{ text: '', unslop: true }, { fail: true }, { text: citedBody([fixtureCite(commit)]) }]);
|
||||
assert.equal((await f.ask()).body, FIXED_TEXTS.empty); assert.equal((await f.ask()).body, FIXED_TEXTS.failed);
|
||||
const reply = JSON.parse((await f.ask()).body);
|
||||
assert.deepEqual(reply.citations, [fixtureCite(f.commit)]); assert.match(reply.text, /unslop/i); assert.equal(f.count(), 3);
|
||||
});
|
||||
|
||||
test('C2-06 cited JSON above the old 32000 clip retains its citations and text', async t => {
|
||||
const f = await setup(t, commit => [{ text: JSON.stringify({ text: 'x'.repeat(32200), citations: [fixtureCite(commit)] }) }]);
|
||||
const reply = JSON.parse((await f.ask()).body); assert.equal(reply.text.length, 32200); assert.deepEqual(reply.citations, [fixtureCite(f.commit)]); assert.equal(f.count(), 1);
|
||||
});
|
||||
|
||||
test('C2-06 a flagged cited answer keeps the note inside text and citations whole', async t => {
|
||||
const f = await setup(t, commit => [{ text: citedBody([fixtureCite(commit)]), unslop: true }]);
|
||||
const reply = JSON.parse((await f.ask()).body);
|
||||
assert.deepEqual(reply.citations, [fixtureCite(f.commit)]); assert.match(reply.text, /unslop/i); assert.equal(f.count(), 1);
|
||||
});
|
||||
|
||||
test('C2-06 send_message typed tool runs every guard code before the valid RESULT', async t => {
|
||||
const f = await setup(t, commit => [{ text: citedBody([fixtureCite(commit)]), tools: guardCases(commit).map(([, body]) => ({ to: 'human', body })) }]);
|
||||
await f.ask(); const results = readFileSync(join(f.runDir, 'fixture-tools.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
|
||||
assert.equal(results.length, 7); results.forEach((r, i) => { assert.equal(r.ok, false); assert.match(r.error, new RegExp(guardCases(f.commit)[i][0])); });
|
||||
assert.equal(f.store.get("SELECT count(*) n FROM messages WHERE from_role='coder'").n, 1);
|
||||
});
|
||||
|
||||
test('C2-06 outside library the old failed text, clip and unslop behavior hold', async t => {
|
||||
const f = await setup(t, () => [{ fail: true }, { text: 'x'.repeat(40000), unslop: true }], { library: false });
|
||||
assert.match((await f.ask()).body, /^The turn failed:/);
|
||||
const reply = (await f.ask()).body; assert.match(reply, /answer cut at 32000 characters/); assert.match(reply, /unslop/i); assert.equal(f.count(), 2);
|
||||
});
|
||||
|
||||
test('C2-06 outside library a refused send keeps the existing no-retry behavior', async t => {
|
||||
const f = await setup(t, () => [{ text: 'answer without send authority' }], { library: false, sendAuthority: false });
|
||||
await f.call(f.human, 'message.send', { to: 'coder', body: 'Fixture question.', class: 'REQUEST' });
|
||||
await until(() => /runner: reply to .* failed:/.test(f.log()));
|
||||
assert.equal(f.count(), 1); assert.equal(f.store.get("SELECT count(*) n FROM messages WHERE from_role='coder'").n, 0);
|
||||
assert.equal(existsSync(join(f.runDir, 'fixture-requests.jsonl')), true);
|
||||
});
|
||||
|
||||
|
||||
// E4: the agent must receive the availability reason in its actual retry turn,
|
||||
// rather than being told only that the source citation was invalid.
|
||||
test('C2-06 the retry turn delivers citation-unavailable reason to the agent', async t => {
|
||||
let sends = 0;
|
||||
const reason = 'The library is busy or unavailable. Retry the answer when the library is available.';
|
||||
const f = await setup(t, commit => [{ text: citedBody([fixtureCite(commit)]) }, { text: citedBody([fixtureCite(commit)]) }], {
|
||||
guard: async () => { if (sends++ === 0) throw new GuardError('citation-unavailable', reason); return null; },
|
||||
});
|
||||
await f.ask(); assert.equal(f.count(), 2);
|
||||
const requests = readFileSync(join(f.runDir, 'fixture-requests.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
|
||||
assert.equal(requests[1].includes(`citation-unavailable: ${reason}`), true);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
// A fixture-only scripted harness. No provider or real credential is used.
|
||||
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { callTool } from '../../src/tools.mjs';
|
||||
|
||||
const root = dirname(process.env.MOSAIC_TURN_MARKER), countFile = join(root, 'fixture-count');
|
||||
const n = existsSync(countFile) ? Number(readFileSync(countFile, 'utf8')) : 0;
|
||||
const steps = JSON.parse(readFileSync(join(root, 'fixture-steps.json'), 'utf8'));
|
||||
const step = steps[n] ?? { fail: true };
|
||||
writeFileSync(countFile, String(n + 1));
|
||||
appendFileSync(join(root, 'fixture-requests.jsonl'), JSON.stringify(process.env.MOSAIC_REQUEST) + '\n');
|
||||
if (step.unslop) appendFileSync(process.env.MOSAIC_UNSLOP_LOG, JSON.stringify({ ev: 'flagged', findings: [{ source: 'fixture', id: 'fixture' }] }) + '\n');
|
||||
for (const args of step.tools ?? []) {
|
||||
let result;
|
||||
try { result = { ok: true, result: await callTool(process.env.MOSAIC_TOOL_SOCKET, 'send_message', args) }; }
|
||||
catch (e) { result = { ok: false, error: e.message }; }
|
||||
appendFileSync(join(root, 'fixture-tools.jsonl'), JSON.stringify(result) + '\n');
|
||||
}
|
||||
if (step.fail) process.exit(3);
|
||||
process.stdout.write(step.text ?? '');
|
||||
writeFileSync(process.env.MOSAIC_TURN_MARKER, 'done\n');
|
||||
@@ -0,0 +1,201 @@
|
||||
// C2-06 (design 5.2, rulings after r11 Q222, Q223 and Q226): the citation guard. The broker runs it
|
||||
// before it stores an agent's message to the human in a business with a library project. It reads
|
||||
// only the citations, never what the text says. Codes are design 5.2's table.
|
||||
import { lstatSync } from 'node:fs';
|
||||
import { git } from '../../project/src/git.mjs';
|
||||
import { classify } from './scope.mjs';
|
||||
import { listTree, readLibrary, readTexts, splitLines } from './reader.mjs';
|
||||
import { LibraryError } from './problems.mjs';
|
||||
import { mirrorPath, readSyncState } from './mirror.mjs';
|
||||
import { plain } from './limits.mjs';
|
||||
|
||||
import { FIXED_TEXTS } from './reply-protocol.mjs';
|
||||
export { GUARD_CODES, FIXED_TEXTS } from './reply-protocol.mjs';
|
||||
|
||||
// Web cites are for these definitions only (design 5.2).
|
||||
export const WEB_CITE_DEFINITIONS = Object.freeze(['research', 'companion']);
|
||||
|
||||
// The reason reaches the runner (sage-3599): the guard's own text naming the check, the cite and the
|
||||
// limit, never a quote or file text, and at most REASON_MAX characters.
|
||||
export const REASON_MAX = 300;
|
||||
export class GuardError extends LibraryError {
|
||||
constructor(code, reason) { reason = reason.slice(0, REASON_MAX); super(code, reason); this.reason = reason; }
|
||||
}
|
||||
const refuse = (code, reason) => { throw new GuardError(code, reason); };
|
||||
|
||||
const SHA = /^[a-f0-9]{40}$/;
|
||||
|
||||
const LIBRARY_CITE = ['path', 'commit', 'lines', 'quote'];
|
||||
const WEB_CITE = ['url', 'quote'];
|
||||
const only = (value, required, optional = []) => plain(value)
|
||||
&& required.every(k => Object.hasOwn(value, k)) && Object.keys(value).every(k => required.includes(k) || optional.includes(k));
|
||||
const text = value => typeof value === 'string' && value.length > 0 && !value.includes('\0');
|
||||
|
||||
function libraryCite(c) {
|
||||
return only(c, LIBRARY_CITE, ['as']) && text(c.path) && c.path.length <= 4096 && typeof c.commit === 'string' && SHA.test(c.commit)
|
||||
&& Array.isArray(c.lines) && c.lines.length === 2 && c.lines.every(n => Number.isSafeInteger(n) && n >= 1) && c.lines[0] <= c.lines[1]
|
||||
&& text(c.quote) && (!Object.hasOwn(c, 'as') || c.as === 'canon');
|
||||
}
|
||||
const webCite = c => only(c, WEB_CITE) && text(c.url) && c.url.length <= 4096 && text(c.quote);
|
||||
|
||||
// A body is free text, or a JSON object {text, citations} that must fit the schema. Returns
|
||||
// { text, citations, cited } where `cited` says the body was the JSON form.
|
||||
export function parseReply(body) {
|
||||
let value;
|
||||
try { value = JSON.parse(body); } catch { return { text: body, citations: [], cited: false }; }
|
||||
if (!plain(value)) return { text: body, citations: [], cited: false };
|
||||
if (!only(value, ['text', 'citations']) || typeof value.text !== 'string' || !Array.isArray(value.citations))
|
||||
refuse('reply-invalid', 'a JSON object reply holds exactly "text", a string, and "citations", a list');
|
||||
value.citations.forEach((c, i) => {
|
||||
if (!libraryCite(c) && !webCite(c))
|
||||
refuse('reply-invalid', `citation ${i + 1} is neither {path, commit, lines, quote, as?} nor {url, quote}`);
|
||||
});
|
||||
return { text: value.text, citations: value.citations, cited: true };
|
||||
}
|
||||
|
||||
const squash = value => value.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Design 5.2's checks, in the table's order. `limits` comes from resolveReplyLimits. `library` reads
|
||||
// git for a library cite (libraryReader below). `web` is { allowed, pages } for this sender: whether
|
||||
// its definition may cite a url, and the session's kept fetches. Returns the stored check result,
|
||||
// { citations: [{ scope, agent, file }] } in body order (agreed with row 225, morgana-0198 and
|
||||
// morgana-0200; a url cite is { scope: 'web', agent: null }), or null for a
|
||||
// plain body. Throws GuardError.
|
||||
export async function checkReply(body, { limits, mapping, library, web }) {
|
||||
if (Object.values(FIXED_TEXTS).includes(body)) return null;
|
||||
const reply = parseReply(body);
|
||||
const { citations } = reply;
|
||||
if (limits.requireCitation && citations.length === 0)
|
||||
refuse('citation-missing', 'replyLimits.requireCitation is true, so a reply carries at least one citation');
|
||||
if (citations.length > limits.citationsPerReply)
|
||||
refuse('citations-over-limit', `replyLimits.citationsPerReply is ${limits.citationsPerReply}; the reply carries ${citations.length}`);
|
||||
if (!reply.cited) return null;
|
||||
const found = [];
|
||||
for (const [i, c] of citations.entries()) {
|
||||
if (!Object.hasOwn(c, 'path')) { found.push(null); continue; }
|
||||
const at = `citation ${i + 1} (${c.path})`;
|
||||
const where = classify(mapping, c.path);
|
||||
if (where.scope === 'unmapped') refuse('citation-invalid', `${at} names a path that isn't mapped`);
|
||||
const lines = await library.lines(c.commit, c.path);
|
||||
if (lines === null) refuse('citation-invalid', `${at} isn't an indexed file at a commit reachable from the served commit`);
|
||||
if (c.lines[1] > lines.length) refuse('citation-invalid', `${at} cites lines past the file's ${lines.length}`);
|
||||
const quote = squash(c.quote);
|
||||
if (!quote || !squash(lines.slice(c.lines[0] - 1, c.lines[1]).join('\n')).includes(quote))
|
||||
refuse('citation-invalid', `${at} quote isn't in lines ${c.lines[0]}-${c.lines[1]}`);
|
||||
found.push(where);
|
||||
}
|
||||
citations.forEach((c, i) => {
|
||||
if (c.as === 'canon' && found[i].scope !== 'canon')
|
||||
refuse('playground-cited-as-canon', `citation ${i + 1} (${c.path}) is ${found[i].scope}, not canon`);
|
||||
});
|
||||
const universes = [...new Set(found.filter(Boolean).map(w => w.universe))];
|
||||
if (universes.length > 1) {
|
||||
const hybrid = universes.find(u => mapping.universes[u]?.kind === 'hybrid');
|
||||
const sources = hybrid ? mapping.universes[hybrid].sources : [];
|
||||
if (!hybrid || !universes.every(u => u === hybrid || sources.includes(u)))
|
||||
refuse('citation-cross-universe', `the citations span universes ${universes.join(', ')}`);
|
||||
}
|
||||
citations.forEach((c, i) => {
|
||||
if (!Object.hasOwn(c, 'url')) return;
|
||||
if (!web.allowed) refuse('citation-unfetched', `citation ${i + 1}: only research and companion cite a url`);
|
||||
const quote = squash(c.quote);
|
||||
const pages = web.pages.filter(p => p.url === c.url || p.finalUrl === c.url);
|
||||
if (!pages.length) refuse('citation-unfetched', `citation ${i + 1}: this session fetched no such url through the bus`);
|
||||
if (!quote || !pages.some(page => squash(page.text).includes(quote))) refuse('citation-unfetched', `citation ${i + 1}: the quote isn't in the url's kept text`);
|
||||
});
|
||||
const out = [];
|
||||
for (const [i, c] of citations.entries())
|
||||
out.push(found[i] ? { scope: found[i].scope, ...await library.origin(c.commit, c.path) } : { scope: 'web', agent: null });
|
||||
return { citations: out };
|
||||
}
|
||||
|
||||
// Git reads for one check against the bare mirror. `lines` is the file's lines at `commit` when the
|
||||
// commit is reachable from the served commit and readLibrary indexes the path there (sage-3595 item 4:
|
||||
// readLibrary at the cited commit stands in for the C2-05a index), else null. Reads are cached per
|
||||
// commit for the one check.
|
||||
export function libraryReader(mirror, served, mapping) {
|
||||
const indexed = new Map(), reach = new Map();
|
||||
const once = (cache, key, read) => {
|
||||
if (!cache.has(key)) cache.set(key, read().catch(() => null));
|
||||
return cache.get(key);
|
||||
};
|
||||
const reachable = commit => once(reach, commit, async () => {
|
||||
if (!mirror || !served) return false;
|
||||
if (await git(mirror, ['cat-file', '-t', commit]) !== 'commit') return false;
|
||||
await git(mirror, ['merge-base', '--is-ancestor', commit, served]);
|
||||
return true;
|
||||
});
|
||||
const index = commit => once(indexed, commit, async () => {
|
||||
const [read, entries] = await Promise.all([readLibrary({ repo: mirror, commit, mapping }), listTree(mirror, commit)]);
|
||||
return { files: new Set(read.files.map(file => file.path)), entries };
|
||||
});
|
||||
return {
|
||||
async lines(commit, path) {
|
||||
if (!await reachable(commit)) return null;
|
||||
const at = await index(commit);
|
||||
if (!at?.files.has(path)) return null;
|
||||
const texts = await readTexts(mirror, [at.entries.find(entry => entry.path === path)]).catch(() => new Map());
|
||||
return texts.has(path) ? splitLines(texts.get(path)) : null;
|
||||
},
|
||||
// One git call: the file's last commit at `commit` (morgana-0200: the Console compares it with the
|
||||
// served tip's, as the cited library commit needn't touch the file) and that commit's trailer.
|
||||
async origin(commit, path) {
|
||||
if (!mirror) return { agent: null, file: null };
|
||||
try {
|
||||
const [file, trailer = ''] = (await git(mirror, ['--literal-pathspecs', 'log', '-1', '--format=%H%x00%(trailers:key=Mosaic-Agent,valueonly)', commit, '--', path])).split('\0');
|
||||
return { agent: trailer.trim() || null, file: SHA.test(file) ? file : null };
|
||||
} catch { return { agent: null, file: null }; }
|
||||
},
|
||||
// The file's last commit at the served tip, for thread.messages (sage-3595), or null.
|
||||
async last(path) {
|
||||
if (!mirror || !served) return null;
|
||||
try {
|
||||
const value = await git(mirror, ['--literal-pathspecs', 'log', '-1', '--format=%H', served, '--', path]);
|
||||
return SHA.test(value) ? value : null;
|
||||
} catch { return null; }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// thread.messages' `last` (sage-3595): each path's last commit at the served tip, one git call per
|
||||
// distinct path. A path maps to null when there is no served commit or the read fails.
|
||||
export async function lastCommits(context, paths) {
|
||||
const { mirror, served } = await servedMirror(context);
|
||||
const reader = libraryReader(mirror, served, null);
|
||||
const out = new Map();
|
||||
for (const path of new Set(paths)) out.set(path, await reader.last(path));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Only an absent mirror or a legitimate empty sync state means there is no served commit.
|
||||
// A lock timeout or unsafe/unreadable state is availability failure, not evidence of an invalid cite.
|
||||
async function servedMirror(context) {
|
||||
const mapping = context.business.projects[context.project];
|
||||
let mirror = null, served = null;
|
||||
try {
|
||||
mirror = mirrorPath(context);
|
||||
try { lstatSync(mirror); }
|
||||
catch (error) {
|
||||
if (error.code === 'ENOENT') return { mapping, mirror: null, served: null };
|
||||
throw error;
|
||||
}
|
||||
served = (await readSyncState(mirror, { branch: mapping.branch })).served ?? null;
|
||||
} catch {
|
||||
refuse('citation-unavailable', 'The library is busy or unavailable. Retry the answer when the library is available.');
|
||||
}
|
||||
return { mapping, mirror, served };
|
||||
}
|
||||
|
||||
// The broker's entry: the library project's mirror at its served commit, then checkReply. `context`
|
||||
// is { business, project, dataRoot }. `definition` is the sender's role definition, which decides only
|
||||
// whether a url cite may pass; `pages` is the broker's page store for the sender's session.
|
||||
export async function guardReply(context, body, { limits, definition, pages }) {
|
||||
const web = { allowed: WEB_CITE_DEFINITIONS.includes(definition), pages };
|
||||
// Plain replies, web-only replies and the runner's exact fallback texts don't read Git.
|
||||
// In particular, a busy mirror must not prevent the fixed fallback from being delivered.
|
||||
const reply = parseReply(body);
|
||||
if (!reply.citations.some(c => Object.hasOwn(c, 'path')))
|
||||
return checkReply(body, { limits, mapping: context.business.projects[context.project], library: null, web });
|
||||
const { mapping, mirror, served } = await servedMirror(context);
|
||||
return checkReply(body, { limits, mapping, library: libraryReader(mirror, served, mapping), web });
|
||||
}
|
||||
@@ -11,3 +11,5 @@ export { SYNC_STATE_FILE, mirrorPath, ensureMirror, withLibraryLock, servedCommi
|
||||
export { syncCommand, sync, ASKPASS, SLOT, SYNC_INTERVAL_MS } from './sync.mjs';
|
||||
export { buildIndex, indexFor, clearIndexCache, indexCacheSize } from './index-build.mjs';
|
||||
export { sees, find, get, search, manuscript, universeInfo, QUERY_REPLY_CAP, LOOKUP_LIMIT, MANUSCRIPT_LINES } from './query.mjs';
|
||||
export { REPLY_LIMITS, resolveReplyLimits, checkReplyLimits } from './limits.mjs';
|
||||
export { GUARD_CODES, FIXED_TEXTS, WEB_CITE_DEFINITIONS, REASON_MAX, GuardError, parseReply, checkReply, libraryReader, guardReply, lastCommits } from './guard.mjs';
|
||||
@@ -0,0 +1,40 @@
|
||||
// C2-06 (design 5.2): the business file's replyLimits, read by the citation guard and written only
|
||||
// by the human's library.limits.set. Pure, so the business package's organization writer imports it.
|
||||
|
||||
export const plain = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
// The replyLimits keys, their defaults and their valid values. A key is read only when Object.hasOwn
|
||||
// finds it and its value passes `valid`; anything else is the default (webby-0144 R3).
|
||||
export const REPLY_LIMITS = Object.freeze({
|
||||
requireCitation: { default: false, valid: v => typeof v === 'boolean' },
|
||||
citationsPerReply: { default: 20, valid: v => Number.isInteger(v) && v >= 1 && v <= 1000 },
|
||||
});
|
||||
|
||||
// `read` is { readable: false } when the business file is missing, unreadable, not JSON or not an
|
||||
// object at check time, else { readable: true, document }. An unreadable file checks at the defaults
|
||||
// except requireCitation, which is true so a setting she turned on doesn't fail open (webby-0151 R2).
|
||||
export function resolveReplyLimits(read) {
|
||||
const limits = {}, fallback = [];
|
||||
const unreadable = !read?.readable || !plain(read.document);
|
||||
const raw = unreadable ? undefined : read.document.replyLimits;
|
||||
for (const [key, { default: value, valid }] of Object.entries(REPLY_LIMITS)) {
|
||||
if (plain(raw) && Object.hasOwn(raw, key) && valid(raw[key])) limits[key] = raw[key];
|
||||
else {
|
||||
limits[key] = value;
|
||||
// A key fell back when replyLimits is there but isn't an object, or holds the key with a value
|
||||
// outside the table. A key left out is simply its default.
|
||||
if (raw !== undefined && (!plain(raw) || Object.hasOwn(raw, key))) fallback.push(key);
|
||||
}
|
||||
}
|
||||
if (unreadable) limits.requireCitation = true;
|
||||
return { ...limits, fallback, unreadable };
|
||||
}
|
||||
|
||||
// library.limits.set's check: the whole object is replaced, a key left out means its default, and an
|
||||
// unknown key or a value outside the table refuses. Returns the object to write.
|
||||
export function checkReplyLimits(value) {
|
||||
if (!plain(value)) return null;
|
||||
for (const [key, v] of Object.entries(value))
|
||||
if (!Object.hasOwn(REPLY_LIMITS, key) || !REPLY_LIMITS[key].valid(v)) return null;
|
||||
return structuredClone(value);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// C2-06: shared pure protocol constants for the host guard and sandboxed runner.
|
||||
export const GUARD_CODES = Object.freeze(['reply-invalid', 'citation-missing', 'citations-over-limit', 'citation-invalid',
|
||||
'playground-cited-as-canon', 'citation-cross-universe', 'citation-unfetched', 'citation-unavailable']);
|
||||
|
||||
// The runner's four fixed texts in a library business. The guard accepts exactly these bodies when
|
||||
// requireCitation is true; an agent that sends one gains nothing it couldn't say anyway.
|
||||
export const FIXED_TEXTS = Object.freeze({
|
||||
empty: '(the session gave no answer)',
|
||||
failed: '(the turn failed)',
|
||||
uncited: '(no answer passed the citation check)',
|
||||
tooLong: '(the answer was too long to send)',
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { checkReply, GuardError, libraryReader, parseReply, FIXED_TEXTS } from '../src/guard.mjs';
|
||||
import { resolveReplyLimits, checkReplyLimits } from '../src/limits.mjs';
|
||||
import { readLibrary, listTree } from '../src/reader.mjs';
|
||||
import { indexFor } from '../src/index-build.mjs';
|
||||
import { BASE, MARKER, mapping, repo, git } from './helpers.mjs';
|
||||
|
||||
const defaults = resolveReplyLimits({ readable: true, document: {} });
|
||||
const body = citations => JSON.stringify({ text: 'A fixture answer.', citations });
|
||||
const cite = (commit, extra = {}) => ({ path: 'Book One/Characters/Mara Vell.md', commit, lines: [9, 9], quote: 'Grey eyes.', ...extra });
|
||||
const context = r => ({ limits: defaults, mapping: mapping(), library: libraryReader(r.repo, r.commit, mapping()), web: { allowed: true, pages: [] } });
|
||||
const refusal = code => e => e instanceof GuardError && e.code === code && typeof e.reason === 'string' && e.reason.length <= 300;
|
||||
|
||||
test('C2-06 reply schema: free bodies, exact objects and safe refusal reasons', async t => {
|
||||
const c = context(repo(t));
|
||||
for (const text of ['plain', 'null', '42', '[]', '"text"', '{broken']) {
|
||||
assert.equal(parseReply(text).cited, false);
|
||||
assert.equal(await checkReply(text, c), null);
|
||||
}
|
||||
assert.deepEqual(await checkReply(body([]), c), { citations: [] });
|
||||
for (const value of [{}, { text: 'x', citations: [], scope: 'canon' }, { text: 1, citations: [] },
|
||||
{ text: 'x', citations: {} }, { text: 'x', citations: [{ url: 'https://invalid.test', quote: 'x', agent: 'forged' }] },
|
||||
{ text: 'x', citations: [cite('a'.repeat(40), { as: 'supporting' })] }])
|
||||
await assert.rejects(checkReply(JSON.stringify(value), c), refusal('reply-invalid'));
|
||||
});
|
||||
|
||||
test('C2-06 citation setting and all four exact fixed texts', async t => {
|
||||
const c = context(repo(t)); c.limits = { ...defaults, requireCitation: true };
|
||||
await assert.rejects(checkReply('plain', c), refusal('citation-missing'));
|
||||
await assert.rejects(checkReply(body([]), c), refusal('citation-missing'));
|
||||
for (const text of Object.values(FIXED_TEXTS)) {
|
||||
assert.equal(await checkReply(text, c), null);
|
||||
await assert.rejects(checkReply(text + ' ', c), refusal('citation-missing'));
|
||||
}
|
||||
});
|
||||
|
||||
test('C2-06 default citation cap passes 20 and refuses 21, even with optional citations', async t => {
|
||||
const r = repo(t), c = context(r);
|
||||
assert.equal((await checkReply(body(Array(20).fill(cite(r.commit))), c)).citations.length, 20);
|
||||
await assert.rejects(checkReply(body(Array(21).fill(cite(r.commit))), c), refusal('citations-over-limit'));
|
||||
});
|
||||
|
||||
test('C2-06 real Git citation scope, reachability, lines and normalized quotes', async t => {
|
||||
const r = repo(t), c = context(r);
|
||||
assert.equal((await checkReply(body([cite(r.commit, { quote: 'Grey\n eyes.' })]), c)).citations[0].scope, 'canon');
|
||||
for (const extra of [{ path: 'Inbox Note.md', lines: [1, 1], quote: MARKER },
|
||||
{ path: 'Book One/Board.canvas' }, { path: 'Book One/Characters/Missing.md' },
|
||||
{ commit: '0'.repeat(40) }, { commit: git(r.repo, 'rev-parse', 'HEAD^{tree}') },
|
||||
{ lines: [999, 999] }, { quote: 'not in her fixture' }])
|
||||
await assert.rejects(checkReply(body([cite(r.commit, extra)]), c), refusal('citation-invalid'));
|
||||
// A future commit exists locally but is not reachable from the served commit.
|
||||
writeFileSync(join(r.repo, 'Inbox Note.md'), 'new\n'); git(r.repo, 'add', '-A'); git(r.repo, 'commit', '-qm', 'future');
|
||||
await assert.rejects(checkReply(body([cite(git(r.repo, 'rev-parse', 'HEAD'))]), c), refusal('citation-invalid'));
|
||||
assert.equal((await checkReply(body([cite(r.commit)]), c)).citations[0].scope, 'canon');
|
||||
});
|
||||
|
||||
test('C2-06 playground cites pass but cannot state canon', async t => {
|
||||
const r = repo(t), c = context(r), p = cite(r.commit, { path: 'Book One/Playground/What If.md', lines: [4, 4], quote: MARKER });
|
||||
assert.equal((await checkReply(body([p]), c)).citations[0].scope, 'playground');
|
||||
await assert.rejects(checkReply(body([{ ...p, as: 'canon' }]), c), refusal('playground-cited-as-canon'));
|
||||
});
|
||||
|
||||
test('C2-06 independent universes refuse; a hybrid and only its sources pass', async t => {
|
||||
const r = repo(t, { ...BASE, 'Crossover/Chapters/01.md': 'A hybrid fixture.\n' }), c = context(r);
|
||||
const other = cite(r.commit, { path: 'Second World/Chapters/01.md', lines: [1, 1], quote: MARKER });
|
||||
await assert.rejects(checkReply(body([cite(r.commit), other]), c), refusal('citation-cross-universe'));
|
||||
c.mapping = mapping(e => { e.universes.cross = { kind: 'hybrid', name: 'Cross', sources: ['harbor', 'second'], books: ['Crossover'] }; return e; });
|
||||
c.library = libraryReader(r.repo, r.commit, c.mapping);
|
||||
const hybrid = cite(r.commit, { path: 'Crossover/Chapters/01.md', lines: [1, 1], quote: 'hybrid fixture' });
|
||||
assert.equal((await checkReply(body([hybrid, cite(r.commit), other]), c)).citations.length, 3);
|
||||
});
|
||||
|
||||
test('C2-06 web cites require allowed definition, this session fetch and kept quote', async t => {
|
||||
const c = context(repo(t)), web = { url: 'https://fixture.invalid/page', quote: 'kept text' };
|
||||
await assert.rejects(checkReply(body([web]), c), refusal('citation-unfetched'));
|
||||
c.web.pages = [{ url: web.url, finalUrl: 'https://fixture.invalid/final', text: 'The kept\n text is here.' }];
|
||||
assert.deepEqual(await checkReply(body([web]), c), { citations: [{ scope: 'web', agent: null }] });
|
||||
assert.equal((await checkReply(body([{ ...web, url: c.web.pages[0].finalUrl }]), c)).citations.length, 1);
|
||||
c.web.pages.push({ url: web.url, text: 'A later fetch.' });
|
||||
assert.equal((await checkReply(body([{ ...web, quote: 'later fetch' }]), c)).citations.length, 1);
|
||||
await assert.rejects(checkReply(body([{ ...web, quote: 'discarded bytes' }]), c), refusal('citation-unfetched'));
|
||||
c.web.allowed = false;
|
||||
await assert.rejects(checkReply(body([web]), c), refusal('citation-unfetched'));
|
||||
});
|
||||
|
||||
test('C2-06 file origin is the path commit and trailer, while last follows served changes', async t => {
|
||||
const r = repo(t), path = 'Book One/Characters/Mara Vell.md';
|
||||
writeFileSync(join(r.repo, path), 'Agent fixture.\n'); git(r.repo, 'add', '-A');
|
||||
git(r.repo, 'commit', '-qm', 'Note\n\nMosaic-Agent: acme/scribe');
|
||||
const file = git(r.repo, 'rev-parse', 'HEAD');
|
||||
writeFileSync(join(r.repo, 'Inbox Note.md'), 'Unrelated.\n'); git(r.repo, 'add', '-A'); git(r.repo, 'commit', '-qm', 'unrelated');
|
||||
const cited = git(r.repo, 'rev-parse', 'HEAD'), reader = libraryReader(r.repo, cited, mapping());
|
||||
assert.notEqual(cited, file);
|
||||
assert.deepEqual(await reader.origin(cited, path), { agent: 'acme/scribe', file });
|
||||
assert.equal(await reader.last(path), file);
|
||||
writeFileSync(join(r.repo, path), 'Human fixture.\n'); git(r.repo, 'add', '-A'); git(r.repo, 'commit', '-qm', 'human');
|
||||
assert.notEqual(await libraryReader(r.repo, git(r.repo, 'rev-parse', 'HEAD'), mapping()).last(path), file);
|
||||
});
|
||||
|
||||
test('C2-06 origin uses literal paths even when a filename contains a glob', async t => {
|
||||
const path = 'Book One/Characters/Mara*.md';
|
||||
const r = repo(t, { ...BASE, [path]: 'Literal fixture.\n' }), original = r.commit;
|
||||
writeFileSync(join(r.repo, 'Book One/Characters/Mara Vell.md'), 'Other fixture.\n'); git(r.repo, 'add', '-A');
|
||||
git(r.repo, 'commit', '-qm', 'Other path\n\nMosaic-Agent: acme/other');
|
||||
const reader = libraryReader(r.repo, git(r.repo, 'rev-parse', 'HEAD'), mapping());
|
||||
assert.deepEqual(await reader.origin(git(r.repo, 'rev-parse', 'HEAD'), path), { agent: null, file: original });
|
||||
assert.equal(await reader.last(path), original);
|
||||
});
|
||||
|
||||
// C2-05a landed: compare the real lookup index with the guard's cited-commit reader.
|
||||
// A guard that admits unmapped files, or excludes a supporting file, breaks this fixture.
|
||||
test('C2-06 cited-commit guard reads exactly the C2-05a indexed file set with supporting scope', async t => {
|
||||
const r = repo(t), map = mapping(entry => {
|
||||
entry.universes.harbor.supporting = ['Book One/Playground/']; return entry;
|
||||
});
|
||||
const read = await readLibrary({ repo: r.repo, commit: r.commit, mapping: map });
|
||||
const index = await indexFor({ repo: r.repo, commit: r.commit, mapping: map });
|
||||
const reader = libraryReader(r.repo, r.commit, map), admitted = [];
|
||||
for (const entry of await listTree(r.repo, r.commit)) {
|
||||
if (await reader.lines(r.commit, entry.path) !== null) admitted.push(entry.path);
|
||||
}
|
||||
assert.deepEqual(admitted.sort(), [...index.files.keys()].sort());
|
||||
assert.deepEqual(admitted, read.files.map(f => f.path).sort());
|
||||
assert.equal(index.files.get('Book One/Playground/What If.md').scope, 'supporting');
|
||||
assert.equal(index.files.has('Inbox Note.md'), false);
|
||||
assert.equal(index.files.has('Book One/Board.canvas'), false);
|
||||
});
|
||||
|
||||
test('C2-06 reply limits fall back independently without coercion or inherited keys', () => {
|
||||
for (const value of ['true', 1, null, [true], {}]) {
|
||||
const r = resolveReplyLimits({ readable: true, document: { replyLimits: { requireCitation: value, citationsPerReply: 5 } } });
|
||||
assert.equal(r.requireCitation, false); assert.equal(r.citationsPerReply, 5); assert.deepEqual(r.fallback, ['requireCitation']);
|
||||
}
|
||||
for (const value of [0, 1001, 2.5, true, '5']) {
|
||||
const r = resolveReplyLimits({ readable: true, document: { replyLimits: { requireCitation: true, citationsPerReply: value } } });
|
||||
assert.equal(r.requireCitation, true); assert.equal(r.citationsPerReply, 20); assert.deepEqual(r.fallback, ['citationsPerReply']);
|
||||
}
|
||||
for (const value of [null, [], 1, 'settings', undefined]) {
|
||||
const r = resolveReplyLimits({ readable: true, document: { replyLimits: value } });
|
||||
assert.equal(r.requireCitation, false); assert.equal(r.citationsPerReply, 20);
|
||||
}
|
||||
assert.equal(resolveReplyLimits({ readable: true, document: { replyLimits: Object.create({ requireCitation: true }) } }).requireCitation, false);
|
||||
for (const document of [undefined, null, [], 'invalid']) assert.equal(resolveReplyLimits({ readable: true, document }).requireCitation, true);
|
||||
assert.equal(resolveReplyLimits({ readable: false }).requireCitation, true);
|
||||
assert.deepEqual(checkReplyLimits({ requireCitation: true, citationsPerReply: 1000 }), { requireCitation: true, citationsPerReply: 1000 });
|
||||
for (const value of [{ unknown: 1 }, { requireCitation: 'true' }, { citationsPerReply: 0 }, null, []]) assert.equal(checkReplyLimits(value), null);
|
||||
});
|
||||
@@ -61,6 +61,7 @@ export const RUNNER_FILES = Object.freeze([
|
||||
"packages/bus/src/landing.mjs",
|
||||
"packages/business/src/vocabulary.mjs",
|
||||
"packages/plugins/src/kinds.mjs",
|
||||
"packages/library/src/reply-protocol.mjs",
|
||||
"packages/processing-control/src/state.mjs",
|
||||
"packages/tasks/src/requirements.mjs",
|
||||
].map((p) => join(REPO, p)));
|
||||
|
||||
Reference in new issue
Block a user