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.
125 lines
8.2 KiB
Markdown
125 lines
8.2 KiB
Markdown
# 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.
|