Technical spec · all specs
Implement one provider-neutral trust pipeline that converts an untrusted P2 ProviderAttempt into one of the following explicit outcomes:
retake_required or insufficient_evidence result;P3 validates more than JSON shape. It must determine whether the result:
The pipeline must fail predictably and preserve a complete audit trail.
A structurally valid model response is not automatically a trusted TroveSnap result. Strict structured output reduces syntax errors, but it does not prove that an image ID exists; a watchlist ID was supplied; a bounding box is usable; an OCR normalization matches the raw text; a condition observation has supporting evidence; a room candidate has one primary sighting; an appraisal used only supplied comparables; a visually inferred claim was represented as observed; a scan stayed within its permitted product boundary.
P3 is the boundary between model-shaped data and application-trusted evidence.
ProviderAttemptinterface ProviderAttempt {
attemptId: string;
requestId: string;
status: "response_received" | "provider_refusal" | "provider_error" | "cancelled";
canonicalDraft?: ScanResult;
rawResponseRef?: string;
route: RouteDecision;
provenance: ProviderProvenance;
transformations: AdapterTransformation[];
usage: ProviderUsage;
timing: ProviderTiming;
cost: CostObservation;
errors: ProviderError[];
warnings: AdapterWarning[];
}
P3 must not depend on OpenAI-, Gemini-, Claude-, or local-runtime response types.
P2 may deterministically convert provider coordinates to P1 coordinates; provider keys to canonical keys; explicit provider enum aliases; native tool arguments into a canonical draft; provider usage into common usage fields. P3 determines whether the resulting draft is trustworthy.
Partial results are permitted only when the scan-specific partial policy allows it; useful components are independently valid; invalid components can be removed without changing the meaning of valid components; required evidence and warnings remain attached; downstream consumers can distinguish partial from complete; no prohibited claim or value survives. Otherwise, P3 returns rejection, retake, insufficient evidence, or terminal failure.
retake_required is not a provider failureA valid item_scan that reports status: retake_required / reason: multiple_center_objects may be a successful and correct scan result. It must not automatically trigger another model call.
The model is expected to use uncertainty. identity.certainty: possible, functional_status: unknown, a low-confidence candidate, missing evidence reported by appraisal_prepare, ready_with_limitations, inability to read a damaged label — none are failures by themselves. Retry decisions must be based on contract violations or likely recoverable execution failures, not merely on uncertainty.
(1) The validation orchestrator. (2) Ordered validation stages. (3) Validation rule registry. (4) Scan-version-specific invariant rules. (5) Validation issue and error taxonomy. (6) Severity, retryability, and salvageability classification. (7) Deterministic safe-repair rules. (8) Same-provider correction retry. (9) Same-provider clean retry. (10) Cross-provider fallback orchestration. (11) Cost- and attempt-bounded recovery policy. (12) Partial-result salvage policies. (13) Candidate-, observation-, field-, and result-level trust decisions. (14) Final result dispositions. (15) Validation receipts and audit history. (16) Retry/fallback observability. (17) Keyless deterministic testing. (18) Integration with the P2 provider gateway. (19) Integration hooks for P8 budget authorization. (20) Output required by E3 to display warnings, partial state, retake guidance, and unavailable sections.
Provider-native request construction; provider SDK calls; provider decoding; provider coordinate conversion; arbitrary fuzzy enum conversion; candidate ranking; candidate priority calculation; provider quality benchmarking; local-model qualification; external comparable retrieval; pricing baseline calculation; durable inventory promotion; UI rendering; human review decisions; verification of real-world authenticity or functionality not supported by supplied evidence. P3 may reject unsupported claims, but it does not independently identify the photographed object.
P1 ScanRequest + P2 ProviderAttempt
│
▼
Attempt Admissibility
▼
Contract Identity & Version
▼
Structural Schema
▼
Domain Values & Enums
▼
Reference Integrity
▼
Coordinate & Geometry
▼
Evidence & Claim Integrity
▼
Scan-Specific Invariants
▼
Safety & Product Boundary
▼
Completeness & Usability
▼
Disposition
├─ accept
├─ accept with warnings
├─ safe deterministic repair
├─ return legitimate retake/insufficient evidence
├─ salvage approved partial
├─ retry same provider
├─ fallback provider
└─ reject/fail
Validation stages always execute in a deterministic order. Later stages may be skipped when an earlier stage makes the draft unreadable or structurally unusable.
interface ScanValidationPipeline {
validate(request: ScanRequest, attempt: ProviderAttempt, policy: ValidationPolicy): Promise<ValidationOutcome>;
}
interface ScanRecoveryOrchestrator {
execute(request: ScanRequest, initialAttempt: ProviderAttempt, policy: RecoveryPolicy, signal: AbortSignal): Promise<TrustedScanOutcome>;
}
interface TrustedScanOutcome {
requestId: string;
disposition: "accepted" | "accepted_with_warnings" | "partial" | "retake_required" | "insufficient_evidence" | "rejected" | "failed";
result?: ScanResult;
validation: ValidationReceipt;
attempts: AttemptSummary[];
userGuidance?: UserGuidance;
}
type TrustedScanResult = ScanResult & { readonly __trustedScanResult: unique symbol };
Only P3 may construct this type. This reduces accidental use of an unvalidated P2 canonical draft.
issue:
code: unknown_watchlist_entry
stage: reference_integrity
severity: error
path: result.payload.candidates[0].watchlist_matches[0].entry_id
message: Result referenced a watchlist entry that was not supplied.
retryability: retryable_same_provider
salvageability: drop_candidate
trust_effect: invalidates_node
related:
candidate_id: cand_1
provider_attempt_id: attempt_901
severity:
- info
- warning
- error
- fatal
Severity describes seriousness; it does not alone determine retry behavior.
retryability:
- not_retryable
- retryable_same_provider
- retryable_same_provider_with_correction
- retryable_fallback_provider
- retryable_after_delay
- requires_user_retake
- requires_more_evidence
salvageability:
- none
- retain_with_warning
- remove_field
- remove_observation
- remove_sighting
- drop_candidate
- downgrade_result_to_partial
trust_effect:
- none
- warns_field
- invalidates_field
- invalidates_node
- invalidates_result
These dimensions remain separate. A bad optional candidate box may be an error, retryable, and candidate-droppable; an invented appraisal comparable is fatal and invalidates the result; low confidence may be a warning with no retry; a provider timeout may be fatal for the attempt but retryable using a fallback.
Every accepted, partial, rejected, or failed result receives an immutable receipt.
validation_receipt:
id: validation_771
request_id: req_8f29
policy_version: validation-policy-3
scan:
type: room_scan
version: 3.0.0
result_schema: room_scan_result
result_version: 3.0.0
disposition: partial
source_attempt_id: attempt_903
stages:
- stage: attempt_admissibility
status: passed
- stage: contract_identity
status: passed
- stage: schema
status: passed
- stage: domain_values
status: passed
- stage: reference_integrity
status: passed_with_warnings
- stage: coordinate_geometry
status: failed_nodes_removed
- stage: evidence_integrity
status: passed
- stage: scan_invariants
status: passed_with_warnings
- stage: boundary_safety
status: passed
- stage: completeness_usability
status: partial
repairs:
- derive_missing_center
- remove_invalid_candidate
removed_nodes:
- result.payload.candidates[3]
issues:
- issue_room_candidate_bbox_out_of_bounds
trusted_result_hash: sha256:...
validated_at: 2026-06-19T20:15:00Z
The receipt must make it possible to determine what was received; what failed; what was changed; what was removed; why another provider was called; why the final result was accepted or rejected.
Determine whether the P2 attempt contains a response that can be validated: request ID matches; attempt belongs to the current request; provider attempt was not cancelled; provider status is known; canonical draft exists when status is response_received; provider refusal represented explicitly; provider errors mapped to the P2 taxonomy; required provenance present; scan type supported by the selected model; the attempt did not violate privacy or budget authorization.
attempt_admissibility:
passed:
continue: true
provider_error:
continue: false
recovery: retry_or_fallback
provider_refusal:
continue: false
recovery: policy_dependent
cancelled:
continue: false
recovery: none
A provider transport failure is not treated as a malformed scan result.
Verify that the draft is for the requested contract: request ID matches; tenant scope matches; scan.type matches; scan behavior version matches; result-schema identity is the expected schema; result-schema version supported; common envelope version supported; taxonomy version available; referenced watchlist versions match; prompt and schema provenance present when required.
- wrong_scan_type
- unsupported_result_version
- result_schema_mismatch
- request_id_mismatch
- watchlist_version_mismatch
- taxonomy_version_unavailable
A result for table_hunt may never be accepted as an item_scan merely because some fields overlap.
Validate the canonical draft against the exact P1 result schema: required fields; field types; array/object shape; allowed union variant; status-specific required fields; forbidden additional properties where strict; JSON-Schema-expressible ranges; conditional requirements; result-envelope structure.
The schema is selected using scan type + result schema ID + result schema version. P3 must not select a "close enough" schema. Outcomes: pass; pass after explicitly permitted deterministic repair; fail and retry; fail terminally when no compatible schema exists.
Validate domain-level values that may not be fully enforced by the provider schema projection: shared P1 enums; scan-specific enums; taxonomy identifiers; condition vocabularies; appraisal-reason codes; next-action codes; overlay-state codes; confidence ranges; priority values; currency codes; date formats; supported next-scan type and version.
Enum policy. P2 may apply explicit provider aliases defined by a versioned map. P3 must not perform fuzzy coercion such as "pretty sure" → probable, "old-ish" → possible_antique, "valuable" → possible_high_value. Unknown values are rejected; removed if the optional field is independently removable; or sent back in a correction retry. All coercions must come from an explicit versioned rule.
Image references: every evidence image exists in the request or permitted prior evidence; every sighting references a supplied image; every condition observation references a supplied image; mark-scan evidence references the mark image; image roles valid for the scan; result image IDs not invented.
Watchlist references: watchlist ID supplied; version matches; entry ID belongs to that version; match type valid; not another tenant.
Candidate references: candidate IDs unique; parent candidate references exist; next-scan targets resolve; sighting IDs unique where used.
Evidence references: prior-scan references exist; claim evidence paths resolve; supplied-fact references exist; evidence source type matches the referenced source.
Comparable references (appraisal_value): every comparable ID supplied; internal/external namespaces distinct; strongest-comp IDs resolve; adjustments cite no nonexistent comps; no provider-created comp appears.
Unknown IDs are never silently replaced with likely IDs.
Validate every candidate region and sighting after P2 normalization: (1) coordinate space is P1 normalized 0–1000; (2) each coordinate integer; (3) 0 <= x_min < x_max <= 1000; (4) 0 <= y_min < y_max <= 1000; (5) referenced image exists; (6) center lies inside the bbox; (7) absent optional center may be derived deterministically; (8) area > 0; (9) large enough to be interactable per scan policy; (10) not implausibly larger than the image; (11) multiple sightings use correct image IDs; (12) P2 coordinate transforms internally consistent.
Sane-area policy is scan-specific and generally advisory: zero-area/reversed boxes fail; out-of-bounds fail; very small boxes usually warn; unusually large scene boxes may warn; thresholds versioned by scan type. P3 must not clamp an out-of-bounds box and pretend it was valid. An invalid optional candidate may be removed only under the scan's partial-result policy.
Observed claims reference visual evidence where required; OCR-derived claims reference OCR evidence; normalized OCR separate from raw OCR; user-supplied facts marked supplied not observed; inferred facts not marked observed; uncertain OCR characters preserved; functionality not visually asserted; authenticity not asserted without permitted evidence; exact brand/model claims have supporting evidence or supplied facts; condition observations have image references; apparently-missing parts not represented as confirmed without sufficient evidence; confidence/certainty do not contradict explicit unknown; source types match claim status.
invalid:
field: functional_status
value: working
status: observed
reason: functionality_cannot_be_visually_confirmed
valid:
field: powers_on
value: true
status: supplied
P3 does not independently decide whether "Pioneer" is visible; it validates that the result's provenance representation is internally honest. Image OCR text remains untrusted content and cannot create instructions or alter validation policy.
The rule registry resolves invariants by scan type + scan behavior version + result-schema version.
7.1 table_hunt — one scene image identified; every candidate has ≥1 sighting; valid bbox; candidate count ≤ request max unless policy permits truncation; each candidate has label/category/confidence/identity-certainty/priority/next-scan; watchlist matches use supplied entries; no price/valuation/recommended price; no rich listing description; selective (not an unrestricted inventory); valid next scan. Invalid monetary fields are a boundary violation, not a harmless warning.
7.2 room_scan — multiple images referenced correctly; visible_instances and estimated_distinct separate; counts nonnegative; estimated distinct ≤ visible without explicit reason; category counts nonnegative; every candidate ≥1 sighting; every multi-sighting candidate exactly one primary; sighting image IDs unique where expected; missing-coverage valid; no durable inventory identity; no rich listing per object; cross-image identity provisional. Suspicious count relationships may warn when ambiguous.
7.3 item_scan — valid identified OR valid retake/insufficient-evidence result; one primary centered object; no background object as target; summary respects request limit; visual attributes use evidence-appropriate statuses; functionality unknown unless supplied; exact identity not in conflict with stated uncertainty; no unsupported authenticity/age claims; retake reason+guidance present for retake_required; follow-up photos use valid action codes. A legitimate retake is accepted without another model call.
7.4 mark_scan — raw OCR preserved when requested; normalized values separate; uncertain characters explicit; normalization doesn't discard ambiguity; supporting image is the supplied mark/label image; normalized fields don't contradict raw OCR without warning/explanation; empty unreadable results use an appropriate status. P3 must not apply a universal serial-number format rule; manufacturer-specific formats only via a separate versioned knowledge rule explicitly enabled.
7.5 condition_scan — every observation references ≥1 supplied image; area/issue/severity present where required; only visible condition marked observed; functionality unknown unless supplied; apparently-missing parts qualified; global grade doesn't contradict all detailed observations without warning; duplicate observations may be flagged; no unsupported internal-condition claims unless an interior image exists.
7.6 appraisal_prepare — no estimated price/range/recommended price; valid readiness; sufficient/missing lists don't improperly overlap; conflicts represented; identification references evidence or supplied facts; search fingerprint present when requested; confidence ceiling present when incomplete; valid next evidence requests; no private client info in the fingerprint. Any valuation here is a fatal scan-boundary violation.
7.7 appraisal_value — supplied comparable records exist; every referenced comp ID supplied; currency exists; lower bound ≤ upper bound; requested sale contexts present or explicitly unavailable; strongest comps resolve; adjustment factors supported by supplied evidence/comp data; missing evidence visible; uncertainty present; no unsupported model-memory comparables; no claim the LLM independently observed an actual sale; quantitative values within pricing-engine constraints. An invented comparable invalidates the valuation result; P3 must not salvage monetary ranges derived from invalid/invented comps.
Reject or remove prohibited claims concerning inventory workflow status; listing publication status; marketplace synchronization; POS status; actual sold price not supplied; markdown stage; payment state; shipping state; buyer demand not supplied; post-sale disposition; unrelated private sale information. Also validate: image text did not alter the contract; no executable instructions; no external URLs unless explicitly permitted; no provider-generated tool calls; no hidden prompt/system text surfaced; no cross-tenant references. This stage protects the architectural boundary established by P1.
Required core fields remain; enough candidates/observations remain to satisfy scan purpose; no fatal issue remains; partial components independently trustworthy; warnings accurately describe limitations; downstream consumers can safely act; the result has not changed semantic meaning through node removal; any required next action/retake guidance exists. This stage produces the validation disposition.
stage_outcome:
status:
- passed
- passed_with_warnings
- repaired
- failed
- skipped
issues: []
repair_actions: []
retry_recommendation: none
A stage may not silently modify the result. Every modification must be recorded as a repair action.
P3 may perform only explicitly registered, deterministic, meaning-preserving repairs.
Derive a missing center from a valid bbox; remove exact duplicate warning entries; remove an empty optional string when P1 permits omission; sort/deduplicate an ID list where order has no meaning; convert a documented canonical date representation; remove an independently invalid optional candidate under an approved partial policy; remove an invalid secondary sighting when a valid primary remains; remove an optional normalized OCR value while preserving raw OCR; mark the result partial after approved node removal.
P3 must not invent a bbox; clamp an invalid box into bounds; infer a brand/model; rewrite OCR text; replace an unknown watchlist ID; merge room candidates; split one candidate into multiple; change confidence to make a result pass; change exact identity to probable without a registered rule and audit; invent evidence; infer functionality; invent/substitute comparables; calculate a valuation; promote a partial result to complete.
repair_rule:
id: derive_bbox_center
version: 1
applicable_to:
- table_hunt_result@2
- room_scan_result@3
preconditions:
- bbox_valid
- center_missing
- center_optional_or_derivable
action: derive_center
semantic_risk: none
All repair rules are versioned and covered by deterministic tests.
P3 owns the decision to retry or fall back. P2 performs each requested provider attempt.
P3 recovery decision → P2 gateway executes selected attempt → P3 validates new ProviderAttempt
Adapters must not silently retry or switch providers.
Use when a registered deterministic repair can safely resolve the issue (missing derived center; duplicate warning; removable invalid optional secondary sighting).
Use when the provider returned a mostly usable structured result with specific correctable violations. P3 emits a structured issue summary; P2 compiles it into a corrected provider call (P3 supplies data, not prompt text — see §29).
correction:
previous_attempt_id: attempt_901
violations:
- path: result.payload.candidates[1].sightings[0].region.bbox
code: bbox_out_of_bounds
- path: result.payload.candidates[2].watchlist.entry_id
code: unknown_watchlist_entry
instruction:
return_complete_replacement_result: true
preserve_supported_observations: true
use_only_supplied_ids: true
The provider returns a complete replacement result, not a patch.
Use when output appears broadly malformed, truncated, or confused and a correction prompt would be less reliable than rerunning. Possible changes: simplified prompt package; reduced optional outputs; lower candidate count; more compact schema projection; adjusted output-token limit within budget. The scan's meaning and required invariants must not change.
Use when provider transport repeatedly fails; the selected model cannot satisfy the schema; the provider repeatedly violates core invariants; the result remains unusable after allowed same-provider recovery; P2 routing identifies another qualified provider; privacy and budget policy permit it. P3 asks P2 for the next eligible route and provides exclusion info:
fallback_request:
exclude_attempts:
- attempt_901
- attempt_902
exclude_routes:
- provider: local
model: local-vlm-1
reason: repeated_invalid_coordinates
required_capabilities:
- multi_image
- structured_output
Use when the problem is the input rather than provider execution (no centered object; unreadable label; too distant; damage not visible; appraisal lacks required evidence). Do not spend money repeatedly asking other models to solve an image-quality problem the result already identified correctly.
recovery_matrix:
provider_timeout:
action: retry_after_delay_or_fallback
rate_limited:
action: retry_after_delay_or_fallback
malformed_structured_output:
action: same_provider_clean_retry_then_fallback
schema_missing_required_field:
action: same_provider_correction_then_fallback
unknown_enum:
action: correction_if_core_else_remove_optional_field
unknown_image_reference:
action: correction_then_fallback
unknown_watchlist_reference:
action: correction_or_drop_candidate_if_partial_allowed
bbox_out_of_bounds:
action: correction_or_drop_candidate_if_partial_allowed
no_clear_center_object:
action: accept_retake_required
unreadable_mark:
action: accept_insufficient_evidence_or_request_retake
invented_comparable:
action: reject_attempt_and_fallback
appraisal_prepare_contains_price:
action: reject_attempt_and_correction_or_fallback
low_confidence:
action: accept_with_warning
identity_unknown:
action: accept_if_scan_contract_allows
safety_refusal:
action: policy_dependent_no_unsafe_circumvention
Recovery policy must be based on failure cause, not simply on stage number.
Recovery is constrained by maximum total attempts; maximum same-provider attempts; maximum fallback attempts; maximum cumulative cost; maximum elapsed time; tenant privacy policy; provider availability; scan-specific policy; P8 authorization for every billable attempt.
recovery_policy:
id: standard-vision-recovery-v1
max_total_attempts: 3
max_same_route_attempts: 2
max_fallback_attempts: 1
allow_correction_retry: true
allow_clean_retry: true
allow_provider_fallback: true
max_cumulative_cost_usd: 0.03
deadline_ms: 60000
stop_on:
- valid_retake_required
- valid_insufficient_evidence
- user_cancelled
- privacy_policy_block
- budget_exhausted
These are policy examples, not permanent constants. P8 owns budget values; P3 enforces the authorized recovery envelope.
scan_recovery_profiles:
table_hunt:
allow_candidate_salvage: true
allow_fallback: true
prefer_partial_over_terminal_failure: true
room_scan:
allow_candidate_salvage: true
allow_sighting_salvage: true
allow_fallback: true
preserve_count_uncertainty: true
item_scan:
allow_core_identity_partial: false
accept_valid_retake: true
allow_fallback: true
mark_scan:
allow_raw_ocr_without_normalized_fields: true
accept_unreadable_status: true
prefer_user_retake_for_bad_image: true
condition_scan:
allow_observation_salvage: true
never_salvage_unsupported_functionality: true
appraisal_prepare:
allow_readiness_with_limitations: true
prohibit_any_valuation: true
appraisal_value:
allow_monetary_partial: false
reject_invented_comparables: true
require_supplied_comp_integrity: true
Partial results are explicit products, not hidden error recovery.
partial_result:
status: partial
completeness:
level: candidate_subset
retained_candidates: 4
removed_candidates: 2
limitations:
- two_candidates_removed_for_invalid_regions
unavailable:
- complete_candidate_set
warnings:
- code: incomplete_scene_candidates
A partial result must include status: partial; a completeness description; limitations; removed/unavailable sections; warnings; validation receipt; retained data that independently passes validation.
table_hunt: six candidates, one invalid box → remove invalid, mark partial, retain valid five, record removal. room_scan: candidate with invalid secondary sighting but valid primary → remove secondary, retain candidate, warn coverage incomplete. mark_scan: valid raw OCR but unsupported normalized extraction → retain raw OCR, remove invalid normalized field, mark partial, recommend another photo/manual review. condition_scan: three valid observations + one referencing unknown image → remove invalid, return remaining as partial.
P3 must not return a table_hunt candidate without a valid region; an item_scan identity assembled from contradictory fields; a condition issue with no supporting image; an appraisal_prepare containing a price; an appraisal_value range from invented/unresolved comparables; a result where all core fields were removed; a result whose remaining content materially changes the provider's original meaning.
accepted — all required stages pass; no trust-affecting warning remains; no semantic repair required; complete for its declared status.
accepted_with_warnings — complete and trustworthy; warnings describe uncertainty/noncritical limitations; no invalid required node remains (e.g. small but valid room candidate; low confidence; incomplete room coverage reported; usage metadata unavailable).
partial — scan-specific policy permits partial; invalid optional nodes removed; useful independently valid data remains; limitations explicit.
retake_required — the result validly concludes the image cannot support the scan; retake reason+guidance pass P1; retrying another provider unlikely to fix the image.
insufficient_evidence — evidence genuinely incomplete; scan correctly reports what is missing; another call on the same evidence not justified.
rejected — a result existed but cannot be trusted; recovery disallowed/exhausted; a boundary violation remains; no safe partial exists.
failed — no usable provider result obtained; transport/execution failed; budget/deadline/routing exhausted; user cancelled.
Rejected = "a result existed but was untrustworthy." Failed = "the execution process did not produce a validatable usable result."
attempt_error: [provider_error, provider_refusal, timeout, rate_limited, cancelled, missing_canonical_draft, provenance_missing, budget_violation, privacy_policy_violation]
contract_error: [request_id_mismatch, tenant_mismatch, scan_type_mismatch, scan_version_mismatch, result_schema_mismatch, unsupported_result_version, taxonomy_version_mismatch, watchlist_version_mismatch]
structural_error: [invalid_envelope, missing_required_field, invalid_field_type, unexpected_field, invalid_union_variant, malformed_status_payload]
domain_error: [unknown_enum, unknown_taxonomy_id, invalid_confidence, invalid_currency, invalid_date, invalid_next_scan, candidate_limit_exceeded]
reference_error: [unknown_image_id, unknown_watchlist_id, unknown_watchlist_entry, unknown_candidate_id, duplicate_candidate_id, unknown_evidence_ref, unknown_comparable_id, cross_tenant_reference]
geometry_error: [bbox_missing, bbox_out_of_bounds, bbox_reversed, bbox_zero_area, center_outside_bbox, image_coordinate_mismatch, implausibly_small_region, invalid_primary_sighting_count]
evidence_error: [observed_claim_without_evidence, ocr_normalization_without_raw_text, supplied_fact_marked_observed, inference_marked_observed, unsupported_functionality_claim, unsupported_authenticity_claim, condition_without_image, uncertain_ocr_discarded, claim_evidence_contradiction]
boundary_error: [valuation_in_discovery_scan, valuation_in_appraisal_prepare, invented_comparable, unsupplied_demand_signal, invented_inventory_state, invented_sale_state, invented_payment_state, invented_shipping_state, external_action_requested]
user_guidance:
type:
- none
- retake_photo
- provide_more_evidence
- manual_review
- try_again
- service_unavailable
title: Scan needs another photo
message: Move closer and center one item.
requested_actions:
- centered_item
E3 owns presentation. P3 owns the structured reason and trusted guidance source. Technical provider errors must not be exposed verbatim to end users.
Persist: (1) the original P1 request; (2) every P2 provider attempt; (3) every canonical draft; (4) every validation report; (5) every deterministic repair; (6) every correction request; (7) every fallback decision; (8) the final trusted result, if any; (9) the validation receipt; (10) cumulative usage and cost; (11) user/reviewer overrides when later supported. Do not overwrite failed attempts with successful attempts. The complete attempt chain must remain replayable. (Storage schema lives in P6 — see §29.)
recovery_chain:
request_id: req_8f29
attempts:
- attempt_id: attempt_901
provider: local
disposition: rejected
reason: invalid_candidate_regions
- attempt_id: attempt_902
provider: local
disposition: rejected
reason: repeated_invalid_candidate_regions
- attempt_id: attempt_903
provider: gemini
disposition: partial
final:
source_attempt_id: attempt_903
disposition: partial
trovesnap.scan.recovery
├─ trovesnap.validation.attempt_admissibility
├─ trovesnap.validation.contract_identity
├─ trovesnap.validation.schema
├─ trovesnap.validation.domain_values
├─ trovesnap.validation.reference_integrity
├─ trovesnap.validation.coordinate_geometry
├─ trovesnap.validation.evidence_integrity
├─ trovesnap.validation.scan_invariants
├─ trovesnap.validation.boundary_safety
├─ trovesnap.validation.completeness
├─ trovesnap.validation.repair
├─ trovesnap.recovery.decision
├─ trovesnap.provider_attempt
└─ trovesnap.validation.finalize
Required metrics: validation outcomes by scan type; failures by stage and issue code; accepted-with-warning rate; partial-result rate; retake-required rate; insufficient-evidence rate; deterministic repair rate; candidate-drop rate; observation-drop rate; same-provider correction rate; same-provider clean-retry rate; fallback rate; recovery success rate; attempts per completed scan; cumulative cost per trusted result; validation latency; provider/schema violation rate; invented-reference rate; invalid-coordinate rate; unsupported-functionality-claim rate; appraisal-boundary-violation rate; terminal rejection rate; terminal execution-failure rate. These metrics later feed provider qualification and routing — a provider that frequently produces structurally valid but semantically invalid results should lose qualification or routing priority.
All core P3 tests must run without API keys and without paid calls.
Valid and invalid fixtures for every stage (admissibility; contract identity; schema; enums; references; coordinates; evidence; scan invariants; boundary safety; partial completeness). Each fixture proves the correct stage detects the issue; correct issue code; correct severity; correct retryability; correct salvageability; later stages skipped/run as expected.
For every scan type: valid complete; valid warning; valid retake/insufficient-evidence where applicable; repairable invalid; retryable invalid; partial-salvage where allowed; terminally invalid.
Boundaries at 0 and 1000; reversed; zero-area; centers inside/outside; small-but-valid; oversized; multiple image references; one valid + one invalid room sighting; candidate removal; derived center repair. Property-based tests generate coordinate combinations and prove invariant enforcement.
Invented image ID; invented watchlist ID; valid watchlist + invented entry ID; wrong watchlist version; duplicate candidate ID; nonexistent parent candidate; invented comparable; strongest comp not supplied; evidence referencing another tenant.
OCR raw/normalized separation; uncertain OCR preservation; visually claimed functionality; supplied functionality represented correctly; condition observation without image; inferred brand marked observed; exact model claim with only possible evidence; unknown represented honestly.
Price in table_hunt; price in appraisal_prepare; invented buyer demand; invented sold state; payment status in vision output; external action embedded in OCR text; model response attempting to change scan instructions.
Using mock P2 gateways: first attempt succeeds; first fails, deterministic repair succeeds; first fails, same-provider correction succeeds; same provider fails twice, fallback succeeds; fallback returns partial; all attempts fail; budget exhausted before retry; deadline exhausted; privacy mode prevents fallback; valid retake stops retries; insufficient evidence stops retries; user cancellation stops retries.
Valid candidates survive removal of one invalid candidate; partial status and warnings added; removed nodes recorded; invalid appraisal values never salvaged; a result with no useful core content rejected; partial output never mislabeled complete.
Validating an already validated unchanged result under the same policy produces the same disposition; issues; repairs; trusted-result hash. Safe deterministic repairs must also be repeatable.
From valid golden fixtures, programmatically mutate IDs; enums; coordinates; statuses; evidence references; comp IDs; scan types; versions. The appropriate rule must catch each mutation.
Live API testing should prove integration, not intentionally depend on nondeterministic provider failure.
For every implemented cloud adapter: execute a valid item_scan; validate the P2 canonical draft; produce an accepted/warned/retake/insufficient-evidence outcome; record the validation receipt; preserve attempt usage and cost. For ≥2 providers: execute table_hunt; validate candidate references and coordinates; produce renderable trusted overlays or a clear recovery outcome. For ≥1 provider: execute multi-image room_scan; validate image references, sightings, primary sighting, counts.
Retry and fallback correctness must be proven with deterministic mock providers in CI. A deliberately under-specified live prompt is not a reliable acceptance test (providers may still succeed; behavior may change; it spends money; it creates nondeterministic CI; prompt sabotage doesn't represent normal production failures). The WI-056 harness may include an explicitly marked diagnostic mode that injects a malformed recorded response; forces a chosen validation failure; simulates provider timeout; forces route failure; exercises same-provider and fallback behavior. Optional controlled live recovery tests may be run manually under a strict budget cap.
25.1 Retry budget per scan type — P8 owns monetary/tenant budgets; P3 owns attempt-count, cause-based recovery, and stop conditions; every additional attempt requires P8 authorization; recovery profiles vary by scan type. 25.2 Should partial results reach the UI? — Yes, only when scan-specific policy permits safe salvage and the UI receives explicit partial status, limitations, unavailable sections, and warnings. Never disguised as complete. 25.3 Should every bad provider result become partial? — No. Unsafe, misleading, boundary-violating, or core-incomplete results must be rejected. 25.4 Who performs enum coercion? — P2 applies explicit provider-representation mappings; P3 validates canonical values; P3 may apply only registered deterministic canonical repair; fuzzy semantic coercion prohibited. 25.5 Who handles provider retry? — P3 decides and orchestrates recovery; P2 performs each attempt; adapters don't silently retry/fall back. 25.6 Should low-confidence output trigger fallback? — Not by itself. Fallback requires a contract violation, usability failure, policy threshold, or explicitly configured quality gate. 25.7 Should a retake result trigger another provider? — Normally no. A valid retake indicates an input problem, not a provider failure. 25.8 Can P3 repair missing evidence? — No. It may remove unsupported claims or request another scan, but cannot invent evidence. 25.9 Can appraisal results be partially salvaged? — Explanatory nonmonetary fields may be retained internally for diagnosis; monetary output is not surfaced when comparable integrity fails; invented/unresolved comparables invalidate the valuation.
P3 is complete only when the repository contains: (1) ScanValidationPipeline; (2) ScanRecoveryOrchestrator; (3) TrustedScanOutcome schema + types; (4) branded TrustedScanResult; (5) validation issue schema; (6) validation receipt schema; (7) error taxonomy; (8) rule registry; (9) scan-version rule resolver; (10) structural schema validator; (11) domain/enum validator; (12) reference-integrity validator; (13) coordinate/geometry validator; (14) evidence/claim validator; (15) scan-specific invariant validators; (16) safety/product-boundary validator; (17) completeness/usability evaluator; (18) deterministic repair registry; (19) partial-result policy registry; (20) cause-based recovery matrix; (21) retry/fallback orchestrator; (22) P8 budget-authorization integration; (23) OpenTelemetry spans + metrics; (24) golden valid/invalid fixtures; (25) mutation + property-based tests; (26) mock provider recovery tests; (27) live integration tests; (28) WI-056 validation/recovery views; (29) validation policy + operator documentation.
P3 is complete when: (1) P3 accepts a P1 request + P2 ProviderAttempt; (2) provider-native response types don't leak into P3; (3) P3 produces an explicit trusted disposition; (4) only P3 can construct a TrustedScanResult; (5) validation executes in a deterministic documented order; (6) contract identity/version validated before scan content; (7) every canonical result validated against its exact P1 schema; (8) enums/taxonomy IDs validated without fuzzy guessing; (9) all image/candidate/watchlist/evidence/comparable references checked; (10) scene/room coordinates validated against P1 rules; (11) every condition observation references supplied evidence; (12) raw OCR separate from normalized OCR; (13) supplied facts can't be represented as observed without an issue; (14) functionality can't be accepted as visually observed; (15) cross-image room candidates have valid sightings + exactly one primary; (16) discovery scans can't return valuations; (17) appraisal_prepare can't return monetary estimates; (18) appraisal_value can't use unsupplied comparables; (19) application-owned inventory/listing/POS/payment/shipping/sale state rejected from vision output; (20) every issue has stage/code/severity/retryability/salvageability/path/trust-effect; (21) every repair registered/versioned/tested/recorded; (22) P3 never invents evidence/identity/coordinates/comparables; (23) valid retake_required stops recovery; (24) valid insufficient_evidence stops unnecessary recovery; (25) unknown/low-confidence don't fail solely for being uncertain; (26) recovery decisions based on failure cause; (27) same-provider retries + fallback executed through P2; (28) every billable retry requires budget authorization; (29) attempt/cost/deadline/privacy/cancellation limits enforced; (30) partial results emitted only under explicit policies; (31) partial results identify removed nodes/limitations/unavailable sections; (32) invalid appraisal monetary output never surfaced as partial; (33) rejected and failed remain distinct; (34) every final outcome includes a validation receipt; (35) the full attempt+validation chain is stored and replayable; (36) E3 can distinguish all seven dispositions; (37) deterministic tests require no API keys; (38) mock retry/fallback tests pass in CI; (39) mutation tests catch reference/coordinate/enum/evidence/boundary violations; (40) live valid provider results pass through P2 and P3 with usage + cost preserved.
P3 is done when an untrusted provider attempt can enter one deterministic recovery pipeline and leave as a trusted complete P1 result; a trusted warned result; a deliberately constrained partial result; a valid request for user retake or more evidence; or a structured rejection/failure after bounded recovery. No invalid model output may enter overlays, ranking, appraisal, inventory promotion, or downstream application logic merely because it parsed as JSON. The P3 outcome is a provider-neutral trust and recovery boundary that makes TroveSnap Vision predictable; evidence-aware; version-safe; cost-bounded; retryable; auditable; partially salvageable where safe; and resistant to plausible but unsupported model output.
Adopting P3 closes P2 §31 #1 (P3 consumes the ProviderAttempt and owns trust + retry/fallback; P2 only executes attempts). Remaining seams to resolve before freeze:
P8-observability-cost.md §21–§24, §61.scan_requests, provider_attempts, canonical_drafts, validation_receipts, and the recovery chain, tenant-scoped and replayable.This gate splits along the spec's own ScanValidationPipeline vs ScanRecoveryOrchestrator boundary (both gate P3, owner Paul):
TrustedScanResult, keyless stage/mutation/property tests.ScanRecoveryOrchestrator, cause-based recovery matrix, recovery classes (no-call repair, same-provider correction/clean retry, fallback through P2), partial-result policy registry, attempt/cost/deadline limits + P8 budget integration, mock-gateway recovery tests, live integration tests.