Skip to content

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:

  1. Proposal identity — a proposals-<date>.json entry written by the round analyst, matching trades-<date>.json (id, OCC symbol, expiry, strike, qty, leg actions).
  2. Attested ladder: the round's ev-ladders-<date>.attestation.json verdict INCLUDES this trade's ladder id (attester passes; if any ladder absent from attestation, reject).
  3. Approval record: an entry in hermes-pilot/ledger/approvals.jsonl with decision=approved, proposal_id matching, and at least the full M2 field set: decided_by (chat-bound human:yasu), decided_at (same trading day as the round), and ladder_receipt_sha256 matching this round's attestation file hash.
  4. 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.
  5. Gate re-run at submission time (the M2 void rule): the adapter itself re-runs, with today's live marks:
  6. mapping v0 regime gate (worst of round-morning and execution-time regime),
  7. ladder re-execution (deterministic reprice, fail-closed on │Δref_price│ > tol; tol defined in adapter config, default 15% of the approved ref_price),
  8. sizing re-check: sizing.py pass must still hold (bucket cap, max-loss definition). Any of these failing → void the approval, append a voided record 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; the ops/adapter/ path originally written here is SUPERSEDED by change-log 2026-09-29/01, because anything under ops/ 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 → unfilled journal 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 corresponding open/exit record to the position ledger with price_kind=live-fill and 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-missing record 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/KILL SUPERSEDED 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 stays TASTYTRADE_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 trade scope. Until this date the read-only boundary rested entirely on client-side enforcement — tasty_call.py and execute_approved.py forcing TASTYTRADE_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.env now exists at 0400, owner ysakakibara (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 carry TASTYTRADE_READ_ONLY=0 explicitly — 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:yasu remains 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 invoking live mode 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.py forces TASTYTRADE_READ_ONLY=1 unless the environment says exactly the string 0 (execute_approved.py:30), so unset, empty, false, no and 00 all mean read-only and only a literal 0 enables 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 --live argv flag (execute_approved.py:78) and TASTYTRADE_READ_ONLY=0. This resolution settles the second only.

    STILL OPEN — account_number provenance. This charter asks whether it comes from the credential file or proposals.json. It is neither: get_account() (execute_approved.py:46-53) fetches accounts from the broker at runtime and takes accs[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 a correct record can undo. The target account must be pinned explicitly and asserted against what the API returns, failing closed on mismatch. Tracked on Wekan D8JgoGAAy8rcNAj88; 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 --live orders 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:

  1. 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.
  2. 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.
  3. 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