Schemas: the Role manifest, the pass record, the feed, and one envelope for all (#28)

Merged
DO
13h ago
+5639-49

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:

ClassCarriesKinds
documentschema, its identity, releasetask, result, applied, outcome (run); pass, view-meta, view-items (pass)
recordschemafacts, issue-facts, decision, issue-decision, finding
partnothingeffect, disposition, coverage, run-name, run-record, view-item
dataschemaconfig, 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

SchemaHoldsMay not hold
pass/1now, 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 filesA title: the facts have no field for one (two new counter-examples). A copy of the configuration
view-meta/1, view-items/1, view-itemThe pass that wrote them; per item the Decision's codes, next_actor, decision_required, age, attempts, runs, cost, sources, url, and the title under untrustedAny other text; a title beside untrusted; a choice no person can make
feed/1, config/feed.jsonWho acts next, by outcome, then attention, then State; what is asked, one row per needs_you reason, a question and choices as codesA 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/1Each 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 codesA 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 setA 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 no release (both): §3.4 and §3.7 amended.
  • The pass record's example replays with the repository's own config/defaults.json and Lifecycles, so its issue is not_served (issues are not served there); the pull request is admitted with its Run id.
  • attempts is a list of {intent, count}, not an object keyed by Intent: the validator cannot constrain a key.
  • next_actor lives in feed.schema.json#/$defs/actor, and view-item references it; by_outcome is its own list in config/feed.json.
  • The paused, pause_unreadable and unmerged_closes reasons 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.json matched 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 in config/hooks.json, as every other file in config/ does.
  • The codes-only walk stopped at a reference into run-record, so a pass record's run, role and intent were free strings (Muse's second major, the security pass's low). A run record holds codes now and joins CODES_ONLY. finding/1, which applied/1 carries, 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 untrusted exemption applied to any schema, and body exempted 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.py had 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.json had 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