SOW-0001 Chunk 12: read-side REST endpoints (sessions, detail, logs, stats)#14
Merged
Merged
Conversation
…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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the read-side HTTP surface of
ai-viewer-serveon top of the canonical SQLite store (internal/presenter):GET /api/sessions— filtered, keyset(seek)-paginated session listGET /api/sessions/{id}— detail: turns, ops, payload refs, child-session linksGET /api/sessions/{id}/logs— severity-filtered, keyset-paginated log entriesGET /api/stats— cross-session aggregates (by model/tool/agent/status/source) over the filtered setKey properties
?placeholder (gosec verified, all#nosecsuppressions audited).400.400.504, DB-unavailable →503, HEAD parity (RFC 9110),405method gating,404vs200-empty,request_idin every error/panic log; recover middleware writes the 500 envelope only when the response has not already started.internal/ingestimport; 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 passinternal/presentercoverage 91.8% (≥90% gate)golangci-lint run— 0 issuesgosec ./...— 0 issues;govulncheck ./...— 0 reachablegofmt/goimports/go vet— cleanHandler()middleware chain on a real temp SQLite DB (filters, pagination, cursor tamper/mismatch, control-char, 404/405/HEAD/504, recover partial-write)