WI-012 · Platform & Contracts · validation-pipeline

Output validation core — trust pipeline (stages 0–9, issue/receipt model, rule registry, deterministic repair, branded TrustedScanResult)

done P0 medium risk   Owner: Paul   100% · 8/8 tasks complete

Gates: P3 Requirements: REQ-CONTRACT-003 Updated: 2026-06-22
Evidence
packages/scan-validation: pipeline+taxonomy+trusted+receipt, shape-normalization (real nested P1 ScanResult/Request -> internal view), full structural schema validation (dep-free Draft-2020-12 subset) vs the @trovesnap/scan-contracts P1 bundle by scan-type, 6 core validators + per-type invariants (all 7 types) aligned to real P1 field names + §11 repair registry (idempotent) + coordinate-property + mutation + nested-shape tests. 47 keyless tests; ci:floor 5 suites green. (§29 freeze handshakes with P8/P6/P2 resolve at the wave gate.)

Goals

Treat raw model output as untrusted: a validation pipeline catches malformed output and retries/falls back before anything downstream uses it (§19).

Implementation — what to build

Spec (docs/specs/P3)

  • Consumes P2 ProviderAttempt (no provider-native types); structurally-valid != trusted; four orthogonal axes (severity/retryability/salvageability/trust-effect).

Build (validation)

  • Orchestrator + stages 0–9 (admissibility, contract identity/version, schema, domain/enum, reference integrity, coordinate/geometry, evidence/claim, scan-specific invariants, boundary safety, completeness); rule registry resolved by scan-type+behavior+result-schema version; deterministic repair registry; validation issue + receipt model + error taxonomy; branded TrustedScanResult.

Test (keyless)

  • Per-stage valid/invalid fixtures; scan-specific fixtures; coordinate property tests; reference/evidence/boundary mutation tests; idempotence (same disposition+hash).

Freeze (§29)

  • P8 budget handshake, P6 scan/validation storage, P2 correction-overlay input — resolve before freeze.

Checklist

  • Issue model + error taxonomy + 7 dimensions (§7/§19)
  • Branded TrustedScanResult — only P3 mints (§6.4)
  • Ordered stage pipeline + §18 disposition + receipt
  • Validators: admissibility, contract-identity, coordinate/geometry, reference-integrity, boundary-safety, completeness
  • Per-scan-type invariants §7.1-§7.7 (deterministic subset, all 7 types)
  • Deterministic repair registry (§11) — derive-center, dedupe; idempotent; wired + recorded
  • Mutation + property-based tests (§23.3/§23.10)
  • Full JSON-Schema validation vs the P1 bundle by scan-type+schema+version (stage 2)

Gates & testing

  • P3 Output pipeline (syntax -> schema -> enum -> coordinate -> business-rule validation) with retry/fallback policy is defined and tested.
    CODE TEST

Requirements

  • REQ-CONTRACT-003 accepted Model output must pass a validation pipeline with retry/fallback before use.
    Done when: Syntax -> schema -> enum -> coordinate -> business-rule validation with the documented 2-attempt retry/fallback policy (same model simplified, then fallback provider).

Verify (done when)

ScanValidationPipeline runs the 10 ordered stages deterministically; validates against the exact P1 schema by scan-type+result-schema+version; issue model carries stage/code/severity/retryability/salvageability/path/trust-effect; reference/coordinate/evidence/boundary validators + deterministic repair registry exist; only P3 mints TrustedScanResult; keyless stage/mutation/property tests pass. Per spec §26/§27 (validation rows). §29 reconciliations resolved before freeze.

Cost triggers

No paid API / infra spend triggered by this item.

Status & editing

done   Sprint: P&C Wave 2: Core Contracts

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

Human-in-the-loop

No human tasks linked.

Findings

  • P3 was validating an invented shape, not canonical P1 — corrected: schema validation + normalization now bind P3 to @trovesnap/scan-contracts.
  • Minting the flat internal view leaked an implementation shape + forked the contract (P1/P2 nested vs P3 flat); corrected to canonical-nested.

Concerns

None recorded yet.

Risks

None recorded yet.

Tech debt

None recorded yet.

Tools used

  • Write — packages/scan-validation (10 modules incl. shape.mjs + schema-validate.mjs; 5 test files)
  • Bash node --test — 47 keyless tests pass
  • Bash ci:floor — 5 suites green (scan-validation auto-discovered)
  • tracker:promote — WI-012 -> in_progress on the live board

Files & artifacts

No files / artifacts recorded.

Update log

Update · 2026-06-20 · WI-012 P3 core — foundation slice (on main)

Built packages/scan-validation: issues.mjs (taxonomy+7 dims), trusted.mjs (branded result), validators.mjs (admissibility/contract-identity/coordinate-geometry/reference-integrity/boundary-safety/completeness + domain/evidence/scan-invariant subset), receipt.mjs, pipeline.mjs (ordered stages -> §18 disposition -> mint). 23 keyless tests green; ci:floor 5 suites. (A misplaced git worktree was recovered to main; solo work per D37.)

Update · 2026-06-22 · WI-012 — repair registry + per-type invariants + mutation tests

Added repair.mjs (§11 deterministic repair registry: derive_bbox_center, dedupe_warnings — idempotent, wired into the pipeline + recorded in the receipt). Expanded scan_invariants for item_scan/mark_scan/appraisal_prepare/appraisal_value (§7.3-§7.7). Added mutation tests (§23.10, 11 mutations) + coordinate property tests. 43 keyless tests pass; ci:floor 5 suites. Remaining: full JSON-Schema validation vs the P1 bundle.

Update · 2026-06-22 · WI-012 complete — full P1 schema validation + shape normalization (real contract)

Closed the last slice: stage 2 now runs full structural validation against the canonical P1 bundle (schema-validate.mjs: dependency-free Draft-2020-12 subset — type/required/properties/items/prefixItems/$ref/enum/const/min-max/minItems/pattern/minLength/additionalProperties), selecting the result-payload schema by scan-type. FINDING: the P3 core had been built against an invented draft shape (candidate_id, confidence 0-1, flat payload); the real P1 ScanResult is nested (result.{schema,status,payload}) with candidate.id, integer confidence 0-100, region.image_id, strongest_comps[] string ids. Added shape.mjs to normalize the real nested ScanResult/ScanRequest (input.images, profiles.watchlists) into the internal view, and realigned every validator (domain confidence 0-100, reference id/strongest_comps, scan-invariants raw_text/retake/currency) to real P1 field names. New shape.test.mjs proves the real nested ScanResult validates. 47 keyless tests pass; ci:floor 5 suites green.

  • P3 was validating an invented shape, not canonical P1 — corrected: schema validation + normalization now bind P3 to @trovesnap/scan-contracts.
Update · 2026-06-22 · Shape correction — TrustedScanResult is the canonical nested ScanResult (user decision)

Reversed the earlier flat-mint: P3 now NEVER reshapes. On accept it mints the canonical (safe-repaired) P1 ScanResult (result.{schema,status,payload}), branded — the same shape P1/P2 produce and P6/MCP store/project. normalizeDraft/Request stay an INTERNAL flat view the stages read (never emitted); safe-repairs mutate the canonical draft via payloadOf. shape.test.mjs now asserts nested input mints the canonical envelope (result.payload.candidates, envelope fields preserved, no flat leakage) + repair writes back into result.payload. 48 keyless tests; ci:floor 5 suites green.

  • Minting the flat internal view leaked an implementation shape + forked the contract (P1/P2 nested vs P3 flat); corrected to canonical-nested.