WI-014 · Platform & Contracts · ingestion-contract

Source ingestion + promotion CORE contract (one-door flow, promoteInventoryCandidate, idempotency, layered dedup, provenance, plugin-event audit, permission matrix)

done P0 high risk   Owner: Paul   100% · 5/5 tasks complete

Gates: P5 Requirements: REQ-PLAT-001 REQ-SEC-001 REQ-SEC-002 Updated: 2026-06-22
Evidence
packages/ingestion-contract (P5 core; imports the P9 floor) — keyless suite + ci:floor green; discovered via trovesnap.keylessCi auto-discovery

Goals

Enforce the load-bearing rule: promoteInventoryCandidate() is the ONLY path into canonical inventory; everything else writes candidates + events (ARCHITECTURE.md).

Implementation — what to build

Spec (docs/specs/P5)

  • One-door rule: every external source enters as candidates+events only; reviewed promotion is the sole canonical writer; service-role key is ability, not permission.

Build (core)

  • Source connection/run/record (raw+normalized), inventory_candidate + lifecycle, candidate_review, promoteInventoryCandidate() (atomic 26-step), promotion idempotency, layered dedup, item/field provenance, conflict + source-derived update, append-only plugin_events, connector permission matrix.

Test (keyless)

  • One-door enforcement (every non-promotion role rejected from canonical writes); full source flow traceability; raw/normalized separation; promotion variants + idempotency; source idempotency; duplicate suggestions (no auto-merge); credential redaction; event chain.

Freeze (§37)

  • P6 enforces (dependency flip: P6 depends on P5); P3/P4 vision candidates enter as source_type vision_scan; P7 MCP routes through the boundary.

Gap-resolution expansion (D26, 2026-06-20)

  • Unified review_item model spanning import/capture/vision/status candidates + one-door acceptance guards: no path creates canonical inventory except reviewed promotion (closes G3; guards G9).

Checklist

  • Audit every writer; ensure only promoteInventoryCandidate() writes estate_sales / estate_sale_items / photos.
  • Connectors / MCP / harness / buyer submissions write source_import_records, inventory_candidates, plugin_events only.
  • Preserve traceability: source_import_records.promoted_record_id links candidate → canonical row.
  • Add a guard/test that fails if any other code path writes canonical tables.
  • Log plugin_events on every ingestion action.

Gates & testing

  • P5 Ingestion + promotion contract is enforced: connectors/MCP/harness/buyer write candidates and events only; promoteInventoryCandidate is the single canonical writer with traceability.
    CODE TEST HITL QA

Requirements

  • REQ-PLAT-001 accepted The ingestion + promotion contract must be the only path into canonical inventory.
    Done when: Connectors/MCP/harness/buyer write candidates and events only; promoteInventoryCandidate() is the single canonical writer with traceability (source_import_records.promoted_record_id).
  • REQ-SEC-001 accepted Third-party credentials must never be stored in app tables; credential_refs holds pointers only, with three documented levels (OAuth server-side, local harness, manual fallback).
    Done when: credential_refs.storage_location is oauth_server | local_harness | vault; no raw secrets in any app table; no server-side scraping with stored passwords.
  • REQ-SEC-002 accepted Supabase key boundaries must be enforced: browser/mobile use anon key + user JWT + RLS; server/edge use the service role; the harness uses a scoped device token, never the global service role.
    Done when: The service role never ships to the browser; OAuth tokens (oauth_tokens) are RLS service-role-only; the harness validates a scoped token.

Verify (done when)

The one-door flow (connection→run→record→candidate→review→promotion) is contracted; promoteInventoryCandidate() is the only external→canonical writer, atomic + idempotent, version-checked, seller-review-gated; raw payloads immutable + normalization versioned; layered dedup (source-native idempotency / fingerprint / similarity suggestions, no silent merge); item+field provenance survives later edits; append-only plugin_events; connector/MCP/harness/buyer permission matrix (candidate+event writes only); deterministic contract + permission-boundary tests. Per spec §34/§35 (core rows). Unblocks P6 + Spine. §37 reconciliations before freeze.

Cost triggers

No paid API / infra spend triggered by this item.

Status & editing

done   Sprint: P&C Wave 1: Foundations

Edit status / sprint on the ★ Live Board → — changes are logged live with who / when / why.

Human-in-the-loop

No human tasks linked.

Findings

  • P5 is frozen as a logical contract/reference implementation; physical table/RLS/grant enforcement remains correctly assigned to WI-015/P6.
  • Source-native IDs can contain characters disallowed by the P9 scope format, so P5 normalizes unsafe native keys before building the scoped idempotency hash.
  • Legacy-quarantine paths remain open in the P9 report by design; WI-014 adds the P5 suite to the floor but does not close D32 bypasses.

Concerns

None recorded yet.

Risks

None recorded yet.

Tech debt

None recorded yet.

Tools used

  • Get-Content — read AGENTS.md, llminterchange.md, session handoff, WI-014 handoff, status.json, decision log, P5 spec, and P9 convention
  • git worktree — created isolated D37 worktree branch wi-014-p5-core
  • apply_patch — added P5 package, tests, CI-floor wiring, and WI-014 D28 evidence
  • node --test — ran packages/ingestion-contract keyless contract suite
  • node scripts/ci-floor.mjs — ran P9 keyless CI floor with P1/P9/P5 suites and quarantine report
  • node scripts/tracker-promote.mjs — promoted WI-014 queued to done on the Live Board with audit actor Codex

Files & artifacts

No files / artifacts recorded.

Update log

Update · 2026-06-20 · Built P5 core ingestion/promotion contract

Added the keyless @trovesnap/ingestion-contract package with the P5 logical one-door flow: source connection → import run → immutable source record + normalized version → inventory candidate → seller review → promoteInventoryCandidate(). The reference promotion service is seller-review-gated, candidate-version-checked, idempotent via the P9 platform convention, duplicate-aware without silent merge, and records item/field provenance plus append-only plugin events. P6 still owns the physical PostgreSQL schema/RLS/grants/RPC enforcement.

CheckResultEvidence
P5 package testsPASS7/7 node:test cases: traceability, direct-write rejection, promotion idempotency, source idempotency, duplicate no-auto-merge, credential redaction, stale-review atomicity
Keyless CI floorPASSscan-contracts + platform + ingestion-contract + quarantine report, no secrets used
Platform idempotency conventionPASSP5 uses `idempotencyScope`/`idempotencyHash`; unsafe source-native IDs are normalized to colon-free scoped keys
Live Board promotionPASSWI-014 promoted queued → done with `tracker-promote` audit actor Codex
  • P5 is frozen as a logical contract/reference implementation; physical table/RLS/grant enforcement remains correctly assigned to WI-015/P6.
  • Source-native IDs can contain characters disallowed by the P9 scope format, so P5 normalizes unsafe native keys before building the scoped idempotency hash.
  • Legacy-quarantine paths remain open in the P9 report by design; WI-014 adds the P5 suite to the floor but does not close D32 bypasses.