feat(ats): close the Spec 1 ticker and label weaknesses with /2 shapes (G0) - #166
Merged
Merged
Conversation
…imitives The strict ticker and closed-charset masked label that agent-browser-ats-order/1 introduced now live in primitives.ts as equityTicker() and closedMaskedLabel(), with byte-identical regexes and messages, so the Spec 1 /2 shapes can share them. The browser order modules import them from there. The browser golden fixture and its Python mirror pass unchanged (189 vectors), which pins the messages; drifting either message in the build fails named browser vectors. symbol() is documented as the frozen /1 ticker it is: it admits URL-shaped strings such as HTTPS://X. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frozen Spec 1 /1 validators accept URL-shaped tickers (HTTPS://X) and masked labels with fullwidth or Arabic-Indic digits, confusable letters, direction overrides and zero-width characters. /1 stays frozen and keeps accepting exactly what it accepts today; six /2 schemas close the weaknesses before any executable order can reuse these shapes: model-order-proposal, equity-order-intent, operator-approval, execution-receipt, delegated-trading-grant and account-binding. Each /2 has the same fields as its /1. The only semantic change is equityTicker() for every ticker (grant allowlist entries included) and closedMaskedLabel() for the masked label. Each pair shares one validator body parameterized by schema tag and check, so /1 keeps its messages and evaluation order. The order-review receipt keeps one version: it carries no ticker or label, and its intent_digest covers the intent's schema tag, so a review can only answer the intent version it was minted for. verifyExecutableCommitAuthority() and verifyExecutableApprovalChain() admit only /2 intent, approval, binding and grant, refusing anything else with the fixed EXECUTABLE_CHAIN_REFUSAL before any other check; past that they run the existing gates' logic unchanged. The /1 gates keep their signatures and behaviour. test/fixtures/ats_contracts_v2_golden.json (pure ASCII) pins canonical text and digests for each /2 document, 8 strict variants that must stay accepted, 12 exact-message rejects, 41 frozen weaknesses accepted by /1 and refused by /2, and 6 chain cases whose version rule is their only possible refusal. Wiring the /2 validators to the weak checks first failed 54 named vectors before the strict checks went in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… it in CI
test/fixtures/ats_contracts_v2_verify.py (stdlib only, pure ASCII)
mirrors the Spec 1 validators at /1 and /2 and both executable gates,
then runs every vector in ats_contracts_v2_golden.json: canonical text
and digests, strict variants, exact-message rejects with their /1
outcome, frozen weaknesses (accepted by /1, refused by /2) and chain
cases with a version-blind single-cause control. It borrows the
primitives and strict helpers from ats_browser_order_wire.py and the JCS
encoder from ats_contracts_golden_verify.py, so each rule has one Python
definition. The frozen /1 label rule uses [0-9]{5,}, not \d, because
Python's \d matches fullwidth digits that JavaScript's does not.
Wired to the weak checks first, the mirror failed the same 54 vectors
the TypeScript suite did; strict, it reproduces all 85.
CI runs it on Linux and Windows beside the other Python verifiers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ats_contracts_v2_wire.py now holds the liftable mirror (validators at /1 and /2 plus both executable gates) and ats_contracts_v2_verify.py only the fixture harness, matching the browser order mirror's wire/verify layout and keeping each file under 800 lines. ATSv2 lifts the wire module. The mirror docstring no longer uses a backslash escape: prose says "digit class" instead. Behaviour is unchanged: 85 vectors reproduced under python -W error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/ats_no_order_tool.test.ts reads all 31 places this CLI registers, advertises or dispatches something a model, Cloud, a device or a person can invoke by name: the brain tool list and its host-side table, the ToolExecutor dispatch, Ollama schemas, what CloudBrain actually sends Cloud when it opens a dev session, the doctor probe, exec and headless subsets, the packaged capability fallback, the permission vocabulary, builtin skill manifests, the ATS vision skill, host and headless actions, device commands and capabilities, the GitHub action rail, the managed ATS agent's autonomy, viewer capabilities, the ATS Python bridge, and every command registry and dispatcher (manifest, CLI, slash, managed-agent verbs, main, /ats, /browser, /goal, github and the ats-skills CLI). No name may be one of the order contracts' 17 operations (the ten browser operations plus order-bearing connector and grant operations), an order submit, commit, place or cancel, or an approval. Each registry names a sentinel its read must find, so an empty or misdirected read fails instead of passing; REGISTRY_FLOOR pins the count; every failure names its registry. ToolExecutor and the headless control parser also refuse all 17 at runtime. Nothing outside src/core/ats_contracts may import the proposal or browser-order validators or the barrel, and a tripwire forces any future MCP server into the inventory. Injecting commit_once into CloudBrain's advertisement and place_order into EXEC_V1_TOOLS in the build failed the test naming both registries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e G0 release matrix docs/CONTRACTS.md marks the Spec 1 /1 ticker and masked-label weakness as frozen and never executable, and adds the /2 closure section: what changed and why, the six /2 schemas, why the order-review receipt keeps one version, the executable gate rule and its fixed refusal, the fixture contents and sha256, and the mutation results in both languages. docs/specs/2026-09-23-ats-agent-browser-remaining-gates-v2.md is a byte-identical copy of the authoritative execution spec (sha256 2363f00f...fbc8). It contains no Cf, Cc, Zl or Zp characters; its only non-ASCII characters are typographic: arrows (Sm), em and en dashes (Pd), curly quotes (Pi/Pf) and a section sign (Po). docs/specs/2026-09-23-ats-browser-execution-release-matrix.md records Agent #163, #164, #165 and ATSv2 #443, #444 with full PR head and merge SHAs, the pinned fixture sha256s (the /2 fixture pending ATSv2's pin), and that ATSv2's ats-mcp.yml pins Agent db4129f, the pre-merge head of #164, whose fixture bytes are unchanged through the merge. A passing fixture means only that both sides agree on shapes and refusals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tcher Review follow-up on the no-order-tool test. The order-name rule now also flags an order, trade or position being executed, confirmed, closed, routed, sent, filled, opened or amended, and a standalone buy, sell, short, flatten or liquidation, so a future execute_order or close_position cannot pass unseen. The contract-name set is unchanged at 17 and the benign canaries (git_commit, cancel, open, shortcut, ...) still pass. A 32nd registry sweeps every case label and verb comparison in all src/commands modules, which covers the skills, device and media dispatchers and any command module added later. The headless TOOL_NAMES reader now stops at the statement terminator. The test header and the docs state the inventory's scope: tools offered by a user-configured MCP server or Cloud's broker are outside it, and ToolExecutor refuses any name outside TOOLS. Removing any of the 32 registries fails the floor; injecting execute_order, sell or a liquidate subcommand fails the test naming the registry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executable gates trusted the schema tag. A chain whose members had passed only /1 validation (a URL-shaped ticker, a label with a right-to-left override), retagged /2 in code, passed both verifyExecutableCommitAuthority and verifyExecutableApprovalChain in TypeScript and Python. After the version check, each gate now re-validates every member: the intent, approval, binding and grant with their /2 validators, the review with validateOrderReview and the usage with validateGrantUsage. The clock and resulting position notional must be whole non-negative numbers, because a NaN clock passes every expiry check and a NaN or negative position or usage passes every limit. Any failure refuses with the new fixed EXECUTABLE_MEMBER_REFUSAL, and the verdict then runs on the re-validated copies. Python fails closed on any exception, as the TypeScript catch does. EXECUTABLE_CHAIN_REFUSAL is reworded to name the four versioned members, since every chain includes the single-version review: "Only an order chain whose intent, approval, account binding and grant are all /2 may authorize an executable order; any /1 member makes it a historical record." The fixture's chain cases now come in three kinds, each single-cause: - 6 version cases. - 9 member cases that hand the gates raw, never-validated documents and inputs the version-blind logic accepts, including the retagged weak chain. - 50 branch cases, one per reachable refusal branch of the shared chain and commit logic. Each has a pinned message both languages must reproduce through the version-blind and executable paths. One pairs a review minted for the /1 intent with the same intent retagged /2 and expects "Review does not answer this intent." The fixture also pins both refusal texts. Written first, the member cases failed with "got null"/"got None" in both languages. Also: - The Python version-blind verdicts are private (_approval_chain_verdict, _commit_authority_verdict). - verifyApprovalChain and verifyCommitAuthority are marked @deprecated for historical records only. - optionalBindingV2 and optional_binding_v2 are added, with tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… harden the scan
Review round 1 on the no-order-tool test.
Name rules:
- New verbs: create, new, post, make, enter, stage, queue, exit, reduce,
cover, authorize, review and preview, plus the order-type qualifiers
limit, market and stop.
- New nouns: ticket, broker (broker_commit) and all (cancel_all).
- trade, trading and rebalance are flagged standalone.
- A verb run into its noun is caught (placeorder, cancelall).
- In the five ATS-scoped registries a bare submit, place, commit or
cancel is an order.
Each of the round's 14 new must-flag names passed unflagged under the
old rules.
Reviewed exemptions: three (registry, name) entries, each with a
reason: the GitHub action rail's --approve flag, as the manifest and
the shell parser register it, and the ATS settings mode approve. The
list is exact: its length is pinned, every entry must be hit by the
scan, and no order-contract operation can be exempted.
Registries: the command flags (manifest and CLI_PARSE_OPTIONS) and the
ATS settings modes are added, so 35 in all. Every reader accepts both
quote styles and the ["a", "b"].includes(verb) dispatch form.
Import scan:
- The module rule now also covers order, approval, grant and connector.
- The symbol rule adds the four chain gates.
- Comment stripping is string-aware: strings, template literals
(nested ${}) and regex literals are kept, and anything left open
throws, so a "/*" in a glob can no longer swallow an import. Across
the 312 scanned files it keeps all 1,421 import statements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tors The round-1 mutation matrix found three guards that the suite passed without pinning: - Deleting the confirm verb survived. Its only mustFlag entry, confirm_trade, is also caught by the new standalone trade rule, so a new guard masked an old one. confirm_order now pins the verb. - Replacing the string-aware stripper in the import check with the old regex stripping was caught only through a false positive on a trailing comment. The existing glob vector, src/**/*.ts, closes its own /*, so it cannot tell the two strippers apart. A single-star glob between a /* and a */ string now shows the regex stripper losing an import. - Disabling regex-literal detection in the stripper failed only by throwing. A regex holding /* before a later */ now shows the stripper swallowing code. Also in this change: flatten leaves ORDER_VERBS, since TRADING_VERBS already flags it and listing it twice only shadowed that rule; the run-together rule runs per token, so place cannot mask replace nor review mask preview; and one mustFlag entry pins each remaining rule element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… scan CONTRACTS.md section 5 now states: - the version, re-validation and verdict steps, with both fixed refusals; - that re-issuing a binding or grant as /2 must bump binding_generation or grant_version; - a follow-up: the approval does not bind the masked label the operator saw; - the 65 chain cases, and the three branches no validated chain can reach; - what each mutation proved. Removing a version check alone now yields the member refusal, because re-validation backstops it. The release matrix records the new /2 fixture digest, notes that ATSv2 pins the two gate refusals in W1-D, and restates the G0 evidence: 35 registries, the widened order-name rules, and three reviewed exemptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chain fixture now holds retagged copies of the intent, approval, grant and binding, so a swap fails more than the document's own vectors. It also fails the raw-document check of each retagged copy, and any member case that copy alone breaks. The label swap also fails the optionalBindingV2() check. Nothing else fails, in either language. 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
Closes the two Spec 1 weaknesses the V2 execution spec says must be fixed before any executable order: the frozen
/1shapes accept a URL-shaped ticker (HTTPS://X) and account labels that can hide digits, confusable letters or direction overrides./2versions of the six affected documents reuse the browser order wire's strict checks, and new executable gates admit only a freshly re-validated, all-/2chain./1behaviour is unchanged, so historical records keep validating. ATSv2 pins the new fixture next.Key Changes
/2contract shapes: proposal, intent, approval, receipt, grant and binding use the strict equity ticker and closed-charset masked label;/1stays frozen.verifyExecutableCommitAuthorityandverifyExecutableApprovalChainrefuse any/1member and re-validate every member, so a/1document retagged in code cannot slip through.docs/specs.Verification
npm test: 2753 tests, 2740 pass, 0 fail, 13 skipped; typecheck, lint and docs:check pass.Risk / Follow-ups
ats_contracts_v2_golden.json(sha25605cc8708...eea21) and re-pin Agent to this merge.🤖 Generated with Claude Code