Repository navigation
RaySpec 1.9.1 is a patch release on 1.9.0. It fixes audio recording on 1.9.0, closes two gaps in
how trusted proxies are matched, moves one dependency past a published advisory, and adds two
opt-in additions: sentence-sized transcript spans, and a run with no provider key for development
and tests.
If you only upgrade and set nothing new, your deployment keeps working as it did, with three things
to do first: correct a RAYSPEC_TRUSTED_PROXIES entry that names no range (the boot refuses it),
repack a bundle with 1.9.1 (one packed for 1.9.0 is refused), and look at the .well-known
directory of a static mount at the root (it is now served). No platform migration runs.
What changes for you
If your product records audio
- An audio session is marked
completedonce all its tracks are sealed. 1.9.0 left every
session atrecording. A client that treatedrecordingas the only status should accept
completed. - A rejected chunk no longer leaves an empty recording behind. A chunk refused with
409 gap
or413 track_too_largecreated an empty session and track row; now only an accepted chunk 0
creates them. - A recording with a chunk that is not Ogg, or that ffmpeg cannot read, now fails. 1.9.0
stitched the recording up to that chunk and transcribed and played the shortened result as the
whole recording. ffmpeg is now handed Ogg chunks only and is limited to the two demuxers and the
one protocol it needs. - The runtime image carries ffmpeg and ffprobe. An image built from the 1.9.0 tag had neither,
so a product withaudio_inputormedia_playbackdeployed on it and then transcribed and
played nothing. - A warning when a bundle needs ffmpeg and the host has none.
rayspec bundle verifyand
rayspec deploy <file.ray> --dry-runwrite a line starting withwarning: media tools missing
to stderr. The result envelope and the exit code are unchanged.
If you run behind a proxy
- An entry of
RAYSPEC_TRUSTED_PROXIESthat names no range refuses the boot, and the message
names it: an address that does not parse, or a missing or malformed prefix length (10.0.0.0/,
10.0.0.0/8.0,10.0.0.0/33). 1.9.0 read an empty prefix as/0, which trusted every address
of the family. - A trusted-proxy range gives one answer for one address, however it is spelled. The hex
spelling of an IPv4-mapped address (::ffff:a01:203) is now the IPv4 address it carries, like
the dotted spelling, so a broad IPv6 entry no longer makes an IPv4 peer a trusted proxy.
If you serve a frontend
- A static mount at
route: /serves its/.well-known/directory (security.txt,
assetlinks.json,apple-app-site-association). Every other dot path still answers404.
Dependencies
@modelcontextprotocol/sdkmoves to1.31.0, the first version outside GHSA-6qxp-vccf-f47h.
@rayspec/adapter-codexpins the SDK exactly, so an install of 1.9.0 keeps1.29.0: upgrading
is the fix.- The workspace lockfile moves
proxy-addrto2.0.8andsource-map-jsto1.2.2. Neither
changed what a consumer of 1.9.0 installs.
New settings
All are off until you set them.
| Setting | What it does |
|---|---|
span_granularity: sentence on the stt capability of a product document |
One transcript span per sentence instead of one per paragraph, so each extracted claim can cite the sentence that backs it. Values are paragraph (the default) and sentence. The Deepgram adapter honours it. |
RAYSPEC_STT_FAKE_FIXTURES=<directory> |
With STT_PROVIDER=fake, the transcript fixture files (*.json) the fake adapter answers recordings from. Read once, at boot. |
RAYSPEC_STT_FAKE_FALLBACK=fixed |
Answers a recording no fixture matches with a fixed two-sentence transcript. |
RAYSPEC_EXTRACTION_DETERMINISTIC_STAND_IN=true |
Under RAYSPEC_EXTRACTION_MODE=deterministic, lets the deterministic extraction provider answer for an extractor whose config names a real backend. |
The three environment variables let a deployed product run from upload to extracted rows with no
provider key. They are a development and test posture: the boot banner names each one in effect,
and RAYSPEC_HOSTING_POSTURE=managed refuses each of them at boot.
For maintainers, scripts/publish.mjs --publish now runs attached to the terminal, so npm can wait
for a browser approval, and takes --otp <code> for an account with an authenticator app.
Known limitations
New in this release:
- The image carries Debian's full ffmpeg build. It brings 206 Debian packages; the image is
1.75 GB unpacked where it was 1.30 GB, and 572 MB as an archive where it was 402 MB. There is no
second image without it. The runtime gives ffmpeg a time limit and no memory, CPU or concurrency
limit and no sandbox: give the container its own limits. The Debian packages are listed in the
image SBOM and no step scans them for advisories; an ffmpeg security fix reaches an image only
when its pinned version is moved. - Nothing caps the number of sentence spans of a track. With
span_granularity: sentenceit
is the number of sentences the provider's response carries, typically several times the number
of paragraphs. - Span ids differ between the two granularities.
<track>:s<index>counts paragraphs under
one value and sentences under the other, and nothing is migrated when the value changes. Choose
it before a product stores evidence. - Chunk 1 of a new track sent while chunk 0 is still being stored is answered
409 gapwith
next_expected_index: 0, where 1.9.0 waited and answered200. A client that uploads a track's
chunks in parallel resumes from the index the answer names. - Sessions finalized by 1.9.0 keep
recordinguntil a track of theirs is finalized again or
the statements of the upgrade notes are run. Rows left by rejected chunks stay until the two
DELETEs are run, and an empty track left in place keeps its session atrecording. - The
media tools missingwarning looks for the tools and starts nothing, so a tool that is
present but broken is not noticed.
Carried from 1.9.0 and still true:
- The runtime is not a sandbox. Handlers and extensions run inside the serving process and can
read what it holds. Contain untrusted code with a VM or container boundary, its own databases and
host egress rules. - No egress enforcement and no encryption at rest in the runtime. Applications declare the hosts
they call; the host network policy must enforce it. Disks and backups need their own encryption. - A removed member keeps read access until their access token expires (480 seconds by
default). Writes, run starts and administrative actions recheck at once. - The workflow system database is outside row-level security.
- The anthropic, codex and pi backends are not certified for public hosting; the managed posture
refuses them. - The supervisor and the application run as the same OS user. On a managed host the boot refuses
unlessptrace_scopeis 1 or more, the hard core-file limit is 0 and the privileged connections
are passed inline. Running the child as a separate user is not offered yet. - Export refuses an application whose extension provides its own blob backend. An application
with extensions exports after one bundle deploy with 1.9.0 or later. - Restoring a paired backup is a documented operator procedure, not a command.
- A stream ingest route has no body size cap of its own; cap request bodies at the reverse proxy.
- The image's third-party packages are resolved when the image is built, so their versions can
differ from the workspace lockfile; the image SBOM lists what the image holds. - A rebuild of the same commit is not byte-identical; verify a release against its own published
tarballs. - Advisories against three packages (undici, brace-expansion, protobufjs) pinned by the Pi agent
SDK's own shrinkwrap remain open for consumers, 22 in all. A patched Pi SDK exists, and the
adapter cannot take it unchanged, so it stays at0.79.9in this release. The dated scanner
exceptions are renewed and now expire on 2026-12-06. - The bundle contract is revision
1.0.0-rc.2, unchanged in this release. It is frozen by the
Core team; joint ratification with the Cloud team is still open, and a change they ask for would
come as a new contract revision.
Upgrading
From 1.9.0, read
From 1.9.0 to 1.9.1
and the
upgrade notes in the changelog.
In short:
- If you set
RAYSPEC_TRUSTED_PROXIES, correct or remove any entry that names no range before
you upgrade; the boot refuses it. - If you serve a static mount at
route: /, look at what a build placed in its.well-known
directory; it is now served. - If you deploy bundles, repack them with the new CLI; a bundle pins its runtime, and one packed
for 1.9.0 is refused withRAY_RUNTIME_UNSUPPORTED. An extension whosepackage.jsonpins a
@rayspec/*package to exactly1.9.0moves the pin to1.9.1, or to a range that includes
it, before repacking. A snapshot pins its runtime too: an export taken on 1.9.0 is imported
with 1.9.0, and the upgrade follows the import. - Install 1.9.1 and start the deployment as before. No platform migration runs: the chain ends at
0017, as in 1.9.0. - If you run the runtime image, build it again from the 1.9.1 tarballs and the Dockerfile at the
v1.9.1tag; ffmpeg comes from the Dockerfile. - If your product recorded audio on 1.9.0, run the three statements of the upgrade notes once to
remove the empty rows rejected chunks left and to settle the sessions 1.9.0 finalized. The
runtime works without them.
Going back to 1.9.0 needs no restore, because both releases run on the same platform schema; take
span_granularity out of a product document first and deploy the bundle packed for 1.9.0.
From 1.8.x, go straight to 1.9.1 and read
Upgrading to 1.9
first: check Node is 22.21.0 or later and take a backup, because that first boot runs six additive
platform migrations and going back to 1.8.x must start from the backup.
Verifying this release
The 34 packages on npm are the tarballs attached here, byte for byte. From a checkout of the tag
v1.9.1 with pnpm install --frozen-lockfile && pnpm build run, and the attached .tgz files in
./tarballs:
node scripts/release-manifest.mjs verify --manifest release-manifest.json --tarballs ./tarballs
node scripts/release-identity.mjs --verify --tarballs ./tarballs --manifest rayspec-release-identity.json
npm view rayspec@1.9.1 dist.integrity # the integrity the manifest lists for rayspecReleasing → Verifying a release
describes each command. Like 1.9.0, this release was published from the owner's machine, from the
tarballs the release workflow built and tested at the tag:
- The release manifest is not signed. It shows which bytes the release build made, not who
published them. - The packages carry no npm provenance attestation.
- The runtime image is not pushed to a registry, and its archive is not attached. The release
build built and tested it; the digest the manifest names records that build and cannot be
pulled. Build the image from the attached tarballs as
The runtime image → Getting the image
shows.