Skip to content

feat: persist per-question cost receipts to DynamoDB (#47)#65

Merged
scttfrdmn merged 1 commit into
mainfrom
feat/cost-receipt-persistence
Jun 28, 2026
Merged

feat: persist per-question cost receipts to DynamoDB (#47)#65
scttfrdmn merged 1 commit into
mainfrom
feat/cost-receipt-persistence

Conversation

@scttfrdmn

Copy link
Copy Markdown
Owner

What & why

Persist the per-question cost receipt — rungs run and $ spent vs the envelope — so the page can show an authoritative $-so-far across reloads, not just the live in-memory figure that the stateless web contract throws away between calls. Uses the table layout the schema already reserved in step 9 (pk=QUESTION#<id>, sk=RECEIPT#<rung>), so no new table.

Closes #47

How

  • internal/gatewayReceipt + an optional ReceiptStore capability (same pattern as the existing enumerator seam) implemented on DynamoStore and MemStore. One row per approved rung, carrying the same expires TTL as session rows so a question's receipts self-clean → $0. QuestionID derives a stable, whitespace/case-normalized partition id from the question text, so the stateless web client (which carries the question text, not an id) and the server land on the same partition. SummarizeReceipts folds rows into $-so-far vs envelope. RecordReceipt/LoadReceipts are best-effort seams — a lost receipt never fails a trace, and a store without the capability silently no-ops.
  • internal/webapi/api/approve persists the receipt after each approved rung (the brain already booked the spend in Approve; this records the fact, never settles acceptance). New GET /api/receipt?question=<text>|id=<id> returns the persisted $-so-far. The SPA seeds its cost meter from it on (re-)propose.

Scope

  • CLI stays ephemeral (its in-process MemStore is unchanged) per the agreed scope; the durable path is the webapi/Lambda DynamoStore.
  • The webapi IAM role already had dynamodb:Query (anticipated in step 9); no Terraform/IAM change beyond a comment + API-route doc.
  • Offline/fake-green is the gate: make demo-fake, no AWS calls. Real DynamoDB round-trip is exercised by table-driven tests over a fake client (no AWS), consistent with prior steps.

Checklist

  • make lint and make test pass
  • CHANGELOG.md updated under [Unreleased]
  • New files carry the Apache 2.0 header
  • Tests added/updated

Invariant check (see CLAUDE.md)

  • Ephemeral by default — receipt rows carry the same TTL and self-clean; no warm state
  • Control plane rests at ~$0 — per-write on the on-demand table; one Query on read; no Scan, no always-on
  • Cost is per-session; budgets/ceilings respected — receipt records (never enforces) spend; the envelope/ceiling gates are unchanged
  • No automatic egress — receipts carry cost refs only, never tensors (same boundary as TraceResult)
  • Single-tenant — no sandbox, no shared tier
  • Device-agnostic — neuron untouched
  • The question stays the load-bearing invariant; brain never auto-accepts — the receipt is a recorded fact written after the human's Go, not an acceptance node

Persist the per-question cost receipt (rungs run, $ spent vs envelope) so the
page shows an authoritative $-so-far across reloads, using the table layout the
schema already reserved (pk=QUESTION#<id>, sk=RECEIPT#<rung>).

- internal/gateway: Receipt + optional ReceiptStore capability (like
  enumerator) on DynamoStore and MemStore; one row per approved rung, same
  expires TTL so it self-cleans to $0. QuestionID derives a stable,
  whitespace/case-normalized partition id from the question text so the
  stateless web client and server agree without carrying an id.
  SummarizeReceipts folds rows into $-so-far vs envelope. RecordReceipt/
  LoadReceipts are best-effort seams (a lost receipt never fails a trace; a
  store without the capability no-ops).
- internal/webapi: /api/approve persists the receipt after each approved rung;
  new GET /api/receipt?question=<text>|id=<id> returns the persisted $-so-far.
  The SPA seeds its cost meter from it on (re-)propose.

CLI keeps its in-process MemStore (receipts ephemeral there, unchanged); the
durable path is the webapi/Lambda DynamoStore. webapi IAM role already had
dynamodb:Query. Offline/fake-green is the gate (make demo-fake, no AWS).

Closes #47
@scttfrdmn
scttfrdmn merged commit 11cd197 into main Jun 28, 2026
7 checks passed
@scttfrdmn
scttfrdmn deleted the feat/cost-receipt-persistence branch June 28, 2026 07:23
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.

ops: cost receipt persisted per question (DynamoDB)

1 participant