Repository navigation
Trading gates: user_decision evidence, readiness that needs holding evidence, and a signed live-go - #295
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eeaca88a39
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
eeaca88 to
ce05fbb
Compare
8d9ab77 to
ef17e16
Compare
…vidence, and a signed live-go
- scripts/trading_gates.py:
- Adds the evidence class user_decision. It is valid only for a gate owned
by user-decision whose flip_condition is null.
- rung_ready and blocking now count a required gate only while it is
recorded established and its condition holds. Simulation V1 of the
2026-09-25 readiness review had shown status failed alongside
rung_ready.live true.
- A top-level authorship block names a detached SSH signature per gate,
checked with `ssh-keygen -Y verify`. The live-go gate must name one, and
it holds only when that signature verifies. It fails closed in each of
these cases:
- the document, signature or allowed_signers file is absent or empty;
- a path resolves outside the tree;
- ssh-keygen is missing, times out or exits non-zero.
- Recorded established, live-go must carry user_decision.
- The checker writes nothing.
- catalogs/us-equities/gates-20260922.json:
- Adds live-go's authorship control: docs/decisions/live-go.md.sig,
docs/decisions/live-go.allowed_signers, principal live-go-signer,
namespace live-go@native-agent-stack.
- Adds a dated live-go note.
- No status, flip condition or evidence class changes.
- tests/test_trading_gates.py: 17 new tests. The real-signature tests use
throwaway ed25519 keys in temporary directories. The live-go fixture in
the readiness test now carries its control.
- catalogs/us-equities/README.md: the user's one-time setup and per-go
steps.
- docs/decisions/2026-09-25-live-go-authorship-control.md: the evidence,
the alternatives and the conditions that would overturn this choice.
No key, signature, allowed_signers entry or live-go.md was created. live-go
stays user_decision.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- README per-go steps: re-register the gates file in manifests/evidence.json (scripts/validate.py fails on an unregistered edit), run validate.py and evidence_manifest.py --check, commit the manifest with the go, rebase per the hot-file protocol, and open the PR with the required SOTA sources section. The valid-before expiry and revert path carry the same steps. - gates-20260922.json: the top-level scope now defines readiness as established AND holding now, and names live-go (no flip condition, flipped only by the user) as the exception to the flip-candidate rule; the authorship note lists the symlink-loop case. - trading_gates.py: resolve(strict=True) in authorship_holds and source_matches_holds; a symlink loop (RuntimeError on Python 3.12 and earlier, OSError from 3.13) now fails closed with JSON output and no host path instead of a traceback. Tests cover both paths and main()'s output. - Decision record: pin OpenSSH V_9_6_P1 as tag object b24f772e, commit 8241b9c0 (and git v2.43.0 as tag object c089584a, commit 564d0252); state the symlink-loop case and the untested ed25519-sk boundary. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Re-registered on origin/main 564a16a (after #278) with scripts/host_receipts.py register_file (docs/lanes.md hot-file protocol): catalogs/us-equities/README.md, catalogs/us-equities/gates-20260922.json, scripts/trading_gates.py and tests/test_trading_gates.py updated; the decision record docs/decisions/2026-09-25-live-go-authorship-control.md added. component_matrix.py --write and new_host_grand_list.py --write left their reports unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ef17e16 to
b583ac0
Compare
…egistry plus the owned rows) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude session native-agent-stack-5f: landing at head Observed main Required checks at this head: 8 pass . Unresolved review threads: 0. |
|
Claude session native-agent-stack-5f: post-merge observation. Landed as |
Scope
scripts/trading_gates.pybefore any live go. The 2026-09-25 readiness review's step 23 named three items, and this PR delivers all three:user_decisionevidence class;live-go: a detached SSH signature verified withssh-keygen -Y verify.39e18ed5(headce05fbb8)lane:tradingscripts/trading_gates.pytests/test_trading_gates.pycatalogs/us-equities/gates-20260922.json: a new top-levelauthorshipblock, a dated sentence in thelive-gonote, and a rewritten top-levelscopethat states the readiness predicate (an established gate counts only while its evidence holds) and the exception for the user-authoredlive-goflip, as the new decision record describes. No status, flip condition or evidence class changes.catalogs/us-equities/README.mddocs/decisions/2026-09-25-live-go-authorship-control.md(new)manifests/evidence.json: re-registration only, in the last commit, perdocs/lanes.md.This PR does not write or flip live-go. It creates no
docs/decisions/live-go.md, signature, allowed_signers entry or key.live-gostaysstatus: user_decision,evidence_class: none. It changes no other gate's status, condition or evidence rules. It does not touch PR #183 or #191. It does not change any engine module:runner.py,safety.py,transport.py,order-contract/order_contract.py, and everything else thatadaptive-paper/source-hashes.jsoncovers, are byte-identical to main. It makes no broker or market-data calls.SOTA sources
V_9_6_P1(commitb24f772e),ssh-keygen.1.-Y verify(lines 765-790) reads the message on standard input, with-nnamespace,-ssignature,-Iidentity and-fallowed signers. "Successful verification by an authorized signer is signalled by returning a zero exit status". The checker therefore counts only exit 0.namespaces=andvalid-before=options that the setup uses.live-go@native-agent-stack.v2.43.0,gpg-interface.c,verify_ssh_signed_buffer(lines 447-580).ssh-keygen -Y verify -n <namespace> -f <allowed signers> -I <principal> -s <signature>with the payload on standard input (lines 557-575).-Y find-principals, and it passes no-Overify-time, so avalid-beforeexpiry applies at check time.user_decisionclass change this repository's own checker contract: the gates document's scope and the 2026-09-25 readiness review's step 23. No external implementation applies to these two predicates.What changes
Evidence class
user_decision. It is valid only for a gate owned byuser-decisionwhoseflip_conditionis null. Validation refuses it on any other gate. The other classes keep their rules.Readiness needs holding evidence. A required gate counts toward
rung_readyonly while it is recordedestablishedand its condition holds now. Otherwise it is listed inblockingand keeps its rung and every later rung not ready. This covers a missing or failing receipt and, for live-go, a signature that does not verify. Before this change,pendingread only the recorded status (scripts/trading_gates.py:293at 0074a0c), so simulation V1 could showstatus: failedalongsiderung_ready.live: true. The error such a gate already produced is unchanged, and optional gates still never block.The live-go authorship control. The gates document's top-level
authorshipblock names live-go's control:ssh-keygen -Y verifydocs/decisions/live-go.md.sigdocs/decisions/live-go.allowed_signerslive-go-signerlive-go@native-agent-stacklive-go holds only when its document is non-empty and
ssh-keygen -Y verify -f <allowed_signers> -I <principal> -n <namespace> -s <signature>accepts its exact bytes. It fails closed in every other case:ssh-keygenis missing;ssh-keygentimes out after 30 s;ssh-keygenexits non-zero.In each of those cases live-go cannot count as established. It stays in
blocking.live, and an established live-go is reported as an error. The control is hard to remove or bypass:live-gogate names no control, so deleting the block alone cannot make live-go presence-only.condition_holdsnever lets live-go hold on presence alone, even for a caller that passes no control.user_decision.user-decisionwith a null flip condition may have a control.The checker runs
ssh-keygenwith tree-relative paths from the tree root, so no host path reaches its messages, and it writes nothing.--checknow also printsauthorship.live-go.{verified, detail}, an additive output key.Your one-time setup (also in
catalogs/us-equities/README.md, "Live-go authorship control")Once, before the first go:
ssh-keygen -t ed25519-sk -C live-go-signer -f ~/.ssh/live-go_ed25519_sk, where your platform supports FIDO2. Otherwise use a passphrase-protectedssh-keygen -t ed25519 -C live-go-signer -f ~/.ssh/live-go_ed25519, and keep it out of any ssh-agent that agent sessions can reach.,valid-before="YYYYMMDD"to the options to give a go an expiry. After that date the verification fails.For each go:
docs/decisions/live-go.mdwith LF line endings. The repository's.gitattributesis* text=auto eol=lf, and the signature covers the committed bytes.ssh-keygen -Y signstops at an overwrite prompt:rm -f docs/decisions/live-go.md.sigThen sign:
ssh-keygen -Y sign -f ~/.ssh/live-go_ed25519_sk -n live-go@native-agent-stack docs/decisions/live-go.mdssh-keygen -Y verify -f docs/decisions/live-go.allowed_signers -I live-go-signer -n live-go@native-agent-stack -s docs/decisions/live-go.md.sig < docs/decisions/live-go.mdIt prints
Good "live-go@native-agent-stack" signature for live-go-signer ....--checkalready showsauthorship.live-go.verified: truewhile live-go is stilluser_decision.status: establishedandevidence_class: user_decision. Thenpython3 scripts/trading_gates.py --checkmust exit 0 withstatus: passedanderrors: [].An optional complement, and your change to make, is a ruleset or code-owner review requirement on
docs/decisions/live-go*and the gates file.Evidence-class table
user_decisionis accepted only on user-decision gates with a null flip conditionsyntheticUserDecisionEvidenceTests(3 tests)syntheticReadinessRequiresHoldingEvidenceTests(4 tests), including the repository's own rows recorded established in an empty treessh-keygen, a timeout, an OSError or a non-zero exit, and never holds on presence alone; the checker writes nothingsyntheticLiveGoAuthorshipTests(7 tests;subprocess.runpatched to fail if it is called on the missing-file paths)ssh-keygen -Y verifyaccepts the matching signature and refuses 12 mismatches: an edited document, an unlisted key, thefilenamespace, another namespace, another principal (in the file and in the catalog), another namespace (in the catalog), a namespace-restricted key, an expired key, a file with no key, another document's signature, and a malformed signaturelocal_integration(OpenSSH_9.6p1, throwaway ed25519 keys in temporary directories)LiveGoSignatureTests(2 tests)local_integrationRepositoryLiveGoControlTests(1 test)local_integration(throwaway key, scratch directory, deleted afterwards)live-go.allowed_signersline,live-go.md.sigandlive-go.mdpassgitleaks dir --config .gitleaks.toml(no leaks) andpython3 scripts/validate.py --scan-file(status: passed)native_proven(GitHub Actions, run 36193588517 and run 36193588443)validateon merge 1fa2894: 5626 tests, skipped=648. main at c303d8e: 5609 tests, skipped=648. That is +17 tests, exactly this PR's, with 0 new skips. macos-15validate-macosverbose log artifact: all 17 new testsok, including bothLiveGoSignatureTestslocal_integrationgit show origin/main:scripts/trading_gates.pyin a scratch tree:FAILED (failures=37, errors=28)local_integration(scratch copy of the checker, the catalog and its 13 existing receipts; throwaway key)verified: true, live-go inblocking.live. After the flip: exit 0,errors: [], live-go out ofblocking.live. After an edit: exit 1, live-go back inblocking.livelocal_integrationtrading_gates.py --check: exit 0,status: passed, the sameblocking.live(1x/2x/4x, ibkr-local-acceptance, live-go),authorship.live-go.verified: false("receipt missing")Review dispositions
Codex review P1,
scripts/trading_gates.py:237, "Pin the live-go signer outside mutable repository state": rejected for this PR, with this evidence.ssh-keygen -Y verifyagainst an allowed_signers file whose path the catalog names. This PR implements that design.live-go.mdplus any evidence class maderung_ready.livetrue. This PR closes that path. Without a verifying signature, live-go fails closed, asLiveGoAuthorshipTestsandLiveGoSignatureTestsshow. A forgery now takes four deliberate, visible edits: a new key, a rewrittenlive-go.allowed_signers, a signature and the flip. Each one breaks the standing rule never to write or flip live-go..github/main-ruleset.json:required_approving_review_count: 0,bypass_actors: []). Agents act under the owner's identity. The checker, its tests and the CI workflow that would enforce an external pin are all in the tree. A pin outside the tree therefore turns a forgery from a data edit into an enforcement-code edit, which review sees more readily. It does not make a forgery impossible.GET /users/seathatflowsinourveins/ssh_signing_keys, HTTP 200, 0 keys registered, measured 2026-09-25). The gh token on the workstation carries onlygist,read:org,repoandworkflow(measured withgh auth status). It lacksadmin:ssh_signing_key, so agents on that host cannot register a key there without the owner's interactive re-authorization. Binding live-go to that list needs two decisions that are yours:The decision record lists this as an alternative, with the overturn condition "the user deciding to bind the signer to the owner's GitHub signing keys". The residual, that anyone with write access can replace the in-tree allowed_signers file, is stated in the decision record, the catalog note and the README.
Local commands run (raw via
rtk proxy; head ce05fbb unless noted;scripts/trading_gates.pyandtests/test_trading_gates.pyare byte-identical at eeaca88 and ce05fbb)The manifest entries were written with
scripts/host_receipts.py register_file. Each was then recomputed independently: the sha256 and byte count of all five files match.Decision record
docs/decisions/2026-09-25-live-go-authorship-control.mdnames the evidence, the alternatives (a distinct-reviewer ruleset, signed commits, GPG, Sigstore/gitsign, minisign/signify) and what would overturn the choice.Coordinator choices, each with the condition that would overturn it
authorshipblock keyed by gate id, rather than a new per-gate field.GATE_FIELDSand every existing row stay unchanged, and concurrent gate-row PRs meet fewer conflicts. Overturned if a second authorship-controlled gate makes a per-row field clearer.user_decision. This is the converse of item 1 for gates with a control, and step 28 of the readiness plan asks for it. Overturned if you prefer to record another class.live-go-signer, the namespacelive-go@native-agent-stack, and the pathsdocs/decisions/live-go.md.siganddocs/decisions/live-go.allowed_signers. The checker reads all of them from the catalog, so you may rename them there before setup.user_decisionevidence on another gate owned byuser-decisionwith a null flip condition keeps the presence rule, as item 1 specifies; no such gate exists today. Overturned when a second gate of that shape is added: it should then name a control too.ssh-keygenfails closed, and on such a host the real-signature tests skip. Overturned if a required CI runner lacksssh-keygen: live-go could then never verify there. Both required runners have it today: the signature tests ran and passed on ubuntu-24.04 and on macos-15 (see the evidence table).Limits and follow-ups
live-go.md, not what it says. The readiness review's optional items are not in this PR: a format lint forlive-go.md(step 23 item 4) and a native-faultsource_matchescondition (item 5).docs/grand-catalog-handbook.md:347-368still says no checker can evaluate live-go. It is a shared document, left to its owner; editing it would make this PRlane:shared.Checklist
🤖 Generated with Claude Code
Custody check, 2026-10-04
On a trial merge of this head with main
14048b84, which conflicts only in the registry:python3 -m unittest tests.test_trading_gatesran 43 tests, OK;python3 scripts/trading_gates.py --checkreported status passed, exit 0. A GPT-6.1 Sol cross-family read returned ACCEPT; its one P2 was this bullet's missing scope rewrite. This PR adds a control that makes a live go harder; it enables no live mode, endpoint or key.