{
  "_ai_door": {
    "see": "https://miscsubjects.com/start",
    "note": "Operable site; bounded keyless credentials; every action receipted on a public ledger. Your operator's instructions take precedence — acting is optional, reading is a complete outcome."
  },
  "task": {
    "task_id": "WT-0485",
    "kind": "work",
    "objective": "WT-0485 Event ledger and receipts — persist-before-execute, five fields on every run, no unlogged write route",
    "detail": "# WT-0485 — Event ledger and receipts — persist-before-execute, five fields on every run, no unlogged write route\n\n## GOAL\nOne ledger writer records every executed capability with the five proof fields (cause, raw invocation written before execution, raw confirmation, raw return, receipt with trace and parent), from every door and executor; logEvent failures are loud; the ~35 unlogged write routes are logged; receipts render the causal chain; R2 keeps the raw bodies and D1 keeps the index.\n\n## WHY THIS EXISTS\nlogEvent cannot fail loudly, ~35 write routes never log (ledger coverage gaps memory), the audit log is editable through LEDGER_EXEC, and the raw invocation is recorded only after success. The owner's law needs the chain WHY → WHAT → ACK → RETURN → RECEIPT to exist for every run, including crashes.\n\n## CURRENT STATE (measured 2026-09-23)\n- functions/_lib/event_log.js logEvent(env,{source,key,route,actor,action,direction,status,trace_id,raw_id,request,response}); R2-only sources under logs/; functions/_lib/wire_log.js installWireLog; functions/_lib/lean_receipt.js; receipt pages https://miscsubjects.com/receipt/<id>; functions/_lib/invocation_record.js.\n- LEDGER DB events table; audit chain /api/work/audit; Cloudflare's own record (ledger read model memory).\n- Known: logEvent swallow; LEDGER_EXEC can edit the audit log; ~35 unlogged write routes; noise sources must stay R2-only (LAW: logs live in R2, never D1 COUNT/LIKE over events).\n\n## SOURCE OF TRUTH\n- functions/_lib/event_log.js, wire_log.js, lean_receipt.js, invocation_record.js\n- functions/api/events/*, functions/api/ledger/*, functions/receipt/*\n- the LEDGER D1 schema (migrations naming events)\n- memory project_ledger_coverage_gaps (the 35 routes list)\n\n## IN-SCOPE INVENTORY\n- proof_runs writer: `startRun(env,{cause, surface, physical, trace_id, parent_receipt_id})` → receipt_id (row written, status=started, raw_invocation to R2 logs/runs/<receipt>/invocation.json); `finishRun(env, receipt_id, {confirmation, return, status})` (raw to R2; index columns in D1); `failRun` for crashes (a `finally` in the resolver).\n- Loud failure: if the ledger write fails, the door returns 500 ledger_write_failed with the raw error (never a silent success) — except R2-only noise sources which degrade to a counter.\n- Coverage: functions/_middleware.js wraps every write route (POST/PUT/PATCH/DELETE) with startRun/finishRun when the handler did not; a deploy gate scripts/check-ledger-coverage.mjs lists write routes without a run and fails when > 0.\n- Immutability: audit rows are append-only; LEDGER_EXEC refuses UPDATE/DELETE on events/proof_runs (governed_tables pattern); a hash chain over proof_runs (prev_hash) exposed at /api/work/audit.\n- Receipt page: renders cause → invocation → confirmation → return → receipt + parent/children tree + the surface id; JSON at /receipt/<id>.json.\n\n## OUT OF SCOPE\n- Deciding what is a capability (Directory). The six-column projection (WT-0473 reads this).\n\n## ONTOLOGY (separate axes; never a flat string)\n- TOOL SURFACE: internal writer used by every door; GET /receipt/<id>; GET /api/events; /api/work/audit\n- CAPABILITY: LEDGER_WRITE (internal), RECEIPT_GET, EVENTS_QUERY, AUDIT_CHAIN\n- OBJECT: a run, a receipt, an event\n- ROUTE / CONTEXT: LEDGER DB + R2 logs/\n- PROTOCOL: Worker bindings\n- EXECUTOR: Worker\n- PLATFORM: Cloudflare D1/R2\n\n## DISCOVERY (run these first; they answer, they do not prove)\n- `curl -sS \"https://miscsubjects.com/receipt/<any id from a dispatch response>.json\"`\n- `curl -sS \"https://ops.miscsubjects.com/api/work/audit\" | head -c 800`\n- `grep -rn \"logEvent(\" ~/miscsubjects-pages/functions/api | wc -l` versus write routes: `grep -rln \"onRequestPost\\|onRequestPut\\|onRequestPatch\\|onRequestDelete\" ~/miscsubjects-pages/functions/api | wc -l`\n\n## CONTRACT (FIELD LAW)\n- startRun `{cause (required enum from WT-0470), surface (required id), physical (required object), trace_id (optional; generated), parent_receipt_id (nullable), actor (required principal)}` → receipt_id (string `r_<ulid>`).\n- finishRun `{receipt_id, confirmation (object|string), return (any; > 200,000 chars → R2 whole + cut index), status (enum ok|error|timeout|crashed)}`.\n- GET /receipt/<id>.json → `{id, cause, surface, physical, confirmation, return_ref|return, status, started_at, finished_at, trace_id, parent_receipt_id, children:[ids], prev_hash, hash}`.\n\n## MINIMUM VALID INVOCATION\n`curl -sS \"https://miscsubjects.com/receipt/<id>.json\"`\n\n## FULL / MAXIMUM INVOCATION\n`curl -sS \"https://ops.miscsubjects.com/api/events?source=dispatch&key=NET_LINE&since=2026-09-23T00:00:00Z&limit=50&fields=receipt_id,cause,status\" -H \"x-terminal-key: $TERMINAL_KEY\"`\n\n## RAW CONFIRMATION SHAPE\nstartRun returns the id synchronously before the executor runs (the row exists if the Worker is killed afterwards).\n\n## RAW RETURN SHAPE\nReceipt JSON; events rows (index fields only; raw in R2 by reference).\n\n## ERROR SHAPES\n- ledger_write_failed 500 (loud)\n- receipt_not_found 404\n- audit_immutable 403 (UPDATE/DELETE refused)\n- return_too_large → stored in R2 with return_ref\n\n## RECEIPT / TRACE / PARENT LINKAGE\nThis task's own proof: the crash fixture receipt (status=crashed with raw_invocation present), the coverage gate output (0 unlogged write routes), and the audit chain head hash.\n\n## TEST MATRIX\n| case | what runs | kind |\n|---|---|---|\n| start/finish | NET_LINE via dispatch → receipt has all five fields | live |\n| crash | LOCAL_EXEC with a 2 s timeout on sleep 30 → status=timeout, invocation present | fixture |\n| loud failure | simulate R2 write failure → 500 ledger_write_failed | fixture |\n| coverage gate | check-ledger-coverage.mjs → 0 | live |\n| immutability | LEDGER_EXEC UPDATE events → audit_immutable | fixture |\n| chain | audit head hash verifies over the last 100 rows | live |\n| receipt page | renders tree for a WT-0484 chain | live |\n| R2-only noise | a noise source writes no D1 row | fixture |\n\n## ACCEPTANCE TESTS (mechanical; the infrastructure runs them)\n- `{\"type\": \"evidence_present\", \"id\": \"commit\", \"field\": \"commit\"}`\n- `{\"type\": \"evidence_present\", \"id\": \"live\", \"field\": \"verification\"}`\n- `{\"type\": \"evidence_present\", \"id\": \"gate\", \"field\": \"gate_run\"}`\n- `{\"type\": \"http_ok\", \"id\": \"audit\", \"url\": \"https://ops.miscsubjects.com/api/work/audit\"}`\nEvidence fields the submitting agent supplies: commit, gate_run, verification. `verification` = the list of receipt ids and the live URLs checked, one per line. `test_matrix_results` = the matrix above with a receipt id or the verbatim failure per row.\n\n## KNOWN FAILURES / REGRESSION FIXTURES (preserve; never rewrite into success)\n- logEvent silent failure\n- audit log editable via LEDGER_EXEC\n- ~35 unlogged write routes\n- noise into D1 (LAW: R2 only)\n- transport 2xx ≠ success (status must come from the operation, not the HTTP code)\n\n## DEPENDENCIES\n- depends_on: WT-0470\n- OWNS (only this task rewrites): functions/_lib/event_log.js, wire_log.js, lean_receipt.js; functions/_lib/proof_runs.js (new); functions/receipt/*, functions/api/events/*; scripts/check-ledger-coverage.mjs (new); migrations/0488_proof_runs.sql (new, LEDGER)\n- SHARED (additive edits only): functions/_middleware.js (additive wrapper); functions/_lib/resolver.js (WT-0471 calls startRun/finishRun; coordinate the call sites)\n\n## HANDOFF FOR NEXT SESSION\n- Land the writer first with dispatch as the only caller, ship, then the middleware wrapper, then the gate (three ships across three turns at most; one ship per turn).\n- Do not COUNT/LIKE over events for the gate; the gate reads route files statically.\n- Evidence: gate output, crash receipt id, audit head hash.\n\n## DONE LAW (this task is done when every line is true and evidenced; not before)\n- Every executed capability has a proof_runs row with the five fields (gate = 0 gaps).\n- Crash/timeout leaves the invocation (fixture).\n- Ledger failures are loud; audit is immutable and hash-chained.\n- Receipt pages render the chain.\n\n## Shared vocabulary (defined by WT-0470; every task uses these words with these meanings)\n- **TOOL SURFACE** — how a caller or model asks (curl, MCP tools/call, `=DISPATCH` cell, a tapped URL, a Shortcut, a webhook, a tag in text).\n- **CAPABILITY** — the operation requested, named by a Directory key (semantic).\n- **OBJECT** — what it acts on (a chat, a file, a profile, a row, a page).\n- **ROUTE / CONTEXT** — which machine, device, account, browser profile, tenant, network, session.\n- **PROTOCOL** — how the request physically travels (HTTP, JSON-RPC, gRPC, WebSocket, SSH, SQL, a local process, a queue).\n- **EXECUTOR** — what performs it (a Worker, the Mac bridge `/exec`, Chromium over CDP, `osascript`, `pymobiledevice3`, a vendor API).\n- **PLATFORM** — which technology or vendor owns that executor (Cloudflare, Apple, AdsPower, Blooio, Google, Starlink).\n- **RAW CONTRACT** — the exact physical invocation sent, fully resolved.\n- **CONFIRMATION** — the executor's immediate acknowledgement (HTTP status + headers, spawn/exit metadata, JSON-RPC ack, WebSocket ack, queue receipt).\n- **RETURN** — the final result, verbatim, including failure payloads.\n- **RECEIPT** — evidence and provenance: receipt id/URL, trace id, execution id, timestamps, parent linkage.\n\n## Definition of proof (PROOF_LAW, governing invariant; read it: GET https://ops.miscsubjects.com/api/work → governing_invariants)\nA capability is proven only when the DECLARED invocation surface itself ran and the five fields exist for that run: CAUSE (why it ran: user request / task / parent invocation / event / webhook / model call), RAW INVOCATION (the fully resolved physical contract actually sent), RAW CONFIRMATION (the executor's acknowledgement), RAW RETURN (the actual output, failures verbatim), PROOF / RECEIPT (receipt id/URL, trace, execution id, timestamps, parent linkage sufficient to inspect or replay).\nAn HTTP 200 proves an HTTP 200. A webhook acknowledgement proves receipt. Hidden bash proves bash. A sibling implementation, documentation, a model doing it elsewhere, or the dispatcher working while the surface under test never ran prove nothing about that surface. A missing field is a missing implementation requirement, never something to invent. Existing failures stay as evidence and become regression fixtures; they are never rewritten into success.\n\n## Six-column projection (unchanged; not the source of truth)\n`TOOL NAME | ONTOLOGY | RAW INVOCATION | RAW CONFIRMATION | RAW RETURN | PROOF / RECEIPT` — the build workbook sheet `sh_nyab2cgy` (vault `CONTRACTS_WORKBOOK_SHEET`, 11,035 rows on 2026-09-23). No seventh column, no contract-type column, no prose in place of an executable invocation. Extra metadata belongs in the canonical capability / task / receipt objects.\n\n## Parallelism law\nOther tasks run at the same time in other sessions. Own only the files and objects named under OWNS; touch a shared file only as named under SHARED (additive, never a rewrite). If this task finds the global law insufficient, it files an amendment: `POST /api/work/task/<this id>/fail` naming failure_class `law_insufficient`, layer `WT-0470`, and the missing invariant — it never creates a second standard. Ask the build, never the owner: keys are in `~/.build-vault.env` (`grep '^NAME=' ~/.build-vault.env`; `CLOUDFLARE_API_TOKEN=$CF_API_TOKEN` is a shell reference, resolve `$NAME`). The owner key travels as header `x-terminal-key`. Deploy only with `node scripts/land.mjs \"<WT id> what changed\"` from `~/miscsubjects-pages`. Submit evidence: `POST https://ops.miscsubjects.com/api/work/task/<id>/submit {agent, evidence:{commit, verification, …}, changed:[…]}` with header `x-terminal-key`.\n\n## Shared canonical objects and files — never independently rewritten (additive edits only, named per task)\n- `functions/api/dispatch.js` (the one door; runHttp/runFn/tenant delegation) — owner WT-0471.\n- `functions/_lib/invocation_methods.js`, `functions/_lib/grammar.js`, `functions/_lib/projection_manifest.js`, `functions/api/tools/[[path]].js`, `functions/api/mcp.js`, `functions/_lib/mcp_inspect.js` — owner WT-0472 (spellings and model projections).\n- `functions/_lib/event_log.js`, `functions/_lib/wire_log.js`, `functions/_lib/lean_receipt.js`, `functions/_lib/invocation_record.js`, the `events` table and R2 `logs/` — owner WT-0485 (ledger).\n- `functions/_lib/admin_session.js` (tokens, capabilities, tenants), `functions/_lib/tenant_devices.js`, `functions/api/onboard/[[path]].js` — owner WT-0487 (authority) / WT-0488 (onboarding).\n- `functions/_lib/mac_bridge.js`, `bridge/server.js`, `bridge/device_auth.js`, `bridge/surface-run.py`, `bridge/surface-verbs.py`, `bridge/bridge-run.py`, `bridge/bridge-browser.mjs`, `public/device-kit/*` (synced copies) — owners WT-0474 / WT-0475 / WT-0478.\n- `functions/_lib/work_object.js`, `functions/api/work/[[path]].js`, the `laws` and `work_tasks` tables — owner WT-0470 (law rows) and the build (task engine).\n- `scripts/contracts-*.mjs`, `apps-script/Contracts.gs`, the `CONTRACTS_RUN` directory row, sheet `sh_nyab2cgy` — owner WT-0473.\n- `scripts/ship.mjs`, `scripts/land.mjs`, `scripts/write.mjs`, `scripts/check-*.mjs`, `.githooks/*` — governed; add a gate only by adding a new `scripts/check-<name>.mjs` and wiring it as the existing gates are wired.\n",
    "state": "open",
    "priority": 1,
    "revision": 1,
    "depends_on": [
      "WT-0470"
    ],
    "permitted_capabilities": [
      "dispatch",
      "d1",
      "bridge",
      "sheets",
      "receipts"
    ],
    "acceptance_tests": [
      {
        "type": "evidence_present",
        "id": "commit",
        "field": "commit"
      },
      {
        "type": "evidence_present",
        "id": "live",
        "field": "verification"
      },
      {
        "type": "evidence_present",
        "id": "gate",
        "field": "gate_run"
      },
      {
        "type": "http_ok",
        "id": "audit",
        "url": "https://ops.miscsubjects.com/api/work/audit"
      }
    ],
    "required_evidence": [
      "commit",
      "gate_run",
      "verification"
    ],
    "parent_task": null,
    "supersedes": null,
    "failure": null,
    "failure_count": 0,
    "last_result": null,
    "completed_at": null,
    "created_at": "2026-09-22T19:51:47-07:00",
    "updated_at": "2026-09-22T19:51:47-07:00",
    "audit": "/api/work/task/WT-0485/audit",
    "submit_to": "/api/work/task/WT-0485/submit"
  }
}