digidem / digidem/comapeo-cloud-app
feat: per-server diagnostics export — capture raw archive responses with validation results for agent debugging
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 29
Description
Problem
When a connected remote archive server returns unexpected responses, there is no way for a user (or an agent debugging on their behalf) to inspect what the server actually sent:
handleResponse()(src/lib/api-client.ts:285-339) runsv.parse(schema, body)— on schema mismatch Valibot throws and the raw body is discarded.- On HTTP errors,
ApiErrorkeeps status/code/message but not the body. - Partial failures surface only as
console.warnstrings (src/lib/remote-archive.ts:359) and sync-result error messages — nothing persistent or exportable. - DevTools Network export is manual, includes credentials, and not feasible for field users.
Proposed design — on-demand per-server diagnostics export
An explicit "Run diagnostics" action (Advanced Settings / ArchiveServerDetail) that performs a bounded, one-shot capture over the existing archive transport and exports a single JSON bundle designed for agent consumption.
Design validated by dual review (Opus 5 + Codex GPT-5.6, both with repo access — consensus on all points below).
Architecture: capture at the existing seam, not a parallel client
shared archive transport (api-client)
→ bounded body reader (response.text(), capped)
→ JSON decoding
→ v.safeParse validation
→ normal client classification
→ optional diagnostic observer (module-scoped sink, armed only for one run)
- Refactor
handleResponsetoawait response.text()→JSON.parse→v.safeParseand emit to an off-by-default observer sink armed only for the duration of one diagnostics run. Diagnostics then calls the ordinaryapiClientmethods in order. - Rationale: a standalone replay client would duplicate
resolveApiRequest()proxy-header logic,getAuthHeaders(),classify404(), and unsupported-endpoint fallbacks — and would diagnose a code path that isn't the failing one. - Going through
apiClientalso gives the security-startup gate for free (resolveApiRequest:136-143). - Do NOT attach raw bodies to
ApiErrorglobally — those errors reach logs, UI state, and telemetry.
Capture scope (V1)
/info,/healthcheck,/projects, then per selected project: project detail, observations, tracks, alerts, presets, fields.- No icons in V1 — byte-length still requires full download (proxy has no HEAD), and the icon handler does upstream SVG→PNG fallback fetches.
- Project scope selection when the archive exceeds a small threshold, with estimated request count shown. Sequential or concurrency 1-2. Cancel support with partial export. No overlap with an active sync or another diagnostics run.
Bundle format (per-endpoint entry envelope)
{
"endpoint": "observations",
"projectId": "…",
"request": { "method": "GET", "path": "/projects/…/observations", "via": "same-origin-api-proxy" },
"durationMs": 84,
"outcome": "response",
"response": {
"status": 200,
"source": "proxy | upstream",
"headers": {},
"body": { "text": "…", "bytesCaptured": 102400, "truncated": true }
},
"schemaId": "observationsResponse",
"validation": { "status": "schema-mismatch", "issues": [{ "path": "$.data[3].lat", "kind": "…", "expected": "number", "receivedType": "string", "message": "…" }] },
"clientClassification": "schema-error"
}
- Validation states:
'ok' | 'invalid-json' | 'schema-mismatch' | 'response-too-large' | 'not-applicable' | 'not-run'. Network failures/aborts/skips use the same envelope without aresponse. source: proxy | upstream(Opus finding):functions/api/_middleware.tssynthesizes responses the server never sent (405UNSUPPORTED_ARCHIVE_PROXY_PATH, 428 security update, 502 upstream failure). Without provenance an agent misdiagnoses the archive server. Proxy-added headers (Cache-Control,Strict-Transport-Security, …) must be labeled as the proxy's.- Include
schemaId+ exact app release SHA (VITE_APP_RELEASE;package.jsonreports0.0.0). Do NOT inline schema source — it lives insrc/lib/schemas/and drifts. Label success as "accepted by app schema", not "exact match" (schemas strip unknown keys). - Do NOT serialize Valibot
issue.pathdirectly — entries contain full ancestor inputs (territary data + bundle blowup). Normalize to JSONPath-style strings. - Optional (Opus): recursive type-only skeleton of the raw body (arrays → first-element shape + length) — diagnostic without territory data.
Redaction — dedicated credential-only scrubber
- Do NOT reuse
sanitizeTelemetry: it redactsgeometry,lat,lon,coordinates,tags,observations,projects(telemetry-redaction.ts:29-61) — exactly the fields a mismatch lives in — and setssaturatedon arrays >100 items, which would replace every string with[REDACTED]. - Write a narrow scrubber: exact runtime token +
Bearer <token>from every captured string, then scan the final serialized JSON; if the token survives, omit the offending field or fall back to a minimal safe bundle. Reuse the credential-pattern idea + canary tests fromsanitizeTelemetryString:157-166. - Optional UI toggle (default on): "redact coordinate values" (type-preserving replacement — schema diagnosis needs
received: "string", not the actual coordinate).
Security invariants (must match the #288 contract)
- Must go through the security-startup gate (automatic via
apiClient/resolveApiRequest). - Same-origin
/apiproxy + credential-revision header; SWNetworkOnlyfor/api/**;cache: 'no-store'(also add it togetIcon— currently the only method missing it — if icons are ever probed). - Identity-bound 401 locking preserved; stop authenticated fan-out after the first 401 (don't repeatedly submit a rejected token; Codex also suggests a candidate-credential pattern so diagnostics can't lock an unlocked archive mid-run).
- Short-circuit remaining work on 429; capture
Retry-Afterif exposed. - If
/projectsfails or is invalid, export that evidence and mark dependent callsskipped— never fan out from untrusted data. - Diagnostics must not bypass reconnect/approval flows for a locked archive. An already-unlocked HTTP archive need not re-prompt.
- Hard caps: project count, total requests, total captured bytes, total bundle bytes, response headers, issues count, per-request and whole-run duration.
100KB × endpoint × projectis NOT bounded on its own. - Strict response-header allowlist (
content-type,content-length,retry-after, rate-limit headers,x-comapeo-*capability headers) — not "serialize everything and sanitize". Never serialize request headers,RequestConfig,Request/Responseobjects, raw Valibot issues, or error stacks. - Capture state lives only in function/component memory; the explicitly downloaded file is the only durable result. Sanitize + length-limit the server label in the filename.
- Confirmation dialog: "This export may contain territory data, precise locations, names, tags, and attachment URLs. Share carefully."
Phasing
- Increment 1 — last-sync report export:
sync.tsalready computes per-resource warnings,ReconciliationCounts, andEndpointSemantics, and throws them away;ArchiveServerDetail.tsxreferences none of it. A "last sync report" panel + export covers unsupported endpoints and per-project detail failures for a fraction of the work. - Increment 2 — raw-body replay diagnostics (this design), for the case structured warnings can't explain: schema mismatch.
- Keep the bundle format close to
scripts/capture-archive-responses.shoutput sotests/fixtures/entries can be extracted from a field bundle.
Related findings (handle separately)
- Upstream
comapeo-cloudhas noGET /projects/:idroute. The app calls it for every project (src/lib/remote-archive.ts:338), producing the recurring warning at:359. Diagnostics should capture this 404, but the known mismatch deserves its own fix rather than being normalized as mysterious server behavior. docs/remote-archive-api-spec.md:41-44says v0.5.0+ moved observations to singular/observation, butarchive-proxy.ts:117allowlists only the plural — if diagnostics should probe the singular path, the proxy allowlist needs extending first.
Alternatives considered (rejected)
- Always-on ring-buffer recorder: memory cost on mobile, misses failures unless pre-enabled.
- MSW/dev-mode only: field users hitting strange servers are not developers.
- DevTools HAR export: manual, includes credentials, not feasible for users.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with handleResponse in src/lib/api-client.ts and the diagnostics entry points named in ArchiveServerDetail.tsx; then read sync.ts, src/lib/remote-archive.ts, functions/api/_middleware.ts, and archive-proxy.ts. Confirm the existing security and proxy contracts before deciding how to phase the work. Done should include an explicit, bounded export with validation results, redaction, cancellation, and the stated security invariants.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design, devtools, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100