Repository navigation
feat(adk): the operator runbook and starter kit, first cut (ADK item 5; R1, R2, R7) - #464
Merged
Merged
Conversation
…5; R1, R2, R7)
ADK track item 5 and the operator's R1 ("a complete runbook, device to
verified kernel, against the current code") and R2 ("ask only what you
cannot do or find"). This is the starter kit that three runtimes load:
Claude Code (CLAUDE.md), Codex (AGENTS.md) and PCC's hosted agent (item 14).
It lives in starter/ until the operator picks its public repo.
- runbook/:
- 10 phases, from prerequisites to session. Each has a goal, exact
commands, done-when checks, the only questions to ask, and a report
call.
- runbook.json is the same graph in machine-readable form. Its phases are
painpoints' contract v1 enum, in order.
- Every command follows the path rehearsal R0 proved on a master gateway,
and each route was checked in master's code. Each known gap says "on the
current code" with a workaround, and "coming" with the change that closes
it (#390, #450, #454, #428, item 12, R8, kits, economics).
- The R2 asking rule and the 9 class-C intake questions, each with its "why".
Money and safety are never defaulted.
- Secrets live only in .pcc/ (mode 600, git-ignored). They are read with
$(cat …) and never printed; provisioning output goes straight to a file.
- The node's two-call finish (evidence, then status completed). Never
pcc_job_complete after it; never /api/setup/test-job as proof.
- Typed operations only: "never let a job's raw parameters reach the
device", because pcc-node's generic adapter is a passthrough (bus #4121).
- bin/pcc-report: stdlib Python that reports each phase in painpoints'
contract v1. It uses a persisted sessionId and seq, redacts on the client
side, never sends a transcript, builds the session roll-up from local
phase logs, and never blocks the onboarding.
Tests (stdlib unittest, 17): 8 for pcc-report (redaction, seq, contract
body, roll-up, a real POST to a local server, an unreachable gateway), and
9 guards for the runbook (the phase graph, each phase reports, no curl to
production, pcc-node[crypto] with a version or a pinned SHA,
pcc_job_complete and test-job only as warnings, provisioning written to
.pcc/, .pcc/ git-ignored).
agent: pcc-adk (4f6668ed)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… stay quiet The fixture pcc_live_AbCdEf0123456789 in test_pcc_report.py is synthetic, but it matched the review router's secret pattern. It is now concatenated at runtime, the same way agent-pack-truth.test.ts builds its fake PEM marker. All 17 starter tests still pass. agent: pcc-adk (4f6668ed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On master, /api/feedback stores each report's summary and detail and drops the attempt fields (phase, outcome, sessionId, seq) until painpoints' #458 merges. Phase 0 now says so, and asks for summaries that stand on their own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The operating loop polls GET /api/operator/jobs, so the channel that fills the operator status's slot 3 should give the gateway nothing to send to. The file transport is not dispatched on master, and once it is, its path would name a file on the gateway's own disk, not the operator's. "manual" says what happens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every authenticated curl in the runbook expanded the key into argv
(-H "Authorization: Bearer $(cat .pcc/api-key)"), where any user of the
machine can read it from ps or /proc/<pid>/cmdline. Phase 0 now writes
.pcc/auth.header (0600) next to .pcc/api-key, and every call uses
curl -H @.pcc/auth.header; the test buyer's key goes to .pcc/buyer.header.
The signing-key step reads its inputs from .pcc/ instead of the environment.
A new guard fails on any "Bearer $" or KEY="$(" line, and on any
authenticated $BASE curl without -H @.pcc/ (mutation-checked both ways).
18 tests pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed fields The provisioning response carries a server-minted Ed25519 private key as hex, as base64 PKCS#8 and in a secret-named JSON field. Hex was caught; the base64 form was not, by this client or by the gateway's redactSecrets. pcc-report now also removes: - a PEM private-key block, whole or cut off; - the value of any secret-named JSON field (private_key*, *secret*, api_key, password, mnemonic, seed); - a run of 40+ base64 characters that mixes upper case, lower case and digits (paths and prose are left alone; tested). The pcc_ key pattern now also allows _ and -, like the gateway's. Fixtures are built at runtime. Each new rule is mutation-checked. 20 tests pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At the release commit, load_or_create_keys without pynacl writes an HMAC placeholder pair to .pcc/node-keys.json, and later runs load that file even after pynacl is installed, so register_signing_key keeps refusing. Phase 5 now says: install the crypto extra, delete the never-registered key file, run the step again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Item 5 is split by file so both lanes' PRs come from master without conflicting (launch #4024, #4165): launch writes the participant-facing starter/README.md (its draft is item5-copy/README.draft.md); adk keeps the runbook, pcc-report, CLAUDE.md, AGENTS.md and .gitignore. The agent entry points already start at runbook/README.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… travels (R0 P9) Steward #3964 made rehearsal R0's P9 a runbook rule: agents send their own publicKey at provisioning, so they never receive a private key. Without one, POST /api/auth/provision mints an Ed25519 pair and returns the private half (hex and base64 PKCS#8) in the response. - Phase 0 step 3 (new) makes the node key with pcc-node's load_or_create_keys in .pcc/node-keys.json, refusing up front if pynacl is missing, and the operator's provisioning call (step 4) sends its public half. - Phase 5 registers that same key as the kernel's signing key; the recovery for a placeholder key now re-runs phase 0 step 3. - The test buyer sends a throwaway public key (a buyer signs nothing). - A guard requires publicKey in every provisioning call (mutation-checked on both calls). 21 tests pass. Dry-run of both provisioning commands against a stub gateway, with pcc-node at the release commit 81a0994: valid JSON bodies carrying a 64-hex key; .pcc/ 0700 and every file in it 0600. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
starter/runbook/index.json maps 22 events, keyed <phase>.<event>, to what to do, the runbook section that covers it, and the phase and outcome to report when the event ends a phase. Examples: the human says "don't know", a register call answers 4xx, the node key was made without pynacl, the emergency stop is set, a secret leaked (provision a new key, then revoke the old one with DELETE /api/auth/keys/<id>). runbook.json points at it, and the runbook README says when to look an event up. The keys are the draft launch has (#4228); launch's guidance text goes at guidance/<key>.md, read if present. 4 new guards: keys match their phase, every section is a real heading (outside code fences), reports use the contract's phases and outcomes, and runbook.json links the index. Each is mutation-checked. 25 tests pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The runbook's pre-publication install pointed at release/pcc-node-0.1.1's 81a0994, which still turned a polled job's fields into device commands (an Opentrons robot playing a job's Python, HTTP passthrough, OctoPrint select+print) and skipped the gateway's TLS check (verdict 68b, 3 CRITICAL). The release branch now carries #442's round-2 fixes: 1e9308a (407 pcc-node tests; the release gate passes on its wheel and sdist). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… defaults money Verdict 115b on #464. 16 new guards in starter/bin failed at 932c0ae. Now 41 tests pass: - F2: "completed" is sent only after an evidence receipt that says "stored": true for this jobId. The relay answers 200 with stored:false for an unknown job or a storage failure. New index event: verify.evidence-not-stored. - F3: nothing calls the result verified. It is a registered kernel exercised by one real job, whose evidence stays unverified until the operating agent signs it (README, AGENTS, CLAUDE, phases 5, 6, 9, graph). - F4: the capability is priced from .pcc/intake.json, not a literal 25. register-device lists no capabilities, so no zero-priced duplicate. The payout wallet is asked in phase 0 and sent at provisioning, where it becomes the operator id. The buyer test stops (verify.price-mismatch) unless the quote equals the human's price. The license rate is never offered or written as a default. The publish text follows economics (#4807, #492). - F5: the e-stop drill says what it shows (the gateway refuses new jobs for a stopped kernel; the device's own stop works) and what it doesn't. pcc-node 0.1.1 does not read the flag. - F6: the envelope digest is re-checked against envelope.confirmed.json before any run (phases 6 and 7). - F7: the kernel is fetched and checked before its signing key is registered. - F8, F9: runbook.json matches the phases. Register no longer needs pcc-node start, verify asks for the drill, and target.default is null. - F10, F11: pcc-report never raises on a malformed gateway address or unwritable state, and repairs a permissive .pcc to 0700. Phase 0 chmods .pcc too. - The kernel's text address goes in physicalAddress. Also for 0.1.1: pcc-node needs https or http to 127.0.0.1/[::1]. Don't run pcc-node start beside the loop, because its heartbeat says acceptingJobs: false. The install pin moves to release r2 @dcc44db9. F1 (operator-route ownership) is the gateway's WP-C; until it carries it, onboard only on a private gateway. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed, and the stop reads clear only from a stored policy (verdict 115c) Each finding was reproduced at cfc5605 first. In bin/test_runbook.py, TestRound3's five checks failed on the old text; the two new pcc-report tests raised on the old script. - F1: phase 6's test job is created with POST /api/jobs/submit, which makes a queued job and nothing else: no quote, no escrow, no payment. The runbook checks that its settlement shows no session and no escrow before running it. submit-from-discovery is named only in a warning, and the late, non-enforcing price check is gone with it (index event verify.price-mismatch becomes verify.money-attached). - F2: no wallet is asked for or sent. Phase 0 tells the human that PCC cannot yet pay a wallet they name, so the runbook runs test jobs only. Phase 7's new rule 0 refuses any job whose settlement shows a negotiation session or an escrow (payout_destination_unbound). Paid operation waits for a payout destination the human confirms and checks as the actual recipient. - F3: the final status is computed by one script from the device's last run answer and the evidence receipt: completed only for a succeeded run whose receipt says stored: true for this jobId with a non-empty bundleId. The status call posts that file; no command types "completed". - F4: the stop reads clear only for a stored policy (updatedAt present, no source "default") whose emergencyStop is false. The drill now checks the read gives STOPPED, then CLEAR. - F5: the provisioning body is built from .pcc/operator.json (written with the agent's file tool) into a private file, sent with --data-binary @file, and deleted. No personal value is on a command line. - F6: pcc-report treats attempt state that is not an object as absent, and the session roll-up skips lines that are not complete phase objects. The old test that asserted walletAddress was sent and the quote was checked encoded the premises this verdict disproved, so it is replaced by TestRound3. The provisioning public-key test now also accepts a body file built just before the call. starter tests: 47 passed. agent: pcc-adk (4f6668ed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…teway cannot run its test job (verdict 115d)
Both CRITICAL findings were reproduced first.
- C1: phase 6's first device run came before its first policy read. A trace
on the real gateway: with the kernel's stored policy at emergencyStop true,
POST /api/jobs/submit still answered 200 queued, and the operator poll
listed the job. So the runbook's own check is the only guard.
Phase 6 now starts with the stop: how to read it, and the drill. The drill's
stop and resume leave the stored policy the later checks need. The stop is
read again before submitting, and immediately before the run (section 3
step 5). Phase 7's rule 5 reads it once more right before every run. The
drill no longer claims the gateway refuses a job for a stopped kernel,
because the current gateway queues it.
- C2: a trace on the real gateway: POST /api/jobs/submit for the gateway's own
local kernel answered {"deviceId":"dev_fdm_001","status":"accepted"},
started at once on a local device. An external kernel's job answered
{"deviceId":null,"status":"queued"}.
Before submitting, the runbook now requires GET /api/setup/detect to list
only mock devices the gateway runs itself (or none), so a local dispatch
cannot actuate anything real. After submitting, it requires status queued
with no deviceId, or it stops. The text no longer says the route "creates a
queued job and nothing else". New index events: verify.gateway-runs-it and
verify.stop-not-clear.
TestRound4's three checks fail on the 7b155b2 runbook files and pass now.
The round-3 test's section reference follows the renumbering. Starter tests:
50 passed.
agent: pcc-adk (4f6668ed)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… one without running it (verdict 115e) Phase 6 no longer submits a test job. The gateway's setup report lists only its configured devices, so no runbook check before a submission can show that the submission will only queue. Phase 6 now ends with the drill and reports the test job blocked until the gateway offers a server-enforced queue-only submission. Section 3 stays as the by-hand run for that day. Phase 7 reports operate blocked: on the current gateway it has no job it may run (paid jobs wait for payout binding, test jobs for that submission). The drill-failure event no longer treats the gateway queueing a job for a stopped kernel, which the runbook documents as current behaviour, as a failure. Phase 7 points at the envelope check where it now is (phase 6 section 3 step 3). Tests: TestRound5 (3 tests); the round 3 and round 4 submission checks now assert that no command anywhere creates a job. Each new or changed check fails at 487ab59. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d the test job waiting (verdict 115e) The README, AGENTS.md, CLAUDE.md, the phase table, intake's payout note and runbook.json's title still said the kernel ends exercised by one test job. Phase 6 no longer runs one on the current gateway, so they now say what it ends with: registered, its emergency stop drilled, its test job waiting for a queue-only submission, and no job that carries money. Test: TestRound5.test_no_summary_says_a_test_job_ran fails at 487ab59. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rrent gateway (verdict 115e) Phase 9's example still reported "session ok" with "one test run". With verify and operate ending blocked, the session is blocked too, and ok only once a test job has run and the loop has a job it may run. Test: the no-summary check now also refuses "one test run" and "session ok"; it fails at 487ab59. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ADK item 5: the operator runbook and starter kit (first cut)
This covers the operator's R1 ("a complete runbook, device to verified kernel, against the current code"), R2 ("the agent asks only what it can't do or find"), and the pack side of R7 (report every phase). It is a starter kit that three runtimes load: Claude Code (
CLAUDE.md), Codex (AGENTS.md) and PCC's hosted agent (item 14). It lives instarter/until the operator picks its public repo.What's in it
starter/runbook/runbook.jsonis the machine-readable graph, and its phases are painpoints' contract v1 enum.starter/runbook/index.json<phase>.<event>("don't know", a 4xx on register, the e-stop set, a leaked key…), each with what to do, the runbook section, and what to report.starter/bin/pcc-reportsessionIdandseq, redacts on the client side, sends no transcript, builds the session roll-up from local logs, and never blocks the onboarding. Stdlib only.starter/CLAUDE.md,starter/AGENTS.mdThe participant-facing
starter/README.mdis pcc-launch's, in its own PR from master (item 5 is split by file).|
starter/.gitignore|.pcc/, which holds this attempt's secrets (mode 600) and state. |How it was grounded
submit-from-discovery→ the node's evidence-then-status finish.Safety lines it holds
.pcc/(0600), and never printed. The node's private key is made locally and never sent (provisioning sendspublicKey). curl sends the API key from.pcc/auth.header(-H @file), so no command line carries it forpsto show. Provisioning output goes straight to a file, since it includes a private key.test-jobis not used as proof,proveis self-attestation, and a by-hand run is stored unverified until the operating agent's signed evidence arrives (D4a, feat(gateway): operator status derives readiness from canonical rows (ADK D4(a)) #428).Tests (stdlib
unittest, 25)pcc-report: redaction, seq, the contract body, the roll-up, a real POST to a local server, and an unreachable gateway.curlto production;pcc-node[crypto]with a version or a pinned SHA;pcc_job_completeandtest-jobonly as warnings;.pcc/;.pcc/git-ignored;-H @.pcc/…(mutation-checked);publicKey(mutation-checked);Run:
python3 -m unittest discover -s starter/bin -p 'test_*.py'.Commits after the first cut:
/api/feedbackkeeps (summary and detail) until painpoints' feat(gateway): attempt reports on /api/feedback (ADK track item 3) #458;manualchannel instead of afileone;Next: R1 (10-08) exercises it end to end, and the final cut is due 10-15.
🤖 Generated with Claude Code