Guard, limits, M11 fence, broker guard/page store/check event, BusError reason, runtime wiring and the CLI replies verb. No runner work, no tests, nothing run. BUILD.md lists the rulings, the row 225 contract and what is left.
8.2 KiB
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 forcesrequireCitationtrue. A key falls back to its default whenreplyLimitsis not an object or the key holds an invalid value.packages/library/src/guard.mjs(new):GUARD_CODESin 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:replyLimitsis a known top key. The validated output doesn't carry it.packages/business/src/organization.mjs: the M11 fencereplyLimitsFence(old, document, actor)compares againstold.human, refuses the tester and any<definition>.<instance>ofold.roles, and is called for bothdocumentandwritten.setReplyLimitsandreadRaware exported.packages/bus/src/broker.mjs: the guard call, the page store, the check event,library.limits.set,library.statusgainsreplyLimits,role.claimgainslibrary,thread.messagesgainscheckandlast.packages/bus/src/server.mjs,client.mjs:BusError.reason(sage-3599).packages/bus/src/runtime.mjs: guard deps and thelibrary.limits.setdispatch.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 forlimits 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:
- Supporting scope: a stub classify test now. Before in-review, rebase onto row 283's landing
(honker/row283-build
bd2c1c4eawhen ruled) and add two real-fixture tests: a supporting cite withas: "canon"refusesplayground-cited-as-canon, and the stored check result namessupporting. - runtime.mjs is approved for the guard wiring and the one
library.limits.setbranch. List its hunks here, name it in the manifest, and drift-check it after the 216 and 285 rebases. - The check result is an
action.allowedevent withaction: "message.checked". The events kind CHECK atpackages/bus/src/migrations/3b.sql:9has nomessage.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. - 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.
- 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.
- Old
role.claimpins stay as they are. render.mjs:6andrender.test.mjs:58belong to row 225. Leave them.thread.messagesrows that carrycheckgetlastper 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.checkedevent:{citations: [...]}in body order. - A path cite:
{scope, agent, file}.scopeis the classify scope.agentis the cite's file's last commit'sMosaic-Agenttrailer at the cited commit, or null.fileis that commit's 40-hex id, or null. - A url cite:
{scope: "web", agent: null}. - On
thread.messages, each path cite gainslast: the file's last commit at the served tip (40-hex or null). The Console compareslastwithfile. - Rows without
checkare unchanged. Nochangedboolean. The webui passes rows through unchanged.
Hunks outside the brief's file list
packages/bus/src/runtime.mjs: importssetReplyLimits; the library deps gainguard,replyLimits(organizationWriterreadRaw, or{readable: false}with no organization),lastandGuardError; the organization dispatch gainslibrary.limits.set.packages/bus/src/server.mjs: arefusal(e, other)helper above serve's doc comment builds every BusError envelope and addsreasononly when set. The synchronous request path accepts a Promise result (the guard is async), with a 120 s timeout, and releasesownwhen it settles.packages/bus/src/client.mjs:new BusError(r.error, reason)withreasona 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.messagesfinds it through the unique index onevents.id. - With no organization configured,
replyLimitsreads 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
repliesverb reads the revision itself fromlimits. - 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, andunslop.sent()is not called, so the note waits. - A body that parses as a JSON object with a string
textgetsunslop.note()appended totext, is serialized again and is never clipped. A plain body keepsclip(body) + unslop.note(). - On a refusal whose code is in
GUARD_CODES, orrequest-too-largefrom the client, or the broker'sinvalid-requestfromstring(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 anderr.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.humancompared instead ofold.human.- The check event split from the insert transaction.
- The client.mjs reason pass-through dropped.