Skip to content

SOW-0001 Chunk 12: read-side REST endpoints (sessions, detail, logs, stats)#14

Merged
ktsaou merged 1 commit into
masterfrom
sow-0001-chunk-12-rest-endpoints
May 28, 2026
Merged

SOW-0001 Chunk 12: read-side REST endpoints (sessions, detail, logs, stats)#14
ktsaou merged 1 commit into
masterfrom
sow-0001-chunk-12-rest-endpoints

Conversation

@ktsaou
Copy link
Copy Markdown
Member

@ktsaou ktsaou commented May 28, 2026

Summary

Implements the read-side HTTP surface of ai-viewer-serve on top of the canonical SQLite store (internal/presenter):

  • GET /api/sessions — filtered, keyset(seek)-paginated session list
  • GET /api/sessions/{id} — detail: turns, ops, payload refs, child-session links
  • GET /api/sessions/{id}/logs — severity-filtered, keyset-paginated log entries
  • GET /api/stats — cross-session aggregates (by model/tool/agent/status/source) over the filtered set

Key properties

  • SQL injection-proof: parameterized SQL only; every user value bound as a ? placeholder (gosec verified, all #nosec suppressions audited).
  • Keyset pagination: opaque base64url cursor bound byte-for-byte to a canonical length-prefixed fingerprint of the full result-defining query (no hash → distinct queries can never collide). Explicit sort/order guards (sessions) and int64-id validation (logs) reject tampered/mismatched cursors with 400.
  • Strict input: control-char rejection on raw query/path values before trimming; present-but-empty array/severity → 400.
  • Robustness: 30s query timeout → 504, DB-unavailable → 503, HEAD parity (RFC 9110), 405 method gating, 404 vs 200-empty, request_id in every error/panic log; recover middleware writes the 500 envelope only when the response has not already started.
  • Read-only: no DB writes, no internal/ingest import; binds localhost.

Specs (presenter.md, rest-api.md) updated in lockstep. Converged across 8 review iterations (codex + glm + minimax) with zero remaining actionable findings.

Test plan

  • go test -race -count=1 ./... — all 10 packages pass
  • internal/presenter coverage 91.8% (≥90% gate)
  • golangci-lint run — 0 issues
  • gosec ./... — 0 issues; govulncheck ./... — 0 reachable
  • gofmt/goimports/go vet — clean
  • httptest coverage of the full Handler() middleware chain on a real temp SQLite DB (filters, pagination, cursor tamper/mismatch, control-char, 404/405/HEAD/504, recover partial-write)

…stats)

Implements the read-side HTTP surface on top of the canonical SQLite store:

- GET /api/sessions        filtered, keyset(seek)-paginated session list
- GET /api/sessions/{id}   detail: turns, ops, payload refs, child links
- GET /api/sessions/{id}/logs  severity-filtered, keyset-paginated log entries
- GET /api/stats           cross-session aggregates over the filtered set

Cross-cutting properties:
- Parameterized SQL only; every user value bound as a ? placeholder.
- Keyset pagination via an opaque base64url cursor bound byte-for-byte to a
  canonical length-prefixed fingerprint of the full result-defining query
  (no hash, so distinct queries can never collide); explicit sort/order and
  (logs) int64-id guards reject tampered/mismatched cursors with 400.
- Control-char rejection on raw query/path values before trimming.
- 30s query timeout -> 504; DB-unavailable -> 503; HEAD parity; 405 gating;
  404 vs 200-empty; request_id in every error/panic log.
- recover middleware writes the 500 envelope only when the response has not
  already started.

Read-only: no DB writes, no internal/ingest import. Specs (presenter.md,
rest-api.md) updated in lockstep. internal/presenter coverage 91.8%.
@ktsaou ktsaou merged commit 90261ee into master May 28, 2026
4 checks passed
@ktsaou ktsaou deleted the sow-0001-chunk-12-rest-endpoints branch May 28, 2026 10:54
ktsaou added a commit that referenced this pull request May 30, 2026
…, EndTs

Round-3 fixes completing the partially-fixed exec/patch enrichment and
web_search pairing plus two correctness gaps, all verified against the
real ~/.codex wire shapes.

- exec_command_end exit_code is now authoritative for op status in BOTH
  orders: exec-first applies at finalize; output-first emits a correcting
  OpFinalized(failed,command_failed) via the finalizedOps lookup (a
  non-zero exit no longer leaves a failed command marked completed).
- patch_apply_end is now order-independent (finalizedOps path) and merges
  patch_success/patch_status into op Extras; success=false -> failed.
- exec_duration_ms is now emitted (real duration is {secs,nanos} ->
  secs*1000 + nanos/1e6).
- web_search pairing uses a per-turn FIFO queue of open searches (oldest
  pairs with each web_search_end) so interleaved searches don't mis-pair;
  the end event's action object is now decoded and emitted alongside query.
- NativeID is taken from the authoritative session_meta.payload.id (the
  UUID parent_thread_id/forked_from_id reference); the filename UUID is
  only a fallback.
- old-format turn_context-only sessions now finalize their EOF turn at the
  turn's last-activity timestamp (deterministic), not the file mtime, so
  the golden is stable across runs; the new-format stale crash finalize
  still uses the stale mtime.

New fixtures l_exec_failed / n_patch_apply / m_multi_web_search /
o_payload_id_filename + regenerated f_exec_truncated / b_old_turncontext /
k_web_search, each line-checked against the spec and byte-identical across
repeated -update-golden runs. Spec pinned the order-independence + the
{secs,nanos} and {patch_success,patch_status} shapes (rules #14/#16/#23).
Gates green: golangci(0)/gosec(0)/vet; race 13/13; codex coverage 92.6%;
FuzzParseLine 0 crashes; secret + AI-attribution scans clean.
ktsaou added a commit that referenced this pull request May 30, 2026
…0022)

adapter-codex.md edge #14 promised that two rollout files sharing a
session_meta.payload.id become separate sessions keyed on
native_id+basename. The v1 adapter uses payload.id as the authoritative
NativeID and the ingester upserts sessions on (source_id, native_id), so
same-id files collapse into one session. This is an unobserved edge (0 of
2,566 modern files) and disambiguation needs cross-file collision
detection the per-file adapter lacks. Align the spec to the v1 reality and
file SOW-0022 to implement the basename-disambiguation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant