Schemas: the Role manifest, the pass record, the feed, and one envelope for all (#28)
Why
Slice 0's exit asks for an example of every schema slice 1 uses. After #210 the pass record, the feed, a Run's applied.json and outcome.json, and the Role manifest had none, and §3.1's envelope rule, "every message carries schema, an identifier and the release", reached too far to hold as written. The working rulings on the issue, with the Maintainer's two answers folded in (applied.json and outcome.json are this story's fourth criterion; the feed carries the title, the next actor and the decision required), are the spec.
What is decided
One envelope, on what crosses a boundary whole (decision record 0039, provisional number). Each schema is in one class, held by ENVELOPE in tools/check_examples.py:
| Class | Carries | Kinds |
|---|---|---|
| document | schema, its identity, release | task, result, applied, outcome (run); pass, view-meta, view-items (pass) |
| record | schema | facts, issue-facts, decision, issue-decision, finding |
| part | nothing | effect, disposition, coverage, run-name, run-record, view-item |
| data | schema | config, hooks, lifecycle, authority, apps, rubric, acceptance, trial, role, feed |
config and hooks gain schema, and so do config/defaults.json, config/hooks.json and their counter-examples. Every reader of those files reads by key and ignores it.
What each schema holds, and what may not be written
| Schema | Holds | May not hold |
|---|---|---|
pass/1 | now, the whole context (every key, always), every run record, each read's date and age, and per item its facts, Decision and the minted Run id. Its example is built by calling the Kernel and replays with the repo's own data files | A title: the facts have no field for one (two new counter-examples). A copy of the configuration |
view-meta/1, view-items/1, view-item | The pass that wrote them; per item the Decision's codes, next_actor, decision_required, age, attempts, runs, cost, sources, url, and the title under untrusted | Any other text; a title beside untrusted; a choice no person can make |
feed/1, config/feed.json | Who acts next, by outcome, then attention, then State; what is asked, one row per needs_you reason, a question and choices as codes | A row for a reason the Kernel never gives needs_you, a reason twice, an attention class with no rule or two (_table) |
applied/1, outcome/1 | Each Effect apply met, with its state, the node id read back and a reason code, and each posted Finding's finding/1; how a Run ended, in the run record's codes | A body; a state in words; a fault the design does not know |
role/1 | §3.11's manifest as JSON; each Intent's tier, permitted Effects, budget class and required eval set | A kind or tier the design does not know; an Effect a Result cannot request |
CODES_ONLY gains the new kinds and coverage; its one exemption, body, gains untrusted. The walk no longer follows a reference into a kind outside the set: the existing records are held by their own rules and are not swept here. tools/check_authority.py holds what a manifest owns, shadows and raises to finding/1's categories.
Where the rulings and the design differed, and the closed forms taken
- A Decision and a Finding carry no
id(the Decision) and norelease(both): §3.4 and §3.7 amended. - The pass record's example replays with the repository's own
config/defaults.jsonand Lifecycles, so its issue isnot_served(issues are not served there); the pull request is admitted with its Run id. attemptsis a list of{intent, count}, not an object keyed by Intent: the validator cannot constrain a key.next_actorlives infeed.schema.json#/$defs/actor, andview-itemreferences it;by_outcomeis its own list inconfig/feed.json.- The
paused,pause_unreadableandunmerged_closesreasons are shown to need a person by the table test itself, beside the existing Kernel tests the check names.
Reviews
A fresh-context Claude review, a security pass (subagents) and Muse Wonder Full read the first head, 5109ac7; the Kernel and the simulator are untouched, so AGY was not run. Every finding was verified and folded into one fix round, 6fae96f:
config/feed.jsonmatched no protected path, so a model-review-exempt change could rewrite what the Maintainer is asked (Muse's major; the security pass noted it). It joins the protected paths inconfig/hooks.json, as every other file inconfig/does.- The codes-only walk stopped at a reference into
run-record, so a pass record'srun,roleandintentwere free strings (Muse's second major, the security pass's low). A run record holds codes now and joinsCODES_ONLY.finding/1, whichapplied/1carries, is the Factory's own record and stays as decision 0038 left it; the schema titles and the record now say so instead of claiming no text. - The
untrustedexemption applied to any schema, andbodyexempted an object of strings (Claude). Only the feed marks text untrusted, and a body is one string; both have a test and a mutant. - A part could carry a
release; a document's identity and release were not held to be strings (Claude, Muse, security). Both held, with mutants. - The table check let a State or an outcome be ruled twice (Claude, Muse, security). Refused now, and the feed test holds the table's rules to the design's: admit or act is the Factory's, then by class, then by State.
- The manifest's categories in
tools/check_authority.pyhad no test and no mutant (Claude, Muse). Added. - The replay test said every act mints a Run; only an approval does (Claude). Fixed.
config/hooks.jsonhad been reformatted whole; it keeps its formatting with the one key added (Claude). The design's Decision and Finding examples validate against their schemas now (Claude).
Each of the twelve new mutants was applied by hand to a scratch copy and its named test seen to fail; CI's shards are the final word. Not taken, and recorded as waiting on roles/ or controller/: an owner with no App or an author that owns a category (the manifest's cross-field rules), a feed item whose url is not its item's, and an evidence alphabet beyond no-whitespace.
Verify
make verify passed on a fresh git archive export with PATH=/usr/bin:/bin SIM_SEEDS=40 at 6fae96f. (At 5109ac7 two timing tests of the identity hook ran over their 5-second bound while three reviews ran on the same machine; both pass alone and on the idle rerun.)
Closes #28
🤖 Generated with Claude Code