You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
[1.3.2] - 2026-07-14
Added
Opt-in readonly route handlers. A { kind: route } handler may now declare readonly: true. A handler-kind route is gated on the sensitive store:write
permission by default (the platform cannot statically prove a handler only reads, so
it fail-closes to the stronger gate); readonly: true is the author's assertion that
the handler only reads product stores, so its route is gated on store:read instead
— letting a read-scoped credential (for example an ingest-only API key) reach a
read-only route. It is an authorization gate / author assertion, not a runtime
write restriction. An absent or false flag parses byte-identically to before, so
every existing spec, fixture, and golden is unchanged.
A tenant-scoped session reprocess endpoint.POST /v1/sessions/{id}/reprocess
(store:write, strictly tenant-scoped) re-drives a session's declared
finalized-session workflow as a fresh durable run under a distinct idempotency
key — the operational recovery path for re-running extraction after a fix or
unsticking a stuck session, without manual database surgery (simply re-emitting the
finalized event deduplicates to the original run and does nothing). It is wired for
audio products; a deployment with no reprocessor wired answers 501, and a foreign
or absent session id returns 404 with zero enqueue.
Opt-in reuse of a machine claude login for the Anthropic backend. Setting RAYSPEC_ANTHROPIC_REUSE_LOGIN=true lets the Anthropic subscription backend boot
with no CLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_API_KEY in the server environment,
reusing a claude login the operator has seeded into the per-tenant config directory
under RAYSPEC_ANTHROPIC_CONFIG_ROOT (still required). A loud boot banner announces
the mode. Honest caveats: the boot cannot verify any per-tenant directory is actually
seeded, so an unseeded tenant boots clean and fails only at first run; seeding the
login is a manual operator step; and if a token or key is also present in the
environment it wins over the seeded login (the boot warns). Without the flag, boot
behaviour is byte-identical (fail-closed when no credential is present). Documented in docs/concepts.md and .env.example.
Fixed
Store enum whitelists are now enforced on the low-level escape-hatch handler
write path too. A text column's declared enum whitelist was already enforced on
the HTTP create/update route and the workflow store.write value path, but a
custom handler writing directly through the HandlerDb facade was not checked. It is
now rejected fail-closed against a table-identity whitelist registry (a non-member
value — including a non-string scalar — is refused; the failure names the store and
column only, never the offending value), so all three write surfaces agree. This closes the "the facade is not enum-checked" residual noted in 1.3.1.
Every sealed track of a multi-track audio session is transcribed. The
session-finalized event fires as soon as one track seals, but a sibling track could
still be uploading at that instant; the transcribe node re-read only the completed
tracks and finished, permanently dropping any track that sealed afterward. The
durable transcribe node now waits (bounded, with real retry backoff) for all tracks
to seal before transcribing, so every sealed track is transcribed under a staggered
or concurrent finalize. The finalize emit stays unconditional, so a session-scoped
idempotency key still deduplicates to exactly one durable run (never zero). Honest
bound: once the wait elapses the run proceeds with whatever sealed and logs loudly, so
an abandoned upload can never stall the run forever.
The migration generator emits foreign keys after the tables they reference. A
store that referenced a later-declared store emitted its REFERENCES <parent> before
that parent's CREATE TABLE, failing at apply (42P01 relation does not exist) while doctor/plan still reported ok. A stable topological sort now orders every CREATE TABLE ahead of the foreign keys that reference it (an already-ordered spec
stays byte-identical, so committed goldens are unchanged). A genuine foreign-key cycle is now a blocking fk_cycle error at doctor/plan time (rather than a
throw at apply); a merely out-of-order forward reference is a non-blocking fk_forward_reference advisory.
An unsatisfiable Range on a static frontend mount now returns 416. The
underlying static server mishandled an unsatisfiable byte range — a closed range
beyond end-of-file yielded a malformed 0-byte 206, and an open one surfaced as a 500. An additive guard now returns RFC-7233 416 with Content-Range: bytes */<size> for an unsatisfiable range (a start at/after EOF —
open or closed — or a reversed range), under GET and every write verb. HEAD/ OPTIONS stay 200 full-size (never 416), every satisfiable/clamped 206 is
unchanged, and the fail-closed dotfile/traversal/symlink guard still returns 404
under a Range request. This corrects the 1.3.1 note that said an unsatisfiable
range returns 500.
API-key minting is exactly-once under a concurrent Idempotency-Key. The mint
applied idempotency as a non-atomic find-then-act, so two concurrent requests with the
same key could each mint a distinct usable key with the loser's key left stranded
(usable but never replayable). The mint is now retrofitted onto the atomic
reserve-before-execute primitive: a concurrent loser replays the winner's redacted
mint metadata (200, plaintext omitted — a caller that lost the original 201 must
mint a new key) or gets a 409 while the mint is in progress, and exactly one key is
ever minted. The
no-idempotency-key path is behaviourally unchanged, and the plaintext secret is still
never stored (the kill-trigger closure is preserved). Honest residual (documented in
code): exactly-once except a rare ambiguous mint-commit window.
A user-dismissed collection row is preserved across a rebuild. The collections
materializer re-stamped dismissed: false on every rebuild, so re-extracting (or a
reprocess) would resurrect a user-dismissed artifact. A dismissed row is now spared
unconditionally — reconciliation never deletes it and the upsert loop skips it —
mirroring the existing human-edit preservation, independent of preserve_human_edits.
An extension-pack agent that selects a backend no base agent uses now boots. The
env-driven backend factory derived its backend set from the pre-merge base document,
so a backend introduced only by a pack agent was never built and the boot failed
closed on it — including a backend spec whose only agents come from a pack (zero base agents:). The composition root now builds any backend a merged agent selects (via the
same fail-closed path), while a base-only deploy stays byte-identical.
Security
The API error envelope strips details structurally for non-input-echo codes.
The "a bare 401/404 leaks no details" invariant moved from a per-call-site
convention to a structural guard at the single envelope chokepoint every non-2xx
response flows through: an allowlist keeps details only for the codes whose details
echo caller-supplied context (VALIDATION_ERROR, FORBIDDEN, RATE_LIMITED, GATEWAY_TIMEOUT) and drops it for every other code regardless of what a caller
passes. Behaviour-preserving — no code outside the allowlist carries a details
payload today, so no current response changes — but the guarantee is now enforced by
construction rather than by convention.
Documentation
Updated the spec reference, concepts, and the authoring skill for the readonly
route-handler flag, the session reprocess endpoint, the all-three-surfaces enum
enforcement (the 1.3.1 handler-facade residual is closed), the static-mount 416
correction (superseding the 1.3.1 "returns 500" note), and the RAYSPEC_ANTHROPIC_REUSE_LOGIN reuse-login option.