Skip to content

feat(charon): add GET /chain/{id} and split entry call by store flag#87

Merged
elevran merged 3 commits into
mainfrom
feat/get-chain-endpoint
Jul 9, 2026
Merged

feat(charon): add GET /chain/{id} and split entry call by store flag#87
elevran merged 3 commits into
mainfrom
feat/get-chain-endpoint

Conversation

@elevran

@elevran elevran commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Adds a read-only chain-fetch endpoint so the proxy can rehydrate context for store:false turns without committing the current turn's request blob. Prior behaviour called POST /staging on every turn and only skipped the response-side store, which left a request-blob staging record behind when the response was discarded.

What changes

  • Server — new HandleGetChain for GET /chain/{id} returning {turns:[…]} root-first; no staging record opened.
  • Client — new GetChain(ctx,id,tenantKey) method + Backend interface extension.
  • Proxy — new Handler.hydrateContext helper that picks the entry call based on ShouldStore():
    • store:true → POST /staging[?prev=…] (commits request blob, returns staging_id)
    • store:false → GET /chain/{prevID}, or no Charon call at all on the first turn

All three proxy paths (HandleCreate, handleStream, wsTurn) funnel through the helper so the entry decision lives in one place. The atomic POST /responses endpoint is unchanged and remains available for clients that prefer one-shot single-turn writes.

Wire surface after change

store stream Entry Persistence
false either GET /chain/{prev} (or none) none
true false POST /staging PUT /chunks/0 + PUT /complete
true true POST /staging PUT /chunks/{k} flushes + PUT /complete

Tests

  • pkg/charon: TestClientGetChain{NotFound,IsReadOnly}
  • internal/server: TestGetChain{UnknownID,DoesNotCommit}
  • cmd/proxy: handler_test additions for store:false continuation, plus a new backend_routing_test.go that wraps the Charon mux in a recording middleware and pins, for every (stream × store × first-turn|continuation) combination, exactly which Charon endpoint the proxy hit. These pin the wire pattern, not just the response shape.

make presubmit is green.

Add a read-only chain-fetch endpoint (GET /chain/{id}) and a corresponding
GetChain client method so the proxy can rehydrate context without opening
a staging record or committing the current turn's request blob.

The proxy now picks its Charon entry call based on the request's store
value:

  store:true  -> POST /staging[?prev=...]   (commits request blob,
                                             returns staging_id)
  store:false -> GET /chain/{prevID}        (read-only, no commit)
                (or no Charon call at all on the first turn)

This makes store:false turns leave zero residue in Charon: no staging
record, no per-chunk PUTs, no Complete. The previous implementation
called POST /staging unconditionally and only skipped the response-side
store call, leaving a request-blob staging record behind even when the
response was discarded.

All three proxy paths (HandleCreate, handleStream, wsTurn) funnel
through a new Handler.hydrateContext helper so the entry-call decision
is made in one place. The /responses atomic-write endpoint is unchanged
and remains available for clients that prefer one-shot single-turn
writes.

Tests:
  - server: TestGetChain{UnknownID,DoesNotCommit}
  - client: TestClientGetChain{NotFound,IsReadOnly}
  - proxy:  handler_test additions for store:false continuation
            and a new backend_routing_test.go that wraps the Charon
            mux in a recording middleware and pins, per
            (stream, store, first-turn|continuation) combination,
            which endpoint the proxy chose.

@elevran elevran left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-motivated change. The hydrateContext helper that all three proxy paths funnel through is the right shape, and the routing tests that pin the wire pattern are a good addition. A few minor issues below.

Comment thread cmd/proxy/handler_test.go
Comment thread cmd/proxy/backend_routing_test.go Outdated
Comment thread internal/server/handlers.go
elevran added 2 commits July 9, 2026 15:41
- Rename TestStoreFalseContinuation -> TestStoreTrueContinuation;
  the body was already a store:true->store:true continuation. The
  store:false->store:true wire pattern is pinned separately in
  backend_routing_test.go.

- backend_routing_test.go: drop dead stagingHits/chainHits locals and
  the json.RawMessage import-retention trick; inline hitsContaining
  into the asserts.

- handlers.go HandleGetChain docstring: drop the stale claim that
  buffered store:true turns use this endpoint (they go through
  POST /staging, not /chain).
- actions/checkout@v4 -> @v5 (Node 24)
- actions/setup-go@v5 -> @v6 (Node 24)
- golangci/golangci-lint-action@v7 -> @v9 (Node 24; golangci-lint v2.12.2 already satisfies v9's >=v2.1.0 requirement)

Drops the 'Node.js 20 is deprecated' annotations that started showing up on PR runs.
@elevran elevran merged commit 4b858a6 into main Jul 9, 2026
6 checks passed
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