Summary¶
The framework ends at stage 7 (human approval). Charter principle 3 makes the order path a
separately governed, separately approved artifact: this concept is that artifact's charter.
Ratification of THIS page — by human:yasu, with a verified event recorded in the decision log —
is what moves execute_approved.py (or its successor) onto a deploy path and permits
TASTYTRADE_READ_ONLY=0 to be set inside this adapter's environment only (corrected
2026-10-01 from "unset", which the code never honoured — see the credential-boundary
resolution note). Until then:
the pilot drives a host that cannot place an order, by design (now evidenced rather than
asserted: the read-only grant carries no trade scope — see the VERIFIED 2026-10-05 note in the
credential boundary below), and that does not change by building code — only by this record.
Scope boundary¶
In scope: one script (execute_approved.py or successor) + its environment scaffold +
its receipts. Out of scope: the round analyst, the report builder, the wiki — none of
them read creds, none emit orders, and this charter is what keeps it that way.
What may be executed (order admission)¶
Only a proposal whose full chain is verifiable at execution time:
- Proposal identity — a
proposals-<date>.jsonentry written by the round analyst, matchingtrades-<date>.json(id, OCC symbol, expiry, strike, qty, leg actions). - Attested ladder: the round's
ev-ladders-<date>.attestation.jsonverdict INCLUDES this trade's ladder id (attester passes; if any ladder absent from attestation, reject). - Approval record: an entry in
hermes-pilot/ledger/approvals.jsonlwithdecision=approved,proposal_idmatching, and at least the full M2 field set:decided_by(chat-bound human:yasu),decided_at(same trading day as the round), andladder_receipt_sha256matching this round's attestation file hash. - Approval is proposal-versioned: any regeneration of the proposal after approval voids it (hash-mismatch or same-day-new-file). Re-approval required. No cross-day approval carry.
- Gate re-run at submission time (the M2 void rule): the adapter itself re-runs, with today's live marks:
- mapping v0 regime gate (worst of round-morning and execution-time regime),
- ladder re-execution (deterministic reprice, fail-closed on │Δref_price│ > tol; tol defined in adapter config, default 15% of the approved ref_price),
- sizing re-check:
sizing.pypass must still hold (bucket cap, max-loss definition). Any of these failing → void the approval, append avoidedrecord to approvals.jsonl (reason recorded), place nothing. Failure to place is not cancellation — it is "never sent."
Execution constraints (M2-v0, submit-only)¶
- Structures: defined-risk debit verticals on SPY/QQQ/IWM/TLT (never naked legs, never undefined-risk premiums, never 0DTE). This matches the current paper book's long-vol posture — the first live trade is expected to be a debit spread, not credit.
- Modes:
dry-run(installs with this default),confirmed-live(requires BOTH a charter ratification event AND a per-round live-mode flag written by you, not the agent). The live flag file (/etc/okf-adapter/live-ok-<date>.flag— root-owned; theops/adapter/path originally written here is SUPERSEDED by change-log 2026-09-29/01, because anything underops/sits in an agent-writable repo clone) must exist and be dated today; the agent cannot mint it. - Price discipline: Limit order, Day TIF, price = the attested ref price (never market,
never chasing the bid/ask). Unfilled by session end →
unfilledjournal record; never re-priced or chased intraday. Next-day retry requires a fresh proposal + fresh approval. - Per-order cap: 1 lot for the first 5 approved orders (shipping & proving mode), then whatever an owner ratified amendment says. Total account risk cap: at most 3 concurrent live positions until 10 filled-and-reconciled receipts exist.
- Fill receipts: the executor writes one per order into
hermes-pilot/ledger/fills-<date>.jsonl, and appends the correspondingopen/exitrecord to the position ledger withprice_kind=live-filland a receipt ref. The broker's own order-status response is the receipt; the assistant-visible stdout is not. - Failure modes fail-closed: API error, partial fill, order rejected, session dropped
mid-batch — all write a
fill-receipt-missingrecord and stop the batch. No retry loop without an explicit human message telling the adapter why. - Kill switch: the presence of the file
/etc/okf-adapter/KILL(root-owned;ops/adapter/KILLSUPERSEDED by change-log 2026-09-29/01) disables every mode instantly and the next round reports it. Owner-created only; simplest possible tripwire.
Credential boundary (the important operational rule)¶
- The read-only credential in
~/.config/tastytrade.env(current, 0600) is the default environment for every routine — capture, round, report. It staysTASTYTRADE_READ_ONLY=1.VERIFIED 2026-10-05 (owner determination, human:yasu, in session; verbatim: "new tokens are read-only scoped"). The OAuth grant behind hermes' refresh token carries no
tradescope. Until this date the read-only boundary rested entirely on client-side enforcement —tasty_call.pyandexecute_approved.pyforcingTASTYTRADE_READ_ONLY=1, plus prompt prose — which is exactly what #40 flagged: a client flag is not a credential boundary, and the round agent both holds the token and ingests attacker-influenceable news. With the grant itself read-only, the injection→order path is closed at the credential, so the boundary no longer depends on flags the agent's own process sets.This is an owner determination, not an agent verification: an OAuth grant's scope is visible only in tastytrade's app configuration, so nothing on this host can re-derive it. It is recorded here as the evidence the summary's "by design" lacked; the ratified sentence is left in place, per this file's convention of dated additive corrections.
Companion facts verified on disk 2026-10-05: the owner rotated the tokens, and the separate owner-owned order file
~/.config/tastytrade-order.envnow exists at 0400, ownerysakakibara(see the clause below).Residual, NOT closed by this note: the round prompt still sources the credential file into the agent's own shell (
ops/cron/options-round-analyst.prompt.txt, step 0). With a read-only grant that can no longer place an order, but it still exposes the secret to the agent process. Carried on #27 (credential hygiene) — a defense-in-depth item, not an "immediate live exposure" one. - The adapter's live mode uses a separate credential file,
~/.config/tastytrade-order.env, which (a) does not exist until ratification, (b) must carryTASTYTRADE_READ_ONLY=0explicitly — unset is not enough and fails closed to read-only (see the resolution note below) — and (c) is readable only by a human-owned shell (mode 0400, owner:ysakakibara — the unix account on CT 100, created 2026-09-30;human:yasuremains the governance identity in records and approvals, the two forms are not interchangeable), so the agent cron env cannot ever read it indirectly. The adapter never reads this env itself; a human invokinglivemode sources it into that specific process only. The agent never types, sees, or handles order-mode envs.RESOLVED 2026-10-01 (owner: "fix the READ_ONLY docs to match the code"). The code is authoritative and clause (b) above is corrected.
execute_approved.pyforcesTASTYTRADE_READ_ONLY=1unless the environment says exactly the string0(execute_approved.py:30), so unset, empty,false,noand00all mean read-only and only a literal0enables writes.The code's behaviour is the safe one, which is why it won rather than the charter. It fails closed: a truncated, half-written or misspelled credential file degrades to read-only instead of silently arming live mode. Changing the code to match the charter would have meant an omitted variable arming live trading — the opposite of fail-closed, and the reason option (b) was rejected.
Scope of the disagreement, for the record: three prose statements said "unset" and one line of code said otherwise — this clause, and
execute_approved.py's own docstrings at lines 7 and 28. The file contradicted itself, not just the charter. All three are corrected; no executable line changed, verified by token comparison.Note live mode still needs two independent conditions: the explicit
--liveargv flag (execute_approved.py:78) andTASTYTRADE_READ_ONLY=0. This resolution settles the second only.STILL OPEN —
account_numberprovenance. This charter asks whether it comes from the credential file orproposals.json. It is neither:get_account()(execute_approved.py:46-53) fetches accounts from the broker at runtime and takesaccs[0]unconditionally — no filter, no expected-account assertion, no error when more than one account is returned. The ledger already holds two real-money Apex IRA legs, so more than one visible account is not hypothetical, and a wrong-account fill is not something acorrectrecord can undo. The target account must be pinned explicitly and asserted against what the API returns, failing closed on mismatch. Tracked on WekanD8JgoGAAy8rcNAj88; not resolved by this note. - No credential, key, or token is ever passed through an approval message.
Precision on the charter's words (this closes the ambiguity that made the M2 rule necessary)¶
Charter principle 3 says execution belongs to "a separately governed, separately approved artifact … outside this framework's scope". Read precisely: outside the framework's scope does not mean outside all governance — it means this adapter is its own governed artifact with its own evidence trail, designed here and ratified below. It cannot amend the framework's rules (mapping, risk gates); it executes within whatever gates the framework emits at the moment it runs. Framework amendment process does not apply to adapter code; wiki review applies to adapter code via its own change log in this concept.
What this charter does NOT authorize¶
- No
--liveorders before ratification. - No widening of per-trade size without its own amendment and a verified event.
- No short premium selling in the adapter's first 10 receipts — even if the regime mapping says calm — until the adapter has a validated fill-reconciliation history.
- No order path for any non-adapter role: the round analyst, report builder, and cron prompts
stay paper-only (
TASTYTRADE_READ_ONLY=1) regardless of the adapter's modes.
First-live-run design (the go-live plan)¶
Assume the four current straddles have aged into or near October expiry — if live money starts when they are near exp, the cleanest first live signal is an exit, not an entry:
- A paper round's exit-path EV (e.g. TSLA straddle EV_PHYS flips negative) triggers the paper exit that the analyst report already does anyway. In live mode, this becomes the FIRST live order — with no new risk taken beyond closing an existing chain.
- Full M2 chain exercised on ONE contract: approval card → approval jsonl → dry-run → confirmed-live → fill receipt → position-ledger open-record (price_kind=live-fill) → journal entry. Then the next live decision can be a fresh entry.
- First new entry AFTER receipt #1 cleared reconciliation: a 1-lot defined-risk debit vertical (likely SPY put-debit, matching the present book's thesis and proving the full-funnel path on a structure the pilot already prices well).
Change log (adapter design; per this charter's own rule, wiki review not framework amendment)¶
2026-09-29/01 — control files move OUT of the repository (proposed, owner review)¶
The ratified text names ops/adapter/live-ok-<date>.flag and ops/adapter/KILL and
requires that the agent cannot mint the flag. As written, it can. On hermes the agent
runs as uid 1000 and the repository clone at ~/okf-trading-system is agent-writable —
deploy.sh and journal-push.sh both run from it — so any path under ops/ is agent-
writable. The control would read as protection while providing none.
This is the same error corrected the same day in ops/recovered/ai-rig-bin/README.md,
where a file was declared off-limits to the production host while that host held a full
clone of the repository containing it. A control boundary is a property of ownership and
permissions, not of a path's name.
Proposed: the control files live at /etc/okf-adapter/, root-owned, mode 0755, on
a host where the agent has no passwordless sudo (verified 2026-09-29). The repo keeps only
the checker, ops/adapter/preflight.py, which verifies — before existence — that the
control directory and the flag are owned by a uid the agent is not and are not group or
world writable, and which refuses outright if the trusted uid equals the agent uid.
KILL short-circuits ahead of every other check; a valid flag cannot override it; and
because removal is also a root action, a trip cannot be undone by the thing it stopped.
Status: APPROVED by human:yasu, 2026-09-29, record
2026-09-29:adapter-changelog-01-approve. /etc/okf-adapter/ is now the ratified location
for the control files and supersedes the ops/adapter/ paths in the Execution-constraints
and Kill-switch sections below; ops/adapter/ holds the checker only.
Verified on approval, empirically rather than by inspecting mode bits — the property this charter rests on is that the agent cannot mint the flag, and it is the property this document got wrong twice:
$ ls -ld /etc/okf-adapter -> drwxr-xr-x root root
$ touch /etc/okf-adapter/agent-probe (as the agent, uid 1000)
touch: cannot touch '/etc/okf-adapter/agent-probe': Permission denied
$ python3 ops/adapter/preflight.py -> BLOCKED, exit 1
- no live-ok flag for 2026-09-29
Nothing is armed. The resting state is BLOCKED and arming is a root action per day.
Links¶
- Telegram approval flow — M2 protocol it consumes
- Position ledger — where fill receipts land
- Automation architecture — stages 7 and 8 boundary
- Framework charter — principle 3 (the inviolable boundary)