Skip to content

feat(ats): close the Spec 1 ticker and label weaknesses with /2 shapes (G0) - #166

Merged
AetherAI3 merged 12 commits into
mainfrom
feat/ats-contracts-v2-closure
Sep 24, 2026
Merged

AetherAI3 merged 12 commits into
mainfrom
feat/ats-contracts-v2-closure

Conversation

@AetherAI3

Copy link
Copy Markdown
Owner

Summary

Closes the two Spec 1 weaknesses the V2 execution spec says must be fixed before any executable order: the frozen /1 shapes accept a URL-shaped ticker (HTTPS://X) and account labels that can hide digits, confusable letters or direction overrides. /2 versions of the six affected documents reuse the browser order wire's strict checks, and new executable gates admit only a freshly re-validated, all-/2 chain. /1 behaviour is unchanged, so historical records keep validating. ATSv2 pins the new fixture next.

Key Changes

  • Six /2 contract shapes: proposal, intent, approval, receipt, grant and binding use the strict equity ticker and closed-charset masked label; /1 stays frozen.
  • Executable-only gates: verifyExecutableCommitAuthority and verifyExecutableApprovalChain refuse any /1 member and re-validate every member, so a /1 document retagged in code cannot slip through.
  • Independent Python mirror: 211 vectors reproduced with identical digests and messages on Linux and Windows CI, so ATSv2 can lift it.
  • G0 no-order-tool proof: 35 CLI registries plus runtime refusal checks show nothing exposes an order operation.
  • V2 spec and release matrix: the authoritative execution spec and the G0 merge SHAs and fixture digests now live in docs/specs.

Verification

  • npm test: 2753 tests, 2740 pass, 0 fail, 13 skipped; typecheck, lint and docs:check pass.
  • Python verifiers: 18 + 189 + 211 vectors OK; the four already-pinned fixtures are byte-identical.
  • 251-row mutation matrix: removing any guard fails a named vector; packedBytes 4,869,772 of 5,000,000.

Risk / Follow-ups

  • ATSv2 must pin ats_contracts_v2_golden.json (sha256 05cc8708...eea21) and re-pin Agent to this merge.
  • Spec 2 data profile and probe still use the weak ticker; fix before those symbols can feed an order.

🤖 Generated with Claude Code

AetherAI3 and others added 12 commits September 23, 2026 17:40
…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>
@AetherAI3
AetherAI3 merged commit 59f167a into main Sep 24, 2026
9 checks passed
@AetherAI3
AetherAI3 deleted the feat/ats-contracts-v2-closure branch September 24, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant