Description
Users can see the proxy error code on an enriched conversation but cannot inspect the upstream HTTP status and upstream error code preserved in request_logs. These distinguish authentication, capacity, quota and continuity failures while reviewing the affected conversation, without switching to an unrelated raw database dashboard. internal/enrich/postgres.go currently selects status, error_code and failure_phase only.
Read-only SQL on 2026-09-05 over request_logs.id <= 472100 (472,098 rows) found 75 non-null upstream_status_code values: 40 HTTP 404, 30 HTTP 401, four HTTP 503 and one HTTP 429. All 75 rows had an archive_request_id; none had a resp_-prefixed request_id. On these rows the upstream and proxy error codes agreed, but the HTTP status is additional evidence. Separately, 75 other rows had differing codes: 60 proxy stream_incomplete and 15 proxy codex_previous_response_stale, all retaining upstream previous_response_not_found and no upstream HTTP status. Across the full snapshot upstream_error_code was present in 469 rows, including 394 previous_response_not_found values. The existing bounded cached archive-ID join therefore matters for early-error enrichment. Recent IDs 462101..472100 had no such diagnostics, so historical population is not current universal coverage.
Scope is populated structured fields: upstream_status_code, upstream_error_code and upstream_transport. Do not bulk-export failure_detail, error_message, credentials, endpoint identifiers, client IPs or other freeform database content. failure_exception_type, bridge_stage and fallback fields were empty in the inspected snapshot and are not evidence-backed additions. Existing CXO-0001 owns enabling enrichment; CXO-0021 already owns the general status-disagreement view.
Acceptance Criteria
- #1 Matched conversation logs and response spans retain upstream HTTP status, upstream error code and upstream transport distinctly from the existing proxy and archive status/error fields.
- #2 The existing bounded archive-ID cache path can correlate early errors without response IDs; unavailable or ambiguous matches remain explicitly missing and never trigger unindexed per-turn scans.
- #3 Users can locate affected conversations by these structured diagnostics through a documented Loki query or existing error view, including a proxy-versus-upstream-code mismatch.
- #4 Absent fields remain absent; arbitrary error strings and identifiers do not become unrestricted metric labels, and freeform private database bodies are excluded.
- #5 Focused validation covers differing upstream/proxy codes, archive-only joins and missing fields; live delivery evidence is distinguished from historical SQL population.
Definition of Done
- #1 just check passes: fmt-check, lint, build, test-short and probe-ci all clean
Implementation Plan
Wave 2: commit root-owned seams first; frozen owned lanes implement with synthetic tests; integrate, just check, one corpus confidence gate and CodeRabbit; one push; watchtower-only deploy observation and m7kni proof; reconcile acceptance by evidence layer.
Implementation Notes
Clean main e8e97fd directly descends from c567894 and equals origin/main. CI 33988760737 and release-please 33988761017 succeeded. D8 holds; traces and agento11y remain disabled.
Final Summary
Wave 2 delivered at aca5e5de0bcd4ba6f5fd72dfaa6caef3a6c7fb71. Synthetic enrichment and sink tests cover differing codes, archive-only joins and absence; upstream diagnostics confined to turn body/response span. Live upstream error query returned empty: not observed in window, no historical SQL re-query. Postgres integration test skipped without DSN; runtime db_hit/cost movement verified. just check passed at that source SHA; CI 33993480890 success. Watchtower deployed that SHA healthy, restart count 0. Publish 33993481221 failed signing after successful manifest push; run-level publication completion remains open. The single D20 corpus gate was canceled (exit 143), so corpus confidence is not proven. Final tracker closeout is local under the one-push contract.