Verification: Kanban Cards with Deleted Items — Phase 1 (backend)
Acceptance criteria and test plan for Phase 1. Implementation detail is in specification.md. Each acceptance scenario (AC) maps to a goal success criterion (SC).
Acceptance scenarios
Section titled “Acceptance scenarios”AC-1 — Scan/open a deleted-item card resolves (SC-1)
Section titled “AC-1 — Scan/open a deleted-item card resolves (SC-1)”Given an item with a card, when the item is deleted and the card is then read (GET /v1/kanban-card/details?cardEId=…, by-record, and the deprecated …/{cardEId}/details), then the response is 200 with the item’s last-known name, item.retired = true, and item.provenance set — no 500. Covers a card retired via this build’s observer and a card whose item was deleted before this build shipped (pre-existing backlog).
AC-2 — List/by-status/by-page tolerates deleted-item cards (SC-2)
Section titled “AC-2 — List/by-status/by-page tolerates deleted-item cards (SC-2)”Given a page that includes one or more deleted-item cards, when listWithDetails (generate / by-status / by-page) runs, then the full page returns 200 with each deleted-item card marked; a single deleted-item card never fails the page, and a genuinely-absent (corrupt) item is skipped, not page-failing.
AC-3 — Reference reports retired + provenance, correct even when cache is stale (SC-3)
Section titled “AC-3 — Reference reports retired + provenance, correct even when cache is stale (SC-3)”Given a deleted-item card whose denormalized cache is stale or empty (e.g. legacy retired = false), when it is read, then the response’s item.retired and item.provenance are correct because they are coalesced from the resolved item record (the tombstone carries the deletion author/asOf). The cache is a hint, not the source of truth.
AC-4 — Cache self-heal on single-card read (SC-3)
Section titled “AC-4 — Cache self-heal on single-card read (SC-3)”Given a stale-cache deleted-item card, when it is read via the single-card detail path, then the reference is persisted with the corrected retired/provenance/rId; a concurrent-version conflict is swallowed (the read still returns 200). List reads do not trigger inline heal writes.
AC-5 — Print reflects deletion on both surfaces (SC-4)
Section titled “AC-5 — Print reflects deletion on both surfaces (SC-4)”Given a deleted item, when its kanban card is printed, then the card print succeeds and carries item_retired = true + last-update provenance. And when the item’s Label / Breadcrumb is printed, then it succeeds (no “Some items not found for printing”) and carries is_retired = true + provenance.
AC-6 — No regression for live items (SC-5)
Section titled “AC-6 — No regression for live items (SC-5)”Given items that are not deleted, when cards are read/listed/printed, then behavior and contract are unchanged; new reference fields default to not-deleted (retired = false, provenance populated by the observer on the next update or null).
Test plan
Section titled “Test plan”Unit / integration (operations + common-module, Kotest + ContainerizedPostgres)
Section titled “Unit / integration (operations + common-module, Kotest + ContainerizedPostgres)”| Test | Target | Validates | AC |
|---|---|---|---|
| Provenance component round-trip | provenanceComponent (common-module) | persists/reads updatedBy/updatedAt; requiring(...) → null when both columns null, error when mixed | — |
| Reference round-trips deletion fields | ItemReferenceComponent | retired/provenance persist & read back on kanban_card; default retired = false | AC-1/6 |
order_line migration safety | order_line | columns added (default/null); order read/write unaffected | AC-6 |
| Propagation on delete | ItemListener.deletedItem | every cardsForItem card gets retired = true, provenance from the delete record, refreshed rId | AC-1/3 |
| Propagation on update | ItemListener.updatedItem | non-delete update sets provenance (and retired = false) uniformly | AC-3/6 |
| Single-card resolve — believed-live retired | ServiceImpl.detailsFor | legacy retired = false + deleted item → includeRetired = true resolves tombstone → marked correctly, no 500 | AC-1/3 |
| Single-card resolve — known retired | ServiceImpl.detailsFor | retired = true → getRecord(rId) tombstone path → marked correctly | AC-1 |
| Coalesce-from-record | composeDetails | response retired/provenance come from the resolved record, overriding a stale cache | AC-3 |
| Single-card heal | ServiceImpl.detailsFor | stale single-card read persists corrected reference; injected ConflictingState is swallowed, read still 200 | AC-4 |
| List tolerates deleted + missing | ServiceImpl.listWithDetails | page with retired-item cards resolves fully (partitioned); no inline heal writes; corrupt item skipped | AC-2/4 |
| Card print carries deletion | KanbanCardPrinter.render | item_retired/item_last_updated_by/item_last_updated_at from coalesced item | AC-5 |
| Item print carries deletion | ItemPrinter.render + ItemPrintingService | retired item resolves (not “not found”); is_retired/provenance populated | AC-5 |
API tests (Bruno, api-test)
Section titled “API tests (Bruno, api-test)”| Test | Method | Path | Expected | AC |
|---|---|---|---|---|
| Scan deleted-item card | GET | /v1/kanban-card/details?cardEId=… | 200, item.retired = true, name present | AC-1 |
| List with a deleted-item card | POST | /v1/kanban-card/details/generate | 200, page includes the marked card | AC-2 |
End-to-end (ContainerizedPostgres)
Section titled “End-to-end (ContainerizedPostgres)”| Scenario | Setup | Validates | AC |
|---|---|---|---|
| Delete → scan | create item+card, delete item, detailsFor | no 500; marked deleted; name preserved; provenance = delete author/time | AC-1/3 |
| Pre-existing deletion | seed a card with retired = false whose item is already retired (no fresh event) | read still marks it correctly (coalesce); single read heals the cache | AC-3/4 |
Manual / soak checks
Section titled “Manual / soak checks”- After release, optionally audit
kanban_cardforitem_reference_retired = truevs cards still showing stalefalseto gauge heal coverage (expected to trend to zero as cards are read). A per-tenant API “refresh” (re-save cards) can warm the cache if ever needed — not required for correctness.
Out of scope (not verified here)
Section titled “Out of scope (not verified here)”Front-end rendering of the marker; procurement/order-read behavior; new-order prevention for deleted items; N1/N3/N4 nice-to-haves.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved