Repository navigation
ADR-0027 amendment: a rehearse check sub-kind for non-binding rehearsal, with paired vectors (#241) - #244
Conversation
…al (#241) Adds a seventh check sub-kind, rehearse: a non-binding dry evaluation of an artifact the host declares, whose result carries no authority and whose execution writes no audit, resource or run state. check/rehearse accepts the effects pure or read only. Both validators (toolTaxonomy.ts, go/adapter/wire/tooltaxonomy.go) accept rehearse under check, refuse it with read-external or act ("tool taxonomy rehearse admits only a pure or read effect") and keep refusing it under any other operation. Host admission (hostTaxonomyProblem, HostTaxonomyProblem) refuses a rehearse declaration whose description does not begin "Rehearsal, non-binding". The evaluate family, the six-tool Runtime ceiling, approvals and hints are unchanged; a rehearse tool derives readOnly and idempotent hints and approval none like any read check. The tool-taxonomy schema gains the enum value and a condition bounding its effect. Handwritten vectors: two rehearse records and four refused host declarations with their reasons in contract/vectors/taxonomy/declarations.json, and the rehearsal rows of contract/testdata/taxonomy/tools.json. Both languages' tests read them; five mutation rows hold the new refusals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
…#241) F1: the ADR now says what the package enforces. Host admission (declareSources, DeclareSources) refuses a rehearse declaration unless its description begins with the words "Rehearsal, non-binding"; tools that reach the gate by other entry points carry whatever admission they passed. Every TypeScript engine start runs host admission; the Go gate's Open, SetOffer and EnrollContext and eino's Observe check taxonomy only. A test per language holds the statement: admission and engine start refuse, the shared validator and the Go gate's SetOffer and EnrollContext do not re-read the description. F2: the words lead the host's declared description, not necessarily the offer; a tool with a source is offered framed. F3: the words must be followed by the end, a space, tab or line break, or one of .,;:!?) (rehearsalDescribed, RehearsalDescribed). New handwritten rows: "Rehearsal, non-bindingly binding: ..." refused, the bare words admitted. New mutation row for the boundary; the description rows follow the helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Clean-room review record: agent PR #244 (ADR-0027 amendment,
|
| What | Command (in <scratch>/repo) |
Result |
|---|---|---|
| install, build | npm ci; npm run build (Node 22, resident-memory cap) |
ok (tsc typechecks the new test) |
| TS taxonomy tests | npx vitest run toolTaxonomy.test.ts (in packages/harness) |
8/8 pass |
| Go taxonomy tests | go test -count=1 -run 'Taxonomy|Rehears' ./context/ ./adapter/wire/ ./gate/ (ulimit -v 8000000, timeout) |
ok |
| Python contract | python3 -m unittest discover -s tests -p test_contract.py |
55 tests OK |
| generated files | node scripts/record-shapes.mjs --check; node scripts/gen-reference.mjs --check |
both match ("13 pages match their sources") |
Probes (all in <scratch>/.review/)
| # | Probe | Outcome |
|---|---|---|
| P1 parity | probe/parity.mjs (TS, from the built dist) and scratch/go/context/zz_probe_test.go (Go) run the same input set. The validator part covers 12 operations × 6 effects × 9 checkKinds (including absent and bogus) × runtime true/false. The host-admission part covers check/read/evaluate × 5 effects × {none, rehearse, check} × 9 descriptions (absent, empty, exact, suffixed, lower case, not first, leading space, double space, "non-bindingly") × with/without a custom source. |
2106 verdict lines in each language; diff ts.txt go.txt is empty. Validators and host admissions agree exactly, reasons included. |
| P2 bound | The same output, filtered | check/rehearse with read-external, propose or act gets "tool taxonomy rehearse admits only a pure or read effect". rehearse under read, discover, evaluate, run and own-state is refused in all 20 combinations (13 with the checkKind reason; 7 hit the existing family/effect reason first). A host evaluate/read still gets "only the gated runtime may declare evaluate". The six existing sub-kinds return verdicts identical to before (P3). |
| P3 nothing widened | The Go probe run against origin/main's wire/tooltaxonomy.go and context/tooltaxonomy.go, then diffed against the head |
200 lines differ, all of them with checkKind rehearse. The only newly admitted rows are check/pure and check/read with rehearse (host rows only with the words). The other 100 changes are previously refused rows that now carry the rehearse or description reason. There is no change to RuntimeTaxonomyProblem, the compatibility table, defaultApproval, taxonomyAnnotations, presets or profiles. |
| P4 record shapes | Semantic diff of go/contract/record_shapes.json and packages/harness/contract/record-shapes.json against main |
The only changes are in tool-taxonomy.json: the checkKind enum gains rehearse and allOf grows from 12 to 13 entries (the shape file stores conditions as {}). No checkpoint, event or record shape changed. events.md changes are line-number shifts only (toolSources.ts:957→958, 1243→1244). tools.md gains the enum value and one paragraph from the changed generator. |
| P5 description at the Go gate | scratch/go/gate/zz_probe_test.go: a check/read/rehearse ToolOfferHostTool whose description lacks the words |
Gate.SetOffer keeps it in the offer, and Gate.EnrollContext admits it (err nil, 1 host tool). |
| P6 description at observation | scratch/go/adapter/eino/zz_probe_test.go: eino.Observe with the same tool |
Observe succeeds, and the model request carries the tool with its description and without "Rehearsal, non-binding". |
| P7 offered form | A custom-source check/read/rehearse declaration through declareSources+offerSources (TS) and OfferedHostTools (Go) |
Both frame the offered description: a BEGIN RETRIEVED DATA … from the description of tool "desk__graph_rehearse" … line, then runs as: desk, then "Rehearsal, non-binding: …". The words are present but do not lead. |
Note: the eino package's TestMain starts the TS scripted server, and Node dies under ulimit -v. P6 therefore ran under the kit's resident-memory cap (peak 482 MB) and a timeout, not the address-space cap.
Mutants (scratch copy only; each restored with git checkout)
| Mutant | Failing test and assertion |
|---|---|
TS: REHEARSE_EFFECTS + 'read-external' |
toolTaxonomy.test.ts › "admits rehearse only as a pure or read check…": expected '' to be 'tool taxonomy rehearse admits only a …' |
| TS: rehearse effect line deleted | same test, same assertion |
TS: non-check exemption (&& value.checkKind !== 'rehearse') |
same test: expected '' to be 'tool taxonomy checkKind contradicts i…' |
TS: startsWith → includes |
same test: "missing refusal" (the not-first row is admitted) |
| TS: case-folded prefix | same test: "missing refusal" (the lower-case row is admitted) |
| TS: missing description admitted | same test: "missing refusal" |
Go: RehearseEffects + "read-external" |
TestTaxonomyRehearse at tooltaxonomy_test.go:385: check/read-external/rehearse want the rehearse reason, got "" |
| Go: effect bound disabled | same test, same line |
| Go: non-check exemption | same test at :385: read/read/rehearse want the checkKind reason, got "" |
Go: HasPrefix → Contains |
same test at :430: want the description refusal, got nil |
| Go: case-folded prefix | same test at :430 |
| Go: description rule disabled | same test at :430 |
Schema: the new if check/rehearse then effect ∈ {pure,read} condition removed |
test_every_refused_rehearse_taxonomy_fails_the_schema fails at the read-external row |
Schema: rehearse removed from the enum |
test_every_vector_validates_against_its_schema fails at declarations.json item 9 |
All five mutation rows the PR adds have find strings that occur exactly once. Their Go twins (rows 1–5) were each confirmed by hand above.
Schema (item 5)
The new condition uses if: {required:[operation,checkKind], properties:{operation:{const:check}, checkKind:{const:rehearse}}} with then: {properties:{effect:{enum:[pure,read]}}}. This is plain draft 2020-12 with required guarding the if, so any conforming validator gives the same answer. effect is required at the top level. act is already refused by the existing check-family condition, so only the read-external row depends on the new condition, and that row is what the schema mutant shows. The Python test covers the enum (through items) and the condition (through refused).
Vectors (item 6)
Both vector files are handwritten in the existing style. refused[].problem strings equal the validators' strings byte for byte: both test suites assert exact equality, and P1 confirms it. The rehearsal section of tools.json holds one admitted declaration (hints, approval none, review true, work true) and the four description refusals the ADR names.
Findings
| # | Sev | Where | What | Evidence |
|---|---|---|---|---|
| F1 | MEDIUM | go/gate/session.go (Open, SetOffer), go/gate/context_enrollment.go (EnrollContext), go/adapter/eino/observation.go (Observe) vs docs/adr/0027-tool-taxonomy.md amendment |
The amendment lists "the offer's description says 'Rehearsal, non-binding'" among the restrictions and says host admission ensures "the words lead the offered description". These four Go entry points take contract.ToolOfferHostTool from the host directly and re-check taxonomy only. A check/rehearse declaration without the words is kept in the gate's offer, enrolled, and in Observe sent to the model. The builder declared two of these paths out of scope, but the ADR text promises the opposite. Either apply the description rule on these paths (one shared wire helper over taxonomy plus description, used by all four) or narrow the ADR to declareSources/DeclareSources and name the Go gate and observation inputs as unchecked. No TypeScript equivalent of these paths was found. |
P5, P6 |
| F2 | LOW | ADR amendment and design doc wording ("the words lead the offered description and any catalogue line") | For a tool with a source (custom is the only source kind a pure/read rehearse can carry), both languages frame the offered description and catalogue line with the retrieved-data header and a runs as: line, so the words come third. They are present but do not lead. |
P7 |
| F3 | LOW | toolSources.ts / go/context/tooltaxonomy.go prefix test |
The prefix match has no word boundary, so "Rehearsal, non-bindingly binding" is admitted in both languages. The ADR's "begins" is satisfied literally. | P1 rows |
| F4 | LOW (note) | wire.RuntimeTaxonomyProblem / runtimeTaxonomyProblem |
A Runtime-served ceiling name other than experimental_evaluate (e.g. validate) may now publish check/rehearse (pure/read) without the description rule. This is declared out of scope and widens no capability, since those names were already bounded to pure/read check. |
P1 runtime=true rows |
There are no HIGH findings. No diff content falls outside the claimed scope: the generated files changed only through the generator change the PR claims, and both --check runs pass.
Re-read (2026-10-29 UTC) at head 43b97c9dfd108b76c96507038e2ef034daaac350
Same reviewer and same room. The coordinator's decision on F1 was to narrow the ADR to a host-admission rule. Verdict: mergeable, with 0 HIGH. F1, F2 and F3 are closed. F4 stays a note, as declared.
-
The merge of main (
cd8198d) brought only Lock @hono/node-server at 2.1.5 (GHSA-rmxm-3fg6-px4f) #245's changes.git diff 2fa784e cd8198dis byte-identical togit diff eba2ad6 af37939(Lock @hono/node-server at 2.1.5 (GHSA-rmxm-3fg6-px4f) #245):docs/dependencies.md,package-lock.jsonandscripts/dependencies-metadata.json. The fix commit43b97c9touches 12 files, all inside the claimed scope: the ADR, the design doc, the reference doc and its generator, the two validators, two host-admission files, tests in both languages, tools.json and the mutation rows. Against main, the PR still changes the same 19 files and no package or lock file.dependency-auditis now green. -
F1 is closed by narrowing the ADR. The ADR, the design doc and
tools.mdnow say the rule is enforced at host admission (declareSources/DeclareSources). They also say that Go'sOpen,SetOffer,EnrollContextand eino'sObservetake host tools directly and do not re-check the description. I checked "every TypeScript engine start runs admission" against the source: vercel, deepagents and codex each start throughmanagedContext, which callscontextualSession, which callsdeclareSources. The delegation children also open throughcontextualSession. P5 and P6 from the first pass match the new text. The new tests pin it:- TypeScript: an engine-start case refuses with a configuration error and makes zero provider calls.
- Go:
TestTaxonomyRehearseAdmissionOnlyshowsDeclareSourcesrefusing,SetOfferkeeping the tool andEnrollContextadmitting it.
The ADR claims nothing the code does not do.
-
F2 is closed. The ADR says the words lead the host's declared description, not necessarily the offer, and names the retrieved-data and
runs as:lines that come first on a sourced tool. -
F3 is closed.
rehearsalDescribedandwire.RehearsalDescribedrequire the words first, followed by the end of the description or one of the ASCII characters space,\t,\n,\r,.,,,;,:,!,?or). This matches the ADR's wording ("a space, tab or line break, or one of.,;:!?)"). The tools.json vectors now include "Rehearsal, non-bindingly binding" among the refused rows and the bare words among the admitted rows.
Re-run
| What | Result |
|---|---|
npm ci; npm run build; npx vitest run toolTaxonomy.test.ts |
9/9 pass |
go test -count=1 -run 'Taxonomy|Rehears' ./context/ ./adapter/wire/ ./gate/ (ulimit -v 8000000) |
ok (TestTaxonomyRehearse and TestTaxonomyRehearseAdmissionOnly pass) |
test_contract.py -k rehearse -k every_vector -k taxonomy |
3 OK |
gen-reference.mjs --check; record-shapes.mjs --check |
match |
Parity probe P1, same inputs plus 19 boundary descriptions: each listed boundary character, -, em dash, NBSP, (, ", an emoji, NUL, ] and vertical tab |
3816 lines in each language, diff empty. Admitted: the bare words and the words followed by : x, ., :, ), \t, \n, \r, !, ?, ; or ,. Refused: everything else, including "non-bindingly", NBSP, em dash, emoji and vertical tab. |
Mutants (scratch copy; each restored)
| Mutant | Failing test and assertion |
|---|---|
TS boundary: return next === '' || REHEARSAL_BOUNDARY.includes(next) → return true |
toolTaxonomy.test.ts › "admits rehearse only…": missing refusal |
TS not-first: at !== 0 → at < 0 |
same test, same assertion |
TS admission off (the rehearsalDescribed check disabled) |
both rehearse tests fail, including the engine-start case |
Go boundary: RehearsalDescribed ends || true |
TestTaxonomyRehearse at tooltaxonomy_test.go:431: want the description refusal, got nil |
Go not-first: at != 0 → at < 0 |
same test at :431 |
Go gate re-check added to SetOffer (the reverse direction) |
TestTaxonomyRehearseAdmissionOnly at :461: "the gate's SetOffer re-checked the description" |
The three changed or added mutation rows each have a find string that occurs exactly once in its file. Nothing was posted.
|
🎉 Thank you for your contribution to Judgment Pack! If you’d like another task, choose a path that interests you: |
Carries #244 (#241, the rehearse check sub-kind). Both sides of ADR-0027, docs/design/tool-taxonomy.md and the mutation table are kept, main's rows first; docs/reference/events.md is regenerated with scripts/gen-reference.mjs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Closes #241 by recording the maintainer's choice of option 1: a non-binding graph rehearsal is a host
checkwith a defined sub-kind, not a new Runtime route.What changed
Decision (ADR-0027 amendment, 2026-10-29). A seventh
checksub-kind,rehearse: a non-binding dry evaluation of a declared artifact whose result carries no authority and whose execution writes no audit, resource or run state.check/rehearseaccepts effectspureorreadonly. The Desk'sgraph_rehearseis named as the first declarer. The sub-kind list and the compatibility sentence are updated in place. The amendment states the restriction (host-declared artifact, mandatory rehearsal flag, a declared description that begins with the words "Rehearsal, non-binding", budget-bounded I/O, no authority from label, name or model text). It also says which parts the package enforces and which the host owns (artifact identity and freshness, the flag, the rehearsal marker).Validators, both languages.
packages/harness/toolTaxonomy.tsandgo/adapter/wire/tooltaxonomy.go:CHECK_KINDS/CheckKindsgainrehearse.REHEARSE_EFFECTS/RehearseEffects= pure, read.check/rehearsewith any other effect is refused with "tool taxonomy rehearse admits only a pure or read effect". This check runs before the family rule, so read-external and act get the same reason.rehearseunder any other operation keeps the existing refusal ("tool taxonomy checkKind contradicts its operation").declareSourcesthroughhostTaxonomyProblem;DeclareSourcesthroughHostTaxonomyProblem) refuses a rehearse declaration unless its own description begins with the words "Rehearsal, non-binding". The words must be followed by the end, a space, tab or line break, or one of.,;:!?). The check is the shared helperrehearsalDescribed/wire.RehearsalDescribed. Every refusal is the existing configuration failure ("Tool taxonomy refused: … Nothing was sent.") before provider work.Open,SetOfferandEnrollContextand eino'sObservetake host tools directly and check taxonomy only, so a tool that reaches them carries whatever admission it passed. The words lead the host's declared description, not necessarily the offer: a tool with a source is offered framed.evaluatefamily (a hostevaluate/readis still refused), the six-tool Runtime ceiling and Runtime metadata rules, approvals, the hints for other families, presets, and phase and delegation rules. No checkpoint, event or record format changes.Public surface.
contract/schemas/tool-taxonomy.jsongains the enum value and oneallOfcondition (check + rehearse ⇒ effect pure|read). The generateddocs/reference/tools.mdgains the value plus a one-line meaning.docs/reference/events.mdchanges only in line numbers fortoolSources.ts. The record shapes (currentonly) are regenerated.docs/design/tool-taxonomy.mdandcontract/README.mddescribe the sub-kind for hosts.Vectors (handwritten)
contract/vectors/taxonomy/declarations.jsoncheck/read/rehearseandcheck/pure/rehearse.refusedrows, each{taxonomy, problem}:evaluate/read→ "only the gated runtime may declare evaluate"read/read/rehearse→ "tool taxonomy checkKind contradicts its operation"check/read-external/rehearse→ "tool taxonomy rehearse admits only a pure or read effect"check/act/rehearse→ the same reason as read-externalcontract/testdata/taxonomy/tools.jsonvalidgains the two rehearse rows: approval none, review and work true.rehearsalsection:graph_rehearsehost declarations (the words then a colon; the bare words), each with its derived hints (readOnly, idempotent, not destructive, not open-world) and approval none.toolTaxonomy.test.ts(two#241cases) andgo/context/tooltaxonomy_test.go(TestTaxonomyRehearse,TestTaxonomyRehearseAdmissionOnly). The admission-only cases pin where the rule lives. In TypeScript, admission and a vercel engine start refuse before provider work, while the shared validator and the preset filter accept the taxonomy. In Go,DeclareSourcesrefuses while the gate'sSetOfferkeeps the tool andEnrollContextadmits it. Both readers also assert the exact reasons, the configuration failure throughdeclareSources/DeclareSources, and that the refusal's error event reads back.tests/test_contract.pyholds the three rehearse refusals to fail the schema and the hostevaluate/readto pass it, since that rule belongs to the owner, not the record.profiles.jsonis unchanged: profiles derive from operations and effects, not sub-kinds.Refusals and their mutants (each in a scratch copy, baseline green first)
mutation-rows.json, run withmutation-check.mjs --row "taxonomy rehearse")TestTaxonomyRehearse)taxonomy rehearse admits read-external: caught, expected reason, received''RehearseEffects+ read-external: fails at tooltaxonomy_test.go:386, got""taxonomy rehearse admits act: caught; falls to the family rule, received "tool taxonomy operation contradicts its effect" (refusal held twice)taxonomy rehearse under another operation: caught, received''CheckKind != "rehearse"exemption: fails at :386, got""taxonomy rehearse description words(at !== 0→at < 0): caught, "missing refusal"at != 0→at < 0: fails at :431, got<nil>taxonomy rehearse description boundary(return true): caught, "missing refusal"<nil>taxonomy rehearse description required: caught in both#241casesTestTaxonomyRehearseAdmissionOnly(:455)SetOffermade to re-check the words:TestTaxonomyRehearseAdmissionOnlyfails at :461evaluate/readtaxonomy runtime owner provenancedisabled by hand: new case fails, received''&& false: fails at :386, got""rehearseremoved fromCHECK_KINDS: acceptance failsCheckKinds: fails at :375allOfcondition:test_every_refused_rehearse_taxonomy_fails_the_schemafails on read-externalrehearsefrom the enum:test_every_vector_validates_against_its_schemafails on declarations.json item 9Self-checks
go/gate,toolGate.ts,refutation.ts,go/adapter/turn/refutation.go):git diff origin/main...HEADtouches none of them. The one admission change these paths see throughwire.TaxonomyProblemis the one the issue names:check/rehearsewith pure or read.readRunEvent(failureEvent(...)); in Go,failure.Eventdecoded ascontract.RunEvent). A missing description is among the refused inputs.Checks
Candidate: head
43b97c9, the fix commit on top of the branch-update merge ofmain(cd8198d, bringing #245's lockfile ataf37939), with no uncommitted diff. Its tree is byte-identical to the tree that was checked (65350e8, the same fix plus the samemain;git rev-parse HEAD^{tree}is equal). Scope:jpdev check --changed origin/mainselected 24 of 30 checks for the 19 changed paths since merge baseaf37939.Caps and environment:
ulimit -vwith timeouts./tmp, because packed-smoke refuses an ancestornode_modules.d9251ec.Focused runs on the merged head:
vitest run toolTaxonomy.test.ts toolSources.test.ts: 86 passed.go test -run Taxonomy ./context/ ./adapter/wire/: ok.mutation-check.mjs --row "taxonomy rehearse": 6 chosen, 6 caught (same fix content).The first round's run on
2fa784ealso passed 24 of 24. CI owns the exact-head run on this pull request, and the full mutation table onmain.Review fix round
The clean-room review of
2fa784efound 0 HIGH, 1 MEDIUM and 3 LOW. Fixed in43b97c9:F4 stays a note below.
maincame in through the branch-update mergecd8198d, separate from the fix.Left out, on purpose
Open,SetOfferandEnrollContextor to eino'sObserve(review F1, decision: narrow the text, not widen the enforcement). The ADR names those paths, andTestTaxonomyRehearseAdmissionOnlyfails if either side drifts.experimental_evaluate, such asvalidate, may publishcheck/rehearse(pure or read) without the description rule. This is out of scope and adds no capability: those names were already bound to a pure or read check.docs/adr/has no index, so there was no row to add. There is no version bump and no release notes.🤖 Generated with Claude Code