Schemas: Task, Result, Effect and Disposition (#19)

Merged
DO
17h ago
+2186-40

Why

Slice 1's Runs need the contract of decision 0004 as schemas a validator can refuse: the Task the Harness builds, the Result the Engine returns, the Effects a Result may ask for and the Disposition on a Finding. The working rulings on the issue (the architect's, with the Maintainer's comments) are the spec; where the issue or the design said otherwise, the rulings were followed and the design amended in the same change.

What each schema holds, and what the model may not write

SchemaHoldsThe model may not write
task/1run, release, role, intent, item, content id, Finding ids, texts ({id, trust, source, body}), limits, tier, permitted Effects, result schema. Built by the Harness from the DecisionAnything: no field comes from the model. Only a text's body is text, and it goes to the model as data
result/1status, reason (a code or null), dispositions, raised Findings ({ref, location, category, severity, criterion, adopted_from}), effects, coverage (by reference to coverage/1), usagerelease and usage (the Harness fills them); failed and superseded (the Harness or apply); a Role, an App, an actor, a content_id, a commit, or a Finding's id, source or surface: the schema has no such field
effect/1One list for each type: submit_review (comments by raised-Finding ref, body), create_check_run (annotations), resolve_thread (thread id), comment (on the item or a thread). $defs/type is the enum the Task's permitted_effects usesA review event or commit (a Role's review never approves); a check run's name or conclusion; add_label, assign, enqueue_merge or dequeue_merge, which are the Controller's acts
disposition/1finding and one half: proposal {outcome, reason} or decision {result, reason}by or content_id: both are the Task's. The schema accepts neither half and both; apply checks that (waiting on harness/)
run-name/1sf <repo>#<n> <intent> <role> <content_id> <run>, the whole content id and the Run id with their own prefixes, as #161 left it to this story

Held by tools/check_examples.py: in these five schemas every string is a code (enum, const or a pattern anchored at both ends) unless the property is named body, through references too. tools/jsonschema_lite.py now resolves <kind>.schema.json and <kind>.schema.json#/$defs/<name> in a mapping the caller passes, the referenced schema being the root of its own references; a reference with no mapping is a SchemaError, and the validator still reads no file. tools/check_authority.py holds a raised Finding's category and severity to finding/1's.

Where the rulings depart from the issue and the design, and the choices made

  • A Role's Finding record lives in its Run's applied.json, written by apply after it posts the surface, not in result.json (design §3.3, §3.7): the Result cannot hold a surface key that does not exist yet.
  • A Disposition in a Result has no by and no content_id (§3.8); a Result has no bundle or preflight until the author Intents (§3.5).
  • The run-name carries the whole content id and the Run id as sha256:… and r_…, not c: and r: (§3.10). GitHub's bound on a run-name's length is unverified (about 160 characters at most).
  • Shapes the rulings left open, taken in closed form and recorded in the decision: usage requires every field; model and session are strings even for a Run the CLI never answered, because the Harness names both before the call; effects in a failed Result is {}; a submit_review requires body, text or null.
  • The decision record is written as 0038, the next free number now; it is renamed at enqueue if another PR takes it first.

Reviews

A fresh-context Claude review, a security pass (subagents) and Muse Wonder Full read the first head, 11071c9; the Kernel and the simulator are untouched, so AGY was not run. Every finding was verified and folded into one fix round, fecd592:

  • The first head failed the context check (the record named the Run's artifacts as paths; three rules over the line limit). Fixed.
  • "Anchored at both ends" was judged by a pattern's first and last characters, so ^a$|b$ and ^a\$ passed as codes (Claude, Muse, security). A pattern is now anchored by its structure, and each anchor, the alternative and the escaped dollar have a mutant; the test was widened so a single-anchor mutant dies (Muse's major).
  • A $ref cycle recursed without bound, and a keyword beside a $ref was silently dropped (Muse, security). Both are a SchemaError now, with tests and mutants.
  • The codes-only walk passed over a reference it could not follow (Muse). It reports one now.
  • usage.seconds was nullable against the record's closed form (Muse). It is a number now; a counter-example holds it.
  • location.path and an adopted surface's key admitted control characters; the Task's item.repo admitted what the run-name refuses (security). Tightened.
  • Rows added for apply and the Harness, waiting on harness/ (security, Muse): an Effect's thread is on the Task's item; a Disposition is on a listed Finding, given once; status and reason are coherent; a text's trust class comes from the facts' actor.

CodeRabbit then found that the codes-only walk passed over a reference cycle, or a keyword beside a $ref, in a branch no example takes (the validator reads a reference only where a value reaches it); f561b9a holds both, with a test case and a mutant each. The first CI run put three mutant shards past their 25 minutes: a mutant that names no test runs the whole suite, with the simulation, before it is read as killed, and the thirteen named none. d9be3c8 names each one's test, as the hook mutants do, and makes the codes-only test ask the walk in memory with one front-door run. Each of the thirteen new mutants was applied by hand to a scratch copy and its named test seen to fail; CI's shards are the final word.

Verify

make verify passed on a fresh git archive export with PATH=/usr/bin:/bin SIM_SEEDS=40.

Closes #19

🤖 Generated with Claude Code