Epic 003 traceability
Contract
Traceability is bidirectional:
acceptance scenario → one or more executable/manual evidence references
test declaring scenario ID → one existing acceptance scenario
Every executable test that proves an Epic 003 scenario includes the exact stable scenario ID in its title or native metadata. Playwright follows the repository's title convention:
test("[MVP-KDS-002-S02] preparing order advances to ready", async ({ page }) => {});
Vitest/integration tests use the same searchable bracketed ID. One test may prove multiple scenarios
when the title or enclosing describe metadata keeps every ID machine-searchable. Incidental test
coverage is not claimed as acceptance evidence unless the ID is declared.
Evidence classes:
- E2E: externally observable workflow through real frontend/backend/PostgreSQL, with controlled external adapters.
- Integration: persistence, history, concurrency, calculations, and application/adapter boundaries that are awkward or misleading through a browser.
- Unit/component/API: focused rules, presentation, validation, and HTTP contracts.
- Architecture/static: dependency direction or boundary evidence.
- Discovery/manual: sourced external capability result or human operational review.
Not every scenario requires browser E2E. Headline journeys and acceptance scenarios may both use Playwright, but journey tests demonstrate a coherent long flow while scenario evidence proves the smaller contracts.
Status model
- Specified: behavioral contract and task mapping exist; required evidence is not implemented.
- Implemented: referenced evidence exists but has not satisfied all required validation.
- Verified: referenced evidence passes for the delivered revision and story/DoD conditions hold.
- Blocked: evidence cannot pass; record cause and next action.
Task status remains separately governed by the task index. This document starts at Specified because this change implements no application behavior or acceptance tests.
Headline journey mapping
| Journey ID | Evidence reference | Task | Status |
|---|---|---|---|
| MVP-JRN-001 | tests/e2e/journeys.spec.ts headline Playwright journey |
MVP-TASK-035 | Implemented |
| MVP-JRN-002 | tests/e2e/journeys.spec.ts commercial-loop Playwright journey plus order-history.integration.test.ts durable-history evidence |
MVP-TASK-036 | Implemented |
| MVP-JRN-003 | tests/e2e/journeys.spec.ts no-checkout Playwright journey plus postgres-order-repository.integration.test.ts classification/sales-exclusion evidence |
MVP-TASK-037 | Implemented |
| MVP-JRN-004 | tests/e2e/journeys.spec.ts checkout-failure-safety Playwright journeys plus postgres-order-repository.integration.test.ts outcome/concurrency evidence |
MVP-TASK-038 | Implemented |
Scenario-to-evidence index
Ranges below are only compact references to individually defined stable IDs; implementations must
name each exact scenario ID they prove. Replace Planned with concrete repository test path/title
references as tasks land.
| Scenario ID(s) | Expected evidence | Task(s) | Test/evidence reference | Status |
|---|---|---|---|---|
| MVP-PROD-001-S01 | E2E + API/component | 001 | products.spec.ts; auth-http.test.ts; postgres-product-repository.integration.test.ts; ProductsPage.test.tsx |
Implemented |
| MVP-PROD-001-S02 | Component/API integration | 001 | auth-http.test.ts; postgres-product-repository.integration.test.ts; ProductsPage.test.tsx |
Implemented |
| MVP-PROD-001-S03 | E2E + API | 001 | products.spec.ts; auth-http.test.ts |
Implemented |
| MVP-PROD-002-S01–S02 | E2E + API/database integration | 002 | products.spec.ts; auth-http.test.ts; product.integration.test.ts; postgres-product-repository.integration.test.ts; ProductsPage.test.tsx |
Implemented |
| MVP-PROD-002-S03 | Component/API + representative E2E | 003 | product-policy.test.ts; product-service.test.ts; auth-http.test.ts; product.integration.test.ts; ProductsPage.test.tsx; products.spec.ts |
Implemented |
| MVP-PROD-003-S01 | E2E + API/database integration | 004 | products.spec.ts; auth-http.test.ts; product.integration.test.ts; postgres-product-repository.integration.test.ts; product-update-api.test.ts; ProductsPageEdit.test.tsx; product-service.test.ts |
Implemented |
| MVP-PROD-003-S02 | Database integration | 023 | order-history.integration.test.ts "[MVP-PROD-003-S02] leaves a finalized snapshot untouched when the catalog is edited" proves a rename, relabel, reprice, and VAT reassignment leave the finalized item snapshot and finalization fact byte-identical while a later order uses the updated data |
Implemented |
| MVP-PROD-004-S01 | E2E + API/database integration | 005 | product-service.test.ts; postgres-product-repository.test.ts; postgres-product-repository.integration.test.ts; auth-http.test.ts; product.integration.test.ts; product-api.test.ts; ProductsPageDelete.test.tsx; products.spec.ts |
Implemented |
| MVP-PROD-004-S02 | Database integration | 023 | order-history.integration.test.ts "[MVP-PROD-004-S02] keeps a finalized snapshot and its history valid when the product is deleted" proves deleting the catalog row leaves the item snapshot, finalization fact, and fulfillment history readable and unchanged |
Implemented |
| MVP-ORD-001-S01 | Component + E2E | 006 | KdsPage.test.tsx; kds.spec.ts; accessibility.spec.ts |
Implemented |
| MVP-ORD-001-S02–S04 | Component + E2E | 007 | KdsPage.test.tsx; kds.spec.ts |
Implemented |
| MVP-ORD-001-S05–S06 | Unit/component + journey | 008 | KdsPage.test.tsx; kds.spec.ts |
Implemented |
| MVP-ORD-001-S07–S08 | Component/E2E + persistence absence | 009 | KdsPage.test.tsx; kds.spec.ts; architecture/static absence of an order persistence boundary |
Implemented |
| MVP-ORD-001-S09 | E2E | 012–013 | KdsPage.test.tsx; kds.spec.ts (non-commercial path; commercial continuation remains task 013) |
Implemented |
| MVP-ORD-002-S01–S06 | E2E + integration | 010 | KdsPage.test.tsx; kds.spec.ts (capture/review boundary; durable PostgreSQL finalization continues in task 012) |
Implemented |
| MVP-ORD-003-S01–S05 | E2E + application/database integration | 012, 027 | order-service.test.ts; postgres-order-repository.integration.test.ts; KdsPage.test.tsx; kds.spec.ts (finalization; later history/sales presentation remains task 027) |
Implemented |
| MVP-PAY-001-S01, S05–S06 | E2E + application/database integration | 013, 023 | order-service.test.ts; order-http.test.ts; auth-http.test.ts; postgres-order-repository.integration.test.ts; order-api.test.ts; KdsPage.test.tsx; kds.spec.ts (commercial finalization; later history remains task 023) |
Implemented |
| MVP-PAY-001-S02 | E2E + integration | 028 | tests/e2e/kds.spec.ts "[MVP-PAY-001-S02] [MVP-SALES-001-S07] a definitively declined payment is unpaid, unqueued, uncounted, and safely recoverable" drives both definitive classes through the real stack; postgres-order-repository.integration.test.ts "[MVP-PAY-001-S02] [MVP-SALES-001-S07] persists nothing for a definitive refusal and leaves the day's sales untouched"; order-service.test.ts, order-http.test.ts, fake-checkout-gateway.test.ts, checkout-composition.test.ts, config.test.ts, KdsPage.test.tsx cover the application, API, adapter, configuration, and UI layers |
Implemented |
| MVP-PAY-001-S03 | E2E + durable-outcome integration | 029 | postgres-order-repository.integration.test.ts "[MVP-PAY-001-S03] [MVP-REL-001-S02] [MVP-SALES-001-S07] durably records an unconfirmed attempt without an order, a queue entry, or a sale" runs the real checkout boundary, service, and PostgreSQL write, then reconstructs the identical attempt through a fresh repository/pool, proves the row is append-only, unresolvable, and unique, and that a later confirmed sale neither resolves nor counts it; kds.spec.ts "[MVP-PAY-001-S03] [MVP-REL-001-S02] [MVP-SALES-001-S07] an unconfirmed payment stays visible across a reload, blocks a blind retry, and is never counted as a sale" drives the disabled retry control, the durable banner after page.reload(), and the unchanged ticker in desktop/mobile; order-service.test.ts, order-http.test.ts, checkout-composition.test.ts, fake-checkout-gateway.test.ts, and config.test.ts prove the distinct error, the CHECKOUT_UNRESOLVED contract, and the amount-keyed unconfirmed outcome; order-api.test.ts and KdsPage.test.tsx prove the read boundary and that the copy claims neither outcome |
Implemented |
| MVP-PAY-001-S04 | Concurrency integration + E2E | 030 | postgres-order-repository.integration.test.ts "[MVP-PAY-001-S04] admits one provider operation under concurrent and repeated checkout initiation" proves one fake-provider call across two repositories and safe replay; KdsPage.test.tsx and kds.spec.ts carry the scenario ID for disabled/progress behavior and one browser request; checkout-operation-id.test.ts proves the client identity works without the secure-context-only randomUUID |
Implemented |
| MVP-KDS-001-S01, S04 | Integration + E2E/component | 014, 015 | postgres-order-repository.integration.test.ts; order-service.test.ts; order-http.test.ts; auth-http.test.ts; order-api.test.ts; KdsPage.test.tsx; kds.spec.ts (tablet case now also asserts S02/S03) |
Implemented |
| MVP-KDS-001-S02–S03 | Component + tablet E2E | 015 | KdsPage.test.tsx; kds.spec.ts (768×1024 tablet queue; next-three window, kitchen-label snapshots, explicit quantities, no drill-down) |
Implemented |
| MVP-KDS-001-S05–S06 | Unit/component + tablet E2E | 016 | order-fingerprint.test.ts; KdsPage.test.tsx; kds.spec.ts (768×1024 tablet; per-order compact overflow, queue number preferred, item fingerprint with safe truncation, no internal IDs) |
Implemented |
| MVP-KDS-001-S07–S08 | Component + tablet E2E | 017 | KdsPage.test.tsx; kds.spec.ts (768×1024 tablet; 15 active fixtures plus a polled 16th order, one representation per active order, visible accurate count, last entry scrolled into view) |
Implemented |
| MVP-KDS-002-S01 | E2E + integration | 018 | kds.spec.ts "[MVP-KDS-002-S01] … advances a queued order to preparing"; postgres-order-repository.integration.test.ts "Queued to Preparing transition in PostgreSQL"; KdsPage.test.tsx; order-service.test.ts; order-http.test.ts |
Implemented |
| MVP-KDS-002-S02 | E2E + integration | 019 | kds.spec.ts "[MVP-KDS-002-S02] … queued through preparing to ready"; postgres-order-repository.integration.test.ts "[MVP-KDS-002-S02] [MVP-HIST-001-S02] … Preparing to Ready"; KdsPage.test.tsx; order-service.test.ts; order-http.test.ts |
Implemented |
| MVP-KDS-002-S03 | E2E + integration | 020 | kds.spec.ts "[MVP-KDS-002-S03] … delivers the order out of the queue"; postgres-order-repository.integration.test.ts "[MVP-KDS-002-S03] [MVP-HIST-001-S07] … while its record and history stay durable"; KdsPage.test.tsx; kitchen-queue-card.test.tsx; order-service.test.ts; order-http.test.ts |
Implemented |
| MVP-KDS-002-S04–S05 | E2E + transition-history integration | 021 | kds.spec.ts tagged transition sequence; postgres-order-repository.integration.test.ts tagged full chronology; KdsPage.test.tsx; order-service.test.ts; order-http.test.ts; order-api.test.ts |
Implemented |
| MVP-KDS-002-S06 | Component + E2E | 022 | kitchen-state-selector.spec.ts "[MVP-KDS-002-S06] … offers an explicit safely constrained state selector"; kitchen-queue-card.test.tsx explicit selector cases; KdsPage.test.tsx "[MVP-KDS-002-S06] … rejects unavailable states" |
Specified |
| MVP-KDS-003-S01–S04 | E2E + database integration | 024 | kds.spec.ts tagged edit/clear display case; order-history.integration.test.ts tagged immutable commercial-facts case |
Implemented |
| MVP-HIST-001-S02 | Database integration | 018–020, 023 | postgres-order-repository.integration.test.ts tagged cases prove exactly-once timestamped Queued→Preparing, Preparing→Ready, and Ready→Delivered history; order-history.integration.test.ts "[MVP-HIST-001-S02] [MVP-HIST-001-S03] appends every forward and backward transition in trustworthy order" adds the mixed forward/backward sequence with bracketed, non-decreasing database timestamps and a fresh-connection readback |
Implemented |
| MVP-HIST-001-S03 | Database integration | 021, 023 | postgres-order-repository.integration.test.ts tagged full backward-correction chronology; kds.spec.ts proves the same sequence through the browser; order-history.integration.test.ts re-proves the four-fact chronology with database-time ordering through a new connection |
Implemented |
| MVP-HIST-001-S07 | Database integration | 020, 023 | postgres-order-repository.integration.test.ts "[MVP-KDS-002-S03] [MVP-HIST-001-S07] … while its record and history stay durable" proves the delivered order row, item snapshots, and full history survive leaving the active queue; kds.spec.ts proves the same through the browser; order-history.integration.test.ts "[MVP-HIST-001-S07] keeps every fact durable when an order leaves the active queue" adds the finalization fact, checkout facts, and complete history after delivery |
Implemented |
| MVP-HIST-001-S01, S06 | Database integration | 023, 032 | order-history.integration.test.ts "[MVP-HIST-001-S01] keeps the finalized snapshot durable across a catalog change and a fresh connection" proves the internal ID, classification, fulfillment metadata, quantities, and product snapshots read back unchanged through a new repository and pool after the catalog was edited; "[MVP-HIST-001-S06] keeps the checkout attempt, outcome, timestamps, and reference associated with the order" proves attempt, outcome, charged gross, provider reference, and the bracketed database recorded time stay associated with the internal order identity and survive every transition and delivery; MVP-TASK-032 adds the whole-stack process restart: tests/docker/backend-lifecycle.mjs reads the same snapshot back through the packaged gateway after really replacing the backend process, and order-restart-durability.integration.test.ts rebuilds it through a fresh repository, pool, and service graph with every fact-table row count unchanged |
Implemented |
| MVP-HIST-001-S04–S05 | Database integration | 024 | order-history.integration.test.ts tagged metadata edit/clear case proves formatted old/new facts and database timestamps |
Implemented |
| MVP-SALES-001-S01 | Integration + tablet E2E | 025 | backend/src/repository/postgres-order-repository.integration.test.ts; tests/e2e/kds.spec.ts; supporting API/component evidence in backend/src/order-http.test.ts, frontend/src/app/api/order-api.test.ts, and frontend/src/pages/KdsPage.test.tsx |
Implemented |
| MVP-SALES-001-S02–S04, S08 | Integration/component/tablet E2E | 026 | backend/src/repository/postgres-order-repository.integration.test.ts "[MVP-SALES-001-S02] [MVP-SALES-001-S04] [MVP-SALES-001-S08] groups today's quantities by stable product identity and keeps one counter through a rename" proves the real application path aggregating by product identity through a rename; tests/e2e/kds.spec.ts "[MVP-SALES-001-S02] [MVP-SALES-001-S03] [MVP-SALES-001-S04] shows today's per-product counts beside the gross in the tablet's persistent top region"; frontend/src/pages/KdsPage.test.tsx mixed-label breakdown and empty-day cases; backend/src/order-http.test.ts and frontend/src/app/api/order-api.test.ts pass-through evidence |
Implemented |
| MVP-SALES-001-S05–S07, S09, S10 | Integration + E2E/restart evidence | 027–029, 032 | postgres-order-repository.integration.test.ts "[MVP-SALES-001-S05] [MVP-SALES-001-S06] [MVP-SALES-001-S07] [MVP-SALES-001-S09] excludes non-sales, ignores fulfillment, and reconstructs the same figures through a fresh repository" proves non-commercial plus rejected/unknown checkout exclusion, every forward/backward fulfillment change through delivery, repeated reads, and reconstruction through a new repository/pool; kds.spec.ts tags the non-commercial unchanged-sales journey, the confirmed-sale browser reload, the "[MVP-PAY-001-S02] [MVP-SALES-001-S07]" definitive-decline journey, and the "[MVP-PAY-001-S03] [MVP-REL-001-S02] [MVP-SALES-001-S07]" unconfirmed-attempt journey (unchanged ticker beside a real sale) in desktop/mobile; MVP-TASK-029 additionally proves the exclusion is by construction — an unresolved attempt has no order, item snapshot, or finalization fact to join — through a fresh repository/pool; MVP-TASK-032 adds the data-bearing whole-process restart: tests/docker/backend-lifecycle.mjs replaces the packaged backend process and re-reads an identical active queue, identical today's figures, and identical durable fact rows with unchanged row counts, while order-restart-durability.integration.test.ts reproduces the same claim at the storage boundary |
Implemented |
| MVP-REL-001-S02 | Integration + E2E using the deterministic fake | 029 | postgres-order-repository.integration.test.ts and kds.spec.ts "[MVP-PAY-001-S03] [MVP-REL-001-S02] [MVP-SALES-001-S07] durably records an unconfirmed attempt without an order, a queue entry, or a sale" / "an unconfirmed payment stays visible across a reload, blocks a blind retry, and is never counted as a sale" prove that a checkout which cannot obtain a definitive provider result produces explicit unknown semantics, a durable visible record, and no paid or sales state whatsoever |
Implemented |
| MVP-REL-001-S01, S03, S05 | Frontend/application integration + E2E | 031 | KdsPage.test.tsx "[MVP-REL-001-S01] [MVP-REL-001-S03] [MVP-REL-001-S05] keeps loaded operational data visible and marks it stale during a backend outage" forces queue, sales, and unresolved-payment refreshes offline after frontend state has loaded; postgres-order-repository.integration.test.ts "[MVP-REL-001-S02] [MVP-REL-001-S03] isolates a provider failure from unrelated active orders and sales" proves a throwing checkout boundary creates no paid/order facts and leaves the real PostgreSQL queue and sales byte-identical; kds.spec.ts "[MVP-REL-001-S01] [MVP-REL-001-S03] [MVP-REL-001-S05] a lost backend request is visible, keeps the draft, and leaves the existing queue intact" aborts the browser finalization request and proves visible failure, no offline/sync/success claim, retained draft, and intact durable queue |
Implemented |
| MVP-REL-001-S04 | Docker/process restart + integration | 032 | tests/docker/backend-lifecycle.mjs creates two real finalized orders through the packaged gateway, drives one of them out of the active queue while leaving the other mid-fulfillment, then really replaces the backend process (Compose stop, clean SIGTERM exit 0, start again, and a different container PID and start time) and re-reads through the same gateway. The active queue, today's commercial figures, and every durable order, item, finalization, transition, checkout, and metadata row come back identical, and the row counts of all six fact tables are unchanged, so nothing was reset and nothing was duplicated. Compose generates the local signing secret per start, so the run signs in again after the restart and no surviving record can come from process memory. order-restart-durability.integration.test.ts carries the same claim at the storage boundary: after every connection is closed, a fresh repository, pool, and service graph rebuilds all four facets and appends nothing. No host crash, high availability, or backup/restore is simulated |
Implemented |
| MVP-TECH-001-S01 | Unit/integration | 011 | checkout-service.test.ts; fake-checkout-gateway.test.ts; checkout-composition.test.ts; config.test.ts |
Implemented |
| MVP-TECH-001-S02–S03 | Integration + architecture/static review | 011, 034 | checkout-service.test.ts; fake-checkout-gateway.test.ts; checkout-composition.test.ts; checkout-boundary.test.mjs; continued by MVP-TASK-034 |
Implemented |
| MVP-TECH-002-S01 | Discovery/manual | 033 | docs/decisions/004-surfboard-integration-discovery.md "Answers to the discovery questions (MVP-TECH-002-S01)" — twenty sourced Yes/No/Unknown answers dated 2026-10-09, explicit unknowns register, Demo and human-owned account/ECR/Skatteverket prerequisites |
Implemented |
| MVP-TECH-002-S02 | Architecture/static review + discovery/manual | 033 | docs/decisions/004-surfboard-integration-discovery.md environment-topology section keeps local/test on the deterministic fake independently of Surfboard; existing checkout-boundary.test.mjs MVP-TECH-001 executable evidence is unchanged by this documentation-only task |
Implemented |
| MVP-TECH-002-S03 | Review of discovery record and adapter task | 033, 034 | ADR 004 defines the boundary; surfboard-checkout-gateway.test.ts, surfboard-webhook.test.ts, checkout-composition.test.ts, and config.test.ts cover the implemented payment/configuration/webhook portion. Fiscal and real Demo evidence remain explicitly blocked in MVP-TASK-034. |
Blocked |
Maintenance and automation
When a task lands:
- Keep the scenario ID and story text stable; clarify without renumbering.
- Add exact test file and title (or decision/manual record) to this index.
- Change only scenarios whose required evidence actually exists and passed.
- Update affected journey references after the long-flow evidence exists.
- Record gaps honestly in current state/handover.
MVP-TASK-039 implements pnpm traceability:check
(scripts/traceability-check.mjs), the mechanical half of this contract, inside pnpm quality. It
rejects duplicate scenario/journey IDs, tests declaring nonexistent IDs, scenarios without a
required **Evidence:** declaration, scenarios missing from this index, and index rows that are
duplicated, reference undefined scenarios, or carry empty/Planned references;
node scripts/traceability-check.mjs --report prints a generated per-scenario index. Fixtures in
tests/ci/traceability.test.mjs cover the valid and failure cases; that fixture file is the one
documented exclusion from the tracked test-file scan, because it intentionally declares synthetic
IDs no story defines. Semantic adequacy of the evidence and the choice of evidence level remain
review responsibilities; the checker requires nothing beyond a declared ID and an index row.