Repository navigation
Conversation
… auth
- events.py: EventBroker (in-process per-conversation pub/sub) +
append_message service. The message log IS the event source: SSE resume
(Last-Event-ID = turn) replays from the store, so in-process fan-out is
best-effort and durability lives in the log.
- app.py: create_app() factory; POST /v1/conversations/{id}/messages
(201 / 413 message_too_large / 422 server_stamped_field), GET snapshot,
GET events (SSE, Last-Event-ID resume), optional bearer auth
(SENJA_API_TOKEN), env-driven store selection at startup.
- tests/api/: API contract tests run over BOTH stores via the parity
message_store fixture — includes named tests
test_server_stamped_fields_reject_client_values and the 413 byte-cap.
- tests/test_static_checks.py: test_no_surrealql_outside_db_dir (P4).
- pyproject: add httpx to dev deps (ASGI transport tests).
- docs/sprints/SPRINT-02-api-slice.md: sprint brief.
Known gap (tracked): threading intent enqueue is post-transaction;
test_append_fails_atomically stays open for Sprint 03.
…id response field Declare response_model=Message on the POST decorator and drop the union return annotation; returning a JSONResponse instance for the 413/422 paths bypasses the response model legally. Collection error killed the whole suite (exit 2, 1.41s) before any test ran.
…treams + pytest-timeout httpx ASGITransport runs the ASGI app to COMPLETION before returning the response — an unbounded SSE stream therefore hangs the request forever (hung CI run 28667163122 for 25+ min; read-timeout guard never engaged because the block is inside __aenter__). - app.py: GET /events gains optional ?until_turn=N — server closes the stream once the given turn has been sent. Bounded catch-up read; also makes the stream deterministic under buffering transports. - tests: SSE tests use until_turn; live fan-out test starts the reader as a task, then posts, so the publish lands while the stream is open. - pyproject: pytest-timeout dev dep + timeout=120 — any future hang is a loud failure, never a zombie job.
… (P4 purity gate)
…_db_dir (P4 purity gate)
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.
Sprint 02 — API slice (SDAI-CHAT-003 Block 6 over HTTP)
Wires FastAPI to the Sprint-01-proven adapters so the 003 vertical slice runs end-to-end: turn appended → persisted atomically → served to UI and harness.
What's in
events.py(was pseudocode):EventBroker(in-process per-conversation pub/sub) +append_messageservice. Design: the message log IS the event source — SSE event id = turn; resume replays the gap from the store, then goes live. No dupes / no gaps follows from turn monotonicity, not broker bookkeeping.app.py(was bare healthz):create_app()factory with env-driven store selection (SURREAL_URL → surreal, else memory);POST /v1/conversations/{id}/messages— 201,413 message_too_large(byte cap, never truncate),422 server_stamped_field(003 Principle 3)GET /v1/conversations/{id}/snapshot— immutable harness readGET /v1/conversations/{id}/events— SSE withLast-Event-IDresumeSENJA_API_TOKEN, healthz exempt)tests/api/test_api_slice.py— runs over BOTH stores via the paritymessage_storefixture. Includes named testtest_server_stamped_fields_reject_client_values, the multibyte 413 boundary, SSE resume + live fan-out, auth.tests/test_static_checks.py— named testtest_no_surrealql_outside_db_dir(003 Principle 4 gate).pyproject.toml—httpxadded to dev deps (ASGI-transport tests).docs/sprints/SPRINT-02-api-slice.md— brief, incl. first clickable localhost instructions (uvicorn app:app→/docs).Known gap (tracked, deliberate)
Threading intent enqueue happens after the append transaction, not inside it —
test_append_fails_atomicallystays open until enqueue folds intoSQL_APPEND_MESSAGEor a sweeper reconciles. Enqueue is idempotent, so replay safety already holds.Verdict process
Same as Sprint 01: CI on this PR is the referee; user pastes logs when red; merge only on green.