feat(e1): freeze the managed ATS tool host contract (schemas, validators, golden vectors) - #167
Merged
Merged
Conversation
Add contracts/managed-ats-tool-host/v1: common definitions plus the trust, device-proof, host-open-proof, observer-channel-receipt, runtime-capability, registry, host-lease, invocation, cancellation, result, workspace-status-input and workspace-status schemas (JSON Schema draft 2020-12), and manifest.json pinning each file's aether.schema/1 digest. Every object level is closed with a full required list, every $ref is document-local (each schema carries an identical copy of the common definitions it uses, so its digest covers everything that validates it), and every document is pure ASCII. Semantic rules the schema language cannot express (canonical bytes, signatures, ordering, lifetimes, byte bounds) are enforced by the validators that follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/core/managed_tool_host implements step 1 of the E1 landing order: closed validators for the trust document, device proof, host-open proof, observer receipt, runtime capability, registry, host lease, invocation, cancellation, result and workspace status (input and output); a strict byte-level frame lexer; the common digest rule, named derivations and Ed25519 over raw keys; and separate cross-object checks (lease binding and expiry, invocation scope and deadline, argument and payload dispatch to the frozen E1 schemas, cancellation fencing, result identity, E1 canary registry, capability attestation). It performs no I/O, opens no transport, registers no tool and grants no execution authority. Every refusal is a ToolHostContractError with a fixed message naming the field path, never the value. Each check returns the value it was given, so deleting any guard flips its single-cause vector to accepted. test/fixtures/managed_tool_host_golden.json (pure ASCII) pins keys, schema digests, canonical bytes, primitive boundaries, derivations, 12+55 raw frames, 39 accept and 352 reject vectors and 23+85 cross vectors. The contract test recomputes every digest and signature independently, compares each refusal message exactly, and asserts closed-field and enum parity with the schema documents, the grants_execution_authority invariant, named coverage floors and a no-I/O, ASCII-only source scan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture, schema and source scans accepted only LF, but this repository pins eol=lf only for other contract directories, so a Windows checkout with core.autocrlf would fail them on line endings alone. Digests are computed over parsed JSON and are line-ending independent; the scans now accept CR and still refuse every other control byte and anything above 126. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/fixtures/managed_tool_host_{wire,objects,cross,ed25519}.py reimplement
the contract in stdlib-only Python 3.10+, sharing nothing with TypeScript: a
strict byte lexer (json.loads with object_pairs_hook, parse_int, parse_float
and parse_constant hooks plus a document-order pre-scan for depth, number
spelling and escaped controls or lone surrogates), RFC 8785 with UTF-16
key order, the common digest rule and derivations, every validator and
cross-object check with byte-identical messages, and a pure RFC 8032
Ed25519 (verify; sign only to self-check the test keys).
managed_tool_host_verify.py reproduces every section of the golden fixture,
including schema digests recomputed from contracts/managed-ats-tool-host/v1,
enforces the same named coverage floors, exits non-zero on any mismatch and
prints a one-line summary. CI runs it on Linux and Windows next to the two
existing Python verifiers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sole A mutation that breaks the mirror's UTF-16 key order made the verifier die with UnicodeEncodeError while printing its own FAIL line: the drifted canonical text holds U+1F600 and a Windows console encodes stdout as cp1252. The run still exited non-zero, but the vector that caught the mutation was never named. - Print every failure line as pure ASCII (backslash escapes), so the report cannot crash on the characters it is reporting. - Record a crash inside a section as a named failure and still run the remaining sections. - A reject vector whose context documents are refused is reported under its id instead of aborting the object section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nesses Both harnesses index accept vectors by id, so a repeated id would silently resolve a reject vector's base to whichever copy came last. The TypeScript test and the Python mirror now refuse any repeated key label, canonical or derivation row name, raw frame id, object vector id or cross vector id within its section. The frozen fixture already satisfies this; the guard was proven by a fixture mutation that repeats raw_reject[0].id, which fails in both languages naming "raw: byte_order_mark". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The single-cause proof previously covered the 352 object and 85 cross vectors. Extending it to the 55 raw frames, 85 primitive boundaries and 9 derivation rows found raw vectors that stayed refused, as "Frame is not valid JSON.", after their own guard was deleted: - byte_order_mark: the decoder kept a leading BOM as U+FEFF. Both decoders now strip one (TextDecoder default, Python utf-8-sig), so the explicit guard, which runs first, is the only rule that refuses a BOM. - escaped_newline, escaped_tab: the lexer never decoded the letter escapes b, f, n, r and t. Every escape now decodes to its UTF-16 unit before the control and surrogate rules run on that unit, so a letter escape and a four-hex-digit escape of the same control are refused by the same rule. - lone_high, high_then_letter, reversed_pair: after an unpaired high surrogate the lexer skipped six characters as if a pair followed. A surrogate that is not half of an escaped pair now falls through to the single unpaired-surrogate rule. Fixture changes (refusal messages unchanged): - truncated_sequence ends with a closing quote and bracket, so the invalid UTF-8 is its only defect (it used to end the frame mid-string). - high_then_bad_hex, which carried two defects (a lone high surrogate and the invalid escape \uZZZZ), is replaced by high_then_escaped_quote. - The 16 "Frame is not valid JSON." vectors carry exception "grammar": a grammar violation has no guard to delete. - The null text primitive carries exception "type_guard". Result: 586 reject rows, 539 flip to accepted when their check is deleted, 47 carry an exception tag, 0 problems. Both harnesses still reproduce the fixture (TypeScript 18/18, Python 719 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting the mirror's raw control-character check left raw_tab refused as "Frame is not valid JSON." because json.loads in strict mode also rejects controls below U+0020 inside strings. json.loads now runs with strict=False, so the pre-scan, which already refuses every Cc character (U+0000 to U+001F and U+007F to U+009F) before json.loads sees the text, is the single rule. No outcome changes while the pre-scan is intact. With the check deleted, raw_tab, raw_del and raw_c1 now all flip to accepted, matching the TypeScript lexer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Append section 5, "Managed ATS tool host v1 (E1 schema bundle)", after the existing sections: - what landed (schema bundle and manifest, validator module, golden fixture, TypeScript test, independent Python mirror run by CI on Linux and Windows), with the statement that it adds no transport, registers no tool and grants no execution authority; - the lead's fourteen encoding clarifications as implemented; - the lane decisions awaiting review, including a disclosed difference: for a frame with two defects the two lexers can name different defects; - the cross-object checks; - vector counts, the fixture size and sha256, the single-cause proof over all 586 refusals with its 47 tagged exceptions, the test coverage, the Python mirror, and the mutation matrix summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both harnesses pin the golden fixture's LF-normalized sha256, and every schema digest covers exact bytes. Mark contracts/managed-ats-tool-host/v1/*.json and test/fixtures/managed_tool_host_golden.json as text with eol=lf so a checkout on any platform reproduces the committed bytes. Only these two lines are added to this shared file, as review round 1 authorised. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HIGH
- Ed25519 keys: trust keys and device_public_key must be canonical point
encodings (y below p with the sign bit masked) outside libsodium's
small-order blocklist, compared with the sign bit masked. ed25519Verify
refuses such keys too. Vectors: all-zero, identity, order-8, y = p + 1,
negative-zero and y = 2^255 - 1 keys, every blocklisted encoding as a
primitive, and the review's forged host-open proof (R = identity, S = 0
under the all-zero key, with the device proof handed over unvalidated),
which is now refused in both languages.
- Trust expiry at every use: verifyCloudSignature takes now and refuses
once now >= trust.expires_at + skew. device_proof.trust_expired and
host_lease.trust_expired pin it; device_proof.expired now checks against
a later trust document with the same keys so it stays single-cause.
- aether.safe-display/1: error.message and diagnostics[].summary are 1 to
256 printable ASCII characters (U+0020 to U+007E). The accept vectors
that pinned U+202E, U+200B and U+FEFF are now rejects, alongside tag
characters, U+2028/U+2029 and Latin-1. account_subject keeps the general
string rule because it is hashed locally and never displayed.
MEDIUM
- checkLeaseBinding requires the lease's revocation_epoch to equal the
device proof's.
- checkHostOpenBinding(hostOpen, registry, lease, {challenge}): issued
challenge, registry session and lease conversation; 1 accept and 9
single-cause rejects.
- Multibyte byte bounds: 262 144 and 262 145 byte frames of two-byte
characters, a result payload holding U+1F600, and 65-byte (64 UTF-16
unit) arguments over max_argument_bytes.
- assertE1CanaryRegistry pins data_classes to ats_status, local_status.
- Both harnesses pin the fixture's LF-normalized sha256; the TypeScript
test also requires CONTRACTS.md to state it.
- Result rules: cancelled requires TOOL_CANCELLED and deadline_exceeded
requires TOOL_DEADLINE_EXCEEDED, refused uses neither; succeeded never
uses new_call_after_recovery; checkResult refuses a succeeded result
completed after deadline_at + skew.
LOW
- The module scan also refuses bare side-effect imports and any process.
use.
- CONTRACTS.md: the section is renumbered to 6 and records the review
decisions, the redelivery clarification and the TOOL_CONTRACT_INVALID
mapping, with updated counts and fixture sha256.
Harness: the Python mirror now reports a non-contract exception against
the vector that raised it instead of aborting its section, matching the
TypeScript harness.
Fixture: 506 945 bytes, sha256 eea8337d0c4caf0fe167ef5c7a144468118d34ee79a24b811d67212243688f60.
45 accept + 378 reject objects, 13 + 56 raw frames, 26 + 99 cross, 151
primitives. Single-cause proof: 656 refusals, 608 flip, 48 tagged, 0
problems. TypeScript 19/19, Python 807 checks, mutation matrix 140/140.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Freezes the E1 managed ATS tool host contract so AETHER-CLOUD, ATSv2 and the Agent host can implement the first Cloud-to-local read-only tool (
ats_workspace_status) against one shared, byte-exact definition. It lands the closed JSON Schema bundle, TypeScript validators and cross-object checks, and 807 golden vectors reproduced by an independent Python mirror in CI. It adds no transport, registers no tool and grants no authority. Cloud and ATSv2 pin the fixture next.Key Changes
contracts/managed-ats-tool-host/v1.eea8337d...8f60, LF-pinned, with lead decisions recorded in CONTRACTS.md section 6.Verification
Risk / Follow-ups
🤖 Generated with Claude Code