Skip to content

PTYCAP-05: throwaway-repo due->PR proof (code + owner runbook) [QC BLOCKED] - #511

Draft
finedesignz wants to merge 136 commits into
mainfrom
phase-ptycap-05-throwaway-repo-proof
Draft

finedesignz wants to merge 136 commits into
mainfrom
phase-ptycap-05-throwaway-repo-proof

Conversation

@finedesignz

Copy link
Copy Markdown
Owner

PTYCAP Phase 5 - Throwaway-Repo Due->PR Proof (code + owner runbook)

Stacked on #506 -> #505. Merge those first (base of this branch is the Phase 4 branch; this PR targets main so Woodpecker qc runs).

Status: DRAFT - /qc BLOCKED (2 open MAJOR after 2 fix rounds). Live proof is an OWNER GO/NO-GO gate and was NOT run.

What it builds (all flag-gated, default OFF = true no-op)

  • orchestrator_proof_runs table + DAL (additive idempotent DDL; frozen budgets with CHECKs; single-flight partial unique indexes; canonical lowercase repo identity).
  • Arm / disarm / enable cores + operator CLI under one hub-global advisory lock; arm is fail-closed (GitHub ruleset assertion, zero bypass actors, non-admin agent, no foreign session halt, no other enabled orchestrator task).
  • Never-merge: GitHub ruleset as the preventive control (frozen id + digest, re-asserted before every dispatch), hub detective layer (any default-branch advance since base_sha terminates), merge-command refusal for proof repos (fail closed on null repo key). Prompt is advisory only; a proof-owned task can never render the dev macro under any flag combination.
  • Hard budget gates (tokens / injects / wall time) on the orchestrator gate lists; durable attribution check; lifetime counter key pinned.
  • Verifier: PR scope (pagination since armed_at, fork + off-prefix rejection), CI green -> pr_open_green, else a typed "did nothing and why" terminal.
  • Read-only GET /api/orchestrator/proof (OpenAPI + docs synced).
  • Owner runbook: docs/ptycap-05-proof-runbook.md (throwaway repo + ruleset, GitHub App permissions incl. the bypass_actors visibility decision, arming, rollback order: kill switch/disarm FIRST, then flags).

QC

Seats: sonnet correctness + adversary lenses, Codex read-only (agy MISSING). Record: .planning/phases/PTYCAP-05-throwaway-repo-due-to-pr-proof/QC/.
Open MAJOR (see QC/VERDICT.md): (1) attribution check compares aggregate counts, not per-turn; (2) a manual session halt engaged AFTER arm is indistinguishable from the proof-owned row. Both reachable only after an owner arms the proof.

Verification

  • bun run check-baseline: OK (pass 3169, skip 259, fail 0). One known flaky timeout (transcript-adapter-codex, AgentAutofix 74489ffe) on a first run.
  • Real-Postgres e2e helpers run only in Woodpecker (no local Postgres).

Not done here (owner-gated)

SC-2/SC-3 live proof: arming REMO_ORCHESTRATOR_AUTOSPAWN + proof flag + allowlist in prod. Runbook steps in docs/ptycap-05-proof-runbook.md.

🤖 Generated with Claude Code

finedesignz and others added 30 commits October 13, 2026 10:28
Goal-backward verification of Phase 1 (PTY token accounting, #395) and
Phase 2 (PTY pre-flight gate, #493) against ROADMAP success criteria, with
every proving test run per-file.

- Phase 1: 3/3 SC implemented; gaps_found (G-1 MAJOR: emitter dedupes on
  record uuid, not message.id, so the PTY ledger over-counts; G-2 MINOR:
  live-Postgres proofs not observed locally).
- Phase 2: passed 3/3; three MINOR gaps (CRLF-fragile guard test, stale
  docs mechanism claim, Telegram permission keystrokes un-preflighted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read-only recon of the gate chain, actor inference, PTY write paths,
counters, owner notification, token-cap defaults and kill-switch
precedents, with file:line evidence. Input to discuss/plan for the
governed-automation guard and the lifetime counter + kill switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ONTEXT

Unattended discuss for Phase 3 (governed-automation guard): the eight
design forks were answered by a claude+codex+agy design-fork panel (record
in GATES/design-fork-1.md, VERDICT PASS with amendments). CONTEXT captures
the binding decisions D-01..D-17: governance primitives only (no PTY
byte-producer), fail-closed lifetime/kill-switch slots, enabled-ceiling
rule, call-time flag default OFF, hard rename with eligible-actor rule,
automation-only governed chain (human chain untouched), door pins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Planner (opus) output verified by gsd-plan-checker: 0 blockers, 2 warnings
(fails_when pass-count floors; plan 03-04 doc-heavy scope), accepted for
execution. Waves: 03-01 tracer (governed-pty module + rename + OFF==legacy
differential), 03-02 preflight automation branch + CRLF fix, 03-03 door pins
+ spoof tests + Telegram required source, 03-04 docs/OpenAPI/baseline gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with OFF==legacy differential

- add hub/src/dispatch/governed-pty.ts: flag reader, eligible-actor rule, four governors (kill_switch/lifetime_counter fail closed), test-only DI, governedAutomationPtyGate
- export isDailyTokenCapEnforced from gates.ts
- telegram dispatch composes governedAutomationPtyGate
- add differential test against a frozen legacy oracle and flag-ON telegram cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tionPtyGate

- ask and work compose governedAutomationPtyGate with unchanged server-inferred resolvers
- pin that an external-ask actor stays blocked with the flag ON and every governor passing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnor semantics

- remove humanOnlyPtyGate from gates.ts (hard rename, no alias); keep AUTOMATION_ACTORS and humanOnlyRejectsActor
- human-only-guard test targets governedAutomationPtyGate with the flag pinned OFF
- pin governor order, enabled rule, fail-closed slots, throw, non-PTY, DI-only and no-alias

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…preflight

- automation actor refused before any DB gate unless the governed-automation flag is ON
- flag ON: spend chain then governors, prefixed reasons; throw/hang stay fail closed
- human chain and scanned gates literal untouched

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… humanInteractive guard CRLF-safe

- no governor in either chain constant; human turn passes with fail-closed governors and flag ON
- split source on optional CR plus LF (Phase-2 MINOR-1)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ed when missing

The webhook and the doctor replay assert source 'human' at the authenticated
boundary; a call without a source is refused on a pty-interactive session
instead of defaulting to human (D-09 iv).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s and preflights as human

- term.input with source/actor extras -> preflight actor human, forwarded keys exactly type/session_id/bytes
- term.attach_file stays preflight-exempt (D-10) and forwards no source/actor keys

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…outing, privileged callers, flag/DI, exemptions

- emitter allowlist (ws/client.ts, telegram/transcript/pty-inject.ts) with non-vacuous self-check
- supervisor canary: user_message/permission_response never touch the PTY, term.input does
- caller pins for automation/human actor, preflight, injectPtyKeystroke, dispatchToSession
- flag/DI literals confined to dispatch/governed-pty.ts; actor-inference and Telegram source pins
- EXEMPTIONS record: injectPtyKeystroke and term.attach_file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-2 mechanism claims, Phase-4 hand-off

- usage-cost.md: PTYCAP Phase 3 section; structural reason automation cannot reach the PTY
- CLAUDE.md: REMO_GOVERNED_AUTOMATION_PTY env bullet, docs-map row, invariant, token-cap fail-closed fix
- ROADMAP: Phase 4 hand-off note (no SC change)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate rename across docs and SESSION-ASK spec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onPtyGate; finish rename in hub/src comments

Comment-only source changes; docs:sync regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…59 fail 0) with phase-3 accounting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nity) ceiling config

maxInjectsPerHour() coerces non-finite env to the default 4, so the governor
passed an invalid value. Add isInjectRateConfigured() (raw validity, governor
only; legacy gate behavior unchanged) and align docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…am/doctor.ts)

doctor.ts takes dispatchToSession as an injected dispatch param, so the bare
call-site needle missed it. Pin importers instead (webhook + doctor), and
assert the doctor replay call passes source: 'human'. Docs name doctor.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pass)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight executable plans (G-1 emitter fix first, tracer-led controls core, counting expansion, PTY door + governors, owner surfaces + 60% alarm, kill-switch coverage, spawn-site guards, e2e + docs + 20M cap last), VALIDATION map with source coverage audit, and 04-FORKS.md recording planner forks for panel confirm or overturn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply the locked fork-panel verdicts and the plan-check fixes: heal route classified machine (F-2), separate final halt check before send (F-3), release resumes every scope and once-task re-arm (F-4), 80 percent lifetime warning page (F-5a), triage key per application (F-5b), one atomic lifetime + de-duplicated rate-union reservation with routine_run_log.inject_token (F-6), zero new skipped tests with the DB proof moved into 04-02 via an existing e2e test body (F-8). 04-02 split into 4 tasks, 04-03 gains a run-log token task, 04-07 gains a heal task, 04-08 reduced to docs + cap-last-code-commit. Includes 04-FORKS-PANEL.md and 04-PLAN-CHECK.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ima and positive deltas

- G-1/D-15: records of one assistant message share message.id and carry cumulative usage; count once
- key = message.id, fallback uuid; negatives clamp to 0; maxima never decrease; replay emits nothing
- FIFO bound SEEN_UUID_CAP kept; six new fixtures in pty-usage-tail.test.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
finedesignz and others added 27 commits October 13, 2026 17:45
- disarm transaction runs first; session kill switch best-effort after, never throws
- enable only on an armed run (proof_not_armed otherwise)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arm with static guard

- refuses before importing the DAL without DATABASE_URL
- read-only installation/session lookups live in a DAL file (script holds no SQL)
- static scan: no env assignment, no Coolify, no network, no SQL text, no cap setter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stack

- 7 OFF combinations: zero sql, zero fetch, no proof guard/verifier/github, identical inject args
- gates pass through with zero dep calls; merge routing unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… e2e body

- proof-checks.ts: 8 sections (DDL x2, 5-way arm single-flight, partial unique indexes,
  CHECKs, exactly-once claim/terminalize, attribution survives session delete,
  budget gate on real ledger, exact-row disarm); own synthetic users, cleanup by id
- called from the first autospawn e2e test body: zero new test() calls, zero skips

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o new skips

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ay-repo proof

- ordered rollback (kill switch or disarm first, then flags, then redeploy)
- GitHub App permission grant, ruleset and non-admin credential prerequisites
- correct autospawn runbook pr_url statement; point at the proof runbook

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… permissions

- CLAUDE.md env entry, docs map row, gate-list invariant
- auto-dev-orchestrator and usage-cost proof sections
- HANDOFF: extra read permissions the proof needs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r Phase 5

- 10 tests: OWNER GO, rollback order, permissions, empty bypass list, refusal codes, no merge instruction
- ROADMAP: plans ticked, progress row 'live proof pending owner GO', Phase 5 stays unchecked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…MAJOR 1)

Arm, the dry run and --enable refuse (other_orchestrator_task_enabled) while ANY other
orchestrator task is enabled, across all users, because the REMO_ORCHESTRATOR_* flags are
hub-global. At runtime, with the flag stack ON, the proof guard blocks and the budget gate
refuses every orchestrator dispatch that is not the claimed proof task
(proof_mode_non_proof_task); the old pass_through for a task without a proof row is gone.
Runbook prerequisite and refusal-table row added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (codex #2, codex #3 recheck)

Proof dispatch requests now carry a server-only proofRunId (DispatchRequest, set by
runMacroCycle through injectOrchestratorPrompt on both the online and autospawn paths). The
budget gate and a new final pre-send check (finalHaltCheck, reached only by proof gate lists and
only with the flag stack ON) require it to equal the ACTIVE run, so an ordinary dev prompt queued
or parked before the arm - which carries the reusable task:<id> counter key - can never be
authorized as proof traffic or reach send, and a run that goes terminal after the gates still
stops the send. Arm now also halts the session (session kill switch) and cancels pending routine
cycles in the same transaction; --enable releases the halt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… terminal transition (codex #3)

disarmProofRunTx now inserts the session kill-switch row first, in the same transaction and
before the run UPDATE to disarmed, so a rollback undoes both and there is never a committed state
where the run is terminal but the session can still be dispatched to. The separate best-effort
post-transaction engage (and its dep) is removed; the script reports the in-transaction engage.
The pre-send proof-run identity recheck landed with the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der any flag combination (adversary MINOR 2)

The arm transaction marks the task scheduled_tasks.proof_owned (additive idempotent column,
set on both created and reused tasks). The marker rides the task row the cycle already loads
(getOrchestratorTaskForSession -> resolveCycleContext -> MacroCycleInput.proofOwned), so
runMacroCycle refuses (proofDecision blocked) before any reconcile or db work whenever the full
flag stack is not ON; with the stack ON the proof guard still decides. Non-proof tasks are
untouched, so flag-OFF stays a true no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the proof as budget_attribution_lost (codex #4)

The agent ws handler logged and dropped a failed token_usage insert, so the proof's hard token
budget silently under-counted while the session row still existed. The handler now marks the
session in an in-process registry; sumSessionTokensSince reports attributionLost for any proof
window the loss falls in, and the budget gate and tick evaluator both fail closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R to come from the armed repo (codex #5)

The adapter dropped every off-prefix PR before the verifier could judge scope, and a fork PR
reusing the exact proof/<run-id> branch name could be reported pr_open_green. listPulls now returns
all recent PRs with head repository, author and creation time; the verifier terminates a PR created
after arming from any non-proof/ head, and a run-branch PR whose head repo is not the armed repo
(or is deleted), as pr_scope_violation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… allowlist, proof row, session match and merge refusal (codex #6)

owner/repo path names are case-insensitive, so github://Owner/Repo and github://owner/repo were two
idents for one repository and bypassed single-flight and the merge refusal. armProof and
armProofRunTx canonicalize through lib/repo-key.ts, findActiveProofRunForRepo canonicalizes the
lookup, and schema.sql adds an additive NOT VALID CHECK (repo_ident = lower(repo_ident)) so the
existing partial unique indexes are single-flight over the canonical value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o repo key (lens-correctness MINOR 2)

With the proof flag stack on, runMergeToMain skipped the proof-repo refusal entirely when
ctx.repoKey was null, leaving the only auto-merge path unguarded for a keyless session. It now
refuses with proof_repo_never_merges:unverifiable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unds the budgets (adversary MINOR 3, 6)

An admin agent identity can edit the ruleset and merge in one turn, yet arm only warned when
--agent-login was missing or unreadable; it now refuses agent_permission_unverified. The owner-supplied
"hard" budget had no upper bound; arm refuses above injects 50 / tokens 50M / wall 24h and schema.sql
adds matching additive NOT VALID CHECK ceilings. Runbook updated (agent-login required, ceilings,
canonical repo ident, attribution-loss and PR-scope behavior).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m and enable (codex r2 new #1)

The exclusivity count ran under a per-user lock, so two users could both see zero
enabled orchestrator tasks and both enable. All proof state changes now share one
global lock and recheck the count under it. The fake sql models real blocking
advisory-lock semantics so the race is exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-send boundary under the arm/disarm lock (codex r2 #2, #3)

buildSend persisted the prompt (awaited) after the pipeline's final halt check, so an
arm or disarm that committed in that window was followed by a stale send. With proof
mode ON the delivery now runs after persistence behind sendAtProofBoundary: a fresh
kill-switch + active-run identity recheck while holding the same global advisory lock
arm/disarm/enable take. Flag OFF is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…emoves allowlist case twins (codex r2 new #2, sonnet MINOR 1, MINOR 3)

Arm now refuses (session_halt_engaged) when a foreign session halt is engaged, releases
only stale halts left by the proof tooling, records the id of the row it engaged on the
run (additive kill_switch_id column) and --enable releases exactly that row, so an owner
emergency stop engaged after arm survives. Disarm deletes the allowlist row by
lower(repo_ident), so a mixed-case twin no longer survives. Runbook gets the new refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nspected, fails closed otherwise (codex r2 #5)

The adapter read only the newest 50 PRs, so a post-arm scope violation on a later page
was invisible. It now pages newest-first until a page is short or its last PR predates
armed_at (the verifier passes it); hitting the page cap or any page error throws and the
verifier stays pending (never green).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… durable across restart (codex r2 #4)

sumSessionTokensSince now also compares completed assistant turns of the session since
arming (older than a 120s grace) with token_usage rows over the same window; fewer rows
than turns reports attributionLost, which the budget gate and tick evaluator already
terminalize as budget_attribution_lost. The in-process mark stays as an additional early
signal. No new DDL; nothing is evicted or forgotten on restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-owner, SHIP-AFTER-OWNER-GO)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agentautofix-fixer

agentautofix-fixer Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

AgentAutofix AI Review

claude

PASSED

  • warning hub/src/dispatch/gates.ts: Default daily token cap halved (50M -> 20M) for any deployment that hasn't set REMO_ORCHESTRATOR_DAILY_TOKEN_CAP -- DEFAULT_DAILY_TOKEN_CAP changes from 50,000,000 to 20,000,000. This affects every environment that relies on the default and could unexpectedly throttle production usage (including the owner's own interactive PTY sessions, per the docs) immediately on deploy. The PR documents this risk extensively, but it's a meaningful behavioral change worth highlighting as a deploy-time risk rather than a pure doc/code tweak.
  • info ``: Diff truncated before full review completed -- The supplied diff was cut off (exceeded the review size cap) partway through hub/src/dispatch/automation-controls.ts. Everything reviewed up to that point (kill-switch API, lifetime-counter ledger, proof-run DAL/transactions, heal-route reservation/release logic, schema migrations) is internally consistent, uses parameterized SQL throughout, and fails closed by design. The remainder of the diff beyond the truncation point was not visible and could not be reviewed.

codex

BLOCKED

  • blocking hub/src/db/orchestrator-proof-dal.ts: Disarm deletes a pre-existing allowlist entry -- armProofRunTx permits the target repo to already exist in orchestrator_autospawn_allowlist and then uses ON CONFLICT DO NOTHING, but disarmProofRunTx unconditionally deletes that (user_id, repo_ident) row. Thus arming and disarming a proof can silently destroy configuration that predated the proof. Record whether the arm transaction created the entry and delete it only when proof-owned.
  • blocking hub/src/db/orchestrator-proof-dal.ts: Re-arming accumulates duplicate proof rows -- Each arm unconditionally INSERTs an enabled orchestrator_rows row with command='proof', while disarm only disables prior rows and subsequent arms reuse the same scheduled task. Re-arming therefore either fails if a uniqueness constraint exists or creates multiple proof due rows that can trigger duplicate cycles. Reuse/reactivate exactly one row or remove the proof-owned row transactionally during disarm.

agy

advisory -- not part of the gate decision

PASSED

No findings.

Result: Blocking finding from: codex

Reviewer models (requested pin, not independently confirmed):

  • claude: claude-haiku-4-5-20251001 (requested pin, not independently confirmed)
  • codex: gpt-5.5 (requested pin, not independently confirmed)

finedesignz and others added 2 commits October 13, 2026 20:10
…agged session halts

Budget attribution: chose the nullable additive turn_id column on messages and token_usage
(supervisor stamps one id on a turn's assistant_message and result/usage_event) over a usage
outbox: smaller, no new table, no backfill. The durable detector now anti-joins each completed
assistant message to a token_usage row of the SAME (session, turn_id); aggregate counts are gone
and a message without a turn_id fails closed.

Kill switch: session halts are unique per (session_id, engaged_by) while engaged, so a manual
emergency engage after arm is a separate row, effective on its own, and proof --enable releases
only its own row by id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tive MAJOR recorded)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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