Story Index: ingestion (ING) — v0.0.0¶
Module: ingestion Total stories: 6 Decomposed from: docs/architecture/ingestion-arch.md (approved — ARCHITECT session 2026-05-21) PRD reference: docs/prd/ingestion-prd.md + docs/prd/prd-v0.md §4 + §8 Design reference: N/A — v0.0.0 has no human-facing UI (architecture §1.1). Design doc will be required from v0.0.3 (UI upload + atelier). Date: 21/05/2026
Dependency Graph¶
ING-001 (scaffold: TestContainers, fixtures, empty migrations)
│
├──> ING-002 (scheduler + MinIO client + tenant iteration + ING_UPLOAD_RECEIVED)
│ │
│ └──> ING-004 (commit TX + reject + dedup + ImportCommitted)
│
└──> ING-003 (parsers + validator + phone normalizer) ──> ING-004
│
├──> ING-005 (startup recovery + ARTCI prod gate)
│ │
│ └──> ING-006 (cross-cutting IT pack)
│
└──> ING-006
External prerequisites (must land before ING-002 starts dev):
- TENANT-001 (TenantConfigCache, default-agency provisioning, customer/contract base tables)
- AUDIT-001 (AuditEventBuilder, outbox, PiiHasher)
- AUDIT catalog extension OQ-X10 admitting ING_* event types
- OQ-X11 (TENANT provisions papillon-ingestion-inbox bucket + per-tenant credentials)
- OQ-X12 (TENANT confirms additive ALTER strategy on customer/contract)
- OQ-X13 (OP confirms ImportCommitted consumption — no-op listener acceptable)
Story List¶
| # | Story ID | Title | Side | Depends On | Complexity | Status |
|---|---|---|---|---|---|---|
| 1 | ING-001 | Dev environment + test infrastructure + empty migrations | BACKEND | TENANT-S0, AUDIT-S0 | S | Backlog |
| 2 | ING-002 | Scheduler + MinIO inbox client + tenant iteration skeleton | BACKEND | ING-001, TENANT-001, AUDIT-001, OQ-X10–11 | S | Backlog |
| 3 | ING-003 | CSV/XLSX parsing + row validation + phone normalization | BACKEND | ING-001 | M | Backlog |
| 4 | ING-004 | Commit transaction + reject path + SHA-256 dedup + ImportCommitted | BACKEND | ING-002, ING-003, TENANT-001, OQ-X10/12 | M | Backlog |
| 5 | ING-005 | Startup recovery + ARTCI feature-flag prod gate + structured logging | BACKEND | ING-004 | S | Backlog |
| 6 | ING-006 | Cross-cutting integration test pack (isolation, large file, magic byte, crash, flag) | BACKEND | ING-005 | M | Backlog |
Concurrency Tiers¶
| Tier | Stories | Can Start After |
|---|---|---|
| 0 | ING-001 | TENANT-S0 + AUDIT-S0 merged |
| 1 | ING-002, ING-003 | ING-001 merged + AUDIT catalog OQ-X10 + TENANT-001 (for 002) |
| 2 | ING-004 | ING-002 AND ING-003 merged |
| 3 | ING-005 | ING-004 merged |
| 4 | ING-006 | ING-005 merged (test scaffolding may start earlier) |
PRD Acceptance Criteria Coverage¶
Architecture §11 lists 5 binding ACs for ING-001 + 3 ACs for ING-S0 scaffolding. Each story below adds its own technical ACs as required by the 2–4-per-story rule.
| Architecture / PRD source | ACs | Assigned To |
|---|---|---|
| arch §11 ING-S0 scaffolding | smoke boot, migrations apply, fixtures parse | ING-001 (3 ACs) ✅ |
| arch §11 ING-001 AC-1 (happy-path commit) | clean 5-row CSV → COMMITTED + audit + processed move + event | ING-004 (AC-001) ✅ |
| arch §11 ING-001 AC-2 (all-or-nothing reject + sidecar) | tainted XLSX → PARSE_FAILED + sidecar + audit | ING-004 (AC-002) ✅ |
| arch §11 ING-001 AC-3 (SHA-256 duplicate path) | re-dropped file → DUPLICATE row + audit + move, no business write | ING-004 (AC-003) ✅ |
| arch §11 ING-001 AC-4 (prod feature-flag closed) | prod + flag=false → tick is no-op + WARN log | ING-005 (AC-001) ✅ |
| arch §11 ING-001 AC-5 (startup crash recovery) | PICKED_UP >5min → PARSE_FAILED + move + audit | ING-005 (AC-003) ✅ |
| arch §12 A4/A10 (cross-tenant SHA-256 isolation) | same hash, two tenants → both COMMIT, no cross-write | ING-006 (AC-001) ✅ |
| arch §8 perf budget + §12 A5 (heap) | 5 000 rows ≤10 s parse + ≤5 s commit, <256 MB heap | ING-006 (AC-002) ✅ |
| arch §5.1 crash matrix (stages 3, 5a, 5b) | three crash points → idempotent recovery by next tick | ING-006 (AC-003) ✅ |
| arch §7.6 A1 magic-byte spoof + AC-4 release gate combo | OOXML-bytes-with-.csv-ext → PARSE_FAILED; prod flag closed smoke | ING-006 (AC-004) ✅ |
| arch §11 ING-002 technical (scheduler+MinIO+upload audit) | cron iterates, ING_UPLOAD_RECEIVED, local tick endpoint, large-file reject | ING-002 (AC-001/2/3) ✅ |
| arch §11 ING-003 technical (parse + validate kernel) | clean parse, tainted anomalies, 5001-row cutoff, magic-byte mismatch | ING-003 (AC-001..004) ✅ |
| arch §11 ING-001 prerequisite (ING-S0) | 3 smoke ACs | ING-001 (AC-001..003) ✅ |
| TOTAL | 21 ACs | 21 assigned ✅ |
Coverage verified: every architecture-§11 binding AC is owned by exactly one story; every architecture-§12/§13 cross-cutting concern is covered by ING-006; ING-002 / ING-003 carry derivative technical ACs proportional to the code they introduce.
Implementation Order¶
- ING-001 — scaffolding unblocks every downstream story.
- ING-002 — wires the scheduler skeleton + MinIO client. Independent of ING-003 (parallel-developable in tier 1).
- ING-003 — pure parser/validator kernel. Independent of ING-002.
- ING-004 — closes the v0.0.0 happy path; merges ING-002 + ING-003 wiring into a transactional commit FSM with audit + ImportCommitted. Production-blocking until ING-005 lands the ARTCI flag.
- ING-005 — flips the v0.0.0 release switch (ARTCI prod gate) + adds crash-recovery resilience. After this story, v0.0.0 ING is functionally complete on dev/staging.
- ING-006 — release gate: cross-cutting integration test pack. v0.0.0 ING ships only when this is green.
Notes¶
- All six stories are backend-only —
Wireframe: N/A — backend onlyeverywhere. The Frontend / UI-States blocks in each story are intentionally marked N/A. - Compliance posture: ARTCI loi 2013-450 Art. 14 →
PRÉLIMINAIRE(see ING-005). CIMA Reg. 01-24 → not directly engaged in v0.0.0 (no payment / regulatory output from ING). - The
[LEGAL-REVIEW]startup WARN is acknowledged for v0.0.0 dev/staging (gate is open in non-prod by design). - Forward-compat checkpoints (not v0.0.0 work, restated for visibility): V0.0.2 ING-007 (DOB +
branchereintroduced +code_agenceresolution); V0.0.3 ING-008+ (UI upload, atelier, history). These will be decomposed in a future/decomposeinvocation, NOT here.