feat: persist per-question cost receipts to DynamoDB (#47)#65
Merged
Conversation
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
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.
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/gateway—Receipt+ an optionalReceiptStorecapability (same pattern as the existingenumeratorseam) implemented onDynamoStoreandMemStore. One row per approved rung, carrying the sameexpiresTTL as session rows so a question's receipts self-clean → $0.QuestionIDderives 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.SummarizeReceiptsfolds rows into $-so-far vs envelope.RecordReceipt/LoadReceiptsare best-effort seams — a lost receipt never fails a trace, and a store without the capability silently no-ops.internal/webapi—/api/approvepersists the receipt after each approved rung (the brain already booked the spend inApprove; this records the fact, never settles acceptance). NewGET /api/receipt?question=<text>|id=<id>returns the persisted $-so-far. The SPA seeds its cost meter from it on (re-)propose.Scope
MemStoreis unchanged) per the agreed scope; the durable path is the webapi/LambdaDynamoStore.dynamodb:Query(anticipated in step 9); no Terraform/IAM change beyond a comment + API-route doc.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 lintandmake testpassCHANGELOG.mdupdated under[Unreleased]Invariant check (see CLAUDE.md)
neuronuntouched