Skip to content

RaySpec v1.9.1

Latest

Choose a tag to compare

@iloveautomation iloveautomation released this 07 Oct 09:42
665ac9e

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 completed once all its tracks are sealed. 1.9.0 left every
    session at recording. A client that treated recording as the only status should accept
    completed.
  • A rejected chunk no longer leaves an empty recording behind. A chunk refused with 409 gap
    or 413 track_too_large created 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 with audio_input or media_playback deployed on it and then transcribed and
    played nothing.
  • A warning when a bundle needs ffmpeg and the host has none. rayspec bundle verify and
    rayspec deploy <file.ray> --dry-run write a line starting with warning: 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_PROXIES that 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 answers 404.

Dependencies

  • @modelcontextprotocol/sdk moves to 1.31.0, the first version outside GHSA-6qxp-vccf-f47h.
    @rayspec/adapter-codex pins the SDK exactly, so an install of 1.9.0 keeps 1.29.0: upgrading
    is the fix.
  • The workspace lockfile moves proxy-addr to 2.0.8 and source-map-js to 1.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: sentence it
    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 gap with
    next_expected_index: 0, where 1.9.0 waited and answered 200. A client that uploads a track's
    chunks in parallel resumes from the index the answer names.
  • Sessions finalized by 1.9.0 keep recording until 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 at recording.
  • The media tools missing warning 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
    unless ptrace_scope is 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 at 0.79.9 in 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:

  1. If you set RAYSPEC_TRUSTED_PROXIES, correct or remove any entry that names no range before
    you upgrade; the boot refuses it.
  2. If you serve a static mount at route: /, look at what a build placed in its .well-known
    directory; it is now served.
  3. If you deploy bundles, repack them with the new CLI; a bundle pins its runtime, and one packed
    for 1.9.0 is refused with RAY_RUNTIME_UNSUPPORTED. An extension whose package.json pins a
    @rayspec/* package to exactly 1.9.0 moves the pin to 1.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.
  4. Install 1.9.1 and start the deployment as before. No platform migration runs: the chain ends at
    0017, as in 1.9.0.
  5. If you run the runtime image, build it again from the 1.9.1 tarballs and the Dockerfile at the
    v1.9.1 tag; ffmpeg comes from the Dockerfile.
  6. 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 rayspec

Releasing → 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.