Skip to content

Tale v0.5.25

Choose a tag to compare

@larryro larryro released this 14 Sep 04:17
78e7950

0.5.25 is a fix release on the 0.5 line, and this one stays inside the patch promise: no schema migration, no configuration-file change, no new environment variable, and the API contract stays at 1.6.0. It closes the sixth external API evaluation's findings, every one a gap between what the surface does and what it said rather than a wire defect: the OpenAPI document and the reference now say what they were silent on, three refusals carry what a client can branch on, Strict-Transport-Security moves from 180 days to one year on every producer of the header, and the web site's changelog prerenders by size instead of by count. The proxy image changes for the one-year value on the edge's own refusals, and a plain tale deploy does not roll it — see Upgrading. The Known issues from 0.5.20 are unchanged; the proxy verification gap 0.5.24 listed is closed, and this release's own is listed with the rest.

Highlights

The sixth API evaluation pass (#3337)

The sixth external black-box evaluation of the REST and MCP API ran against 0.5.24 (about 700 requests across nine lanes, no backend 5xx; the proxy's access log for the evaluation hour agrees: 1,867 platform requests, none answered 5xx) and reported 0 S1, 0 S2, 4 S3 and about a dozen S4 findings — the first round with no blocker and no connectivity caveat. Every wire fix 0.5.24 promised held, so did all four S1 fixes from the earlier rounds, and the round-E verdict on the slow IPv4 handshake (the evaluating machine's tunnel, not the platform) was reconfirmed with a five-leg control bound to the physical interface. Every finding was second-confirmed from source before a fix was designed; every one that stood was a matter of contract precision, closed at the source:

  • The REST send is text only, and now says so. GET /models lists capabilities.vision and a vision tag as facts about the model, but POST …/messages takes content alone: a data URI pasted there reaches the model as text and is answered as text, settled complete and billed, with nothing on the wire marking it. The field descriptions, the send body and the reference say exactly that, and an image input over REST is recorded as contract debt with its design (an attachments field naming staged uploads, through the same gate the app's composer uses). No content-sniffing refusal was added: a data URI in a text field behaves the same on every text surface, the app included.
  • POST /conversations/sync names its precondition. externalContactId must be the externalId of a contact that already exists — the snapshot links, it never creates — and a snapshot never re-homes a mirrored conversation (409 CONVERSATION_CONTACT_CONFLICT). The body schema describes the field, the operation names CONTACT_NOT_FOUND, CONTACT_AMBIGUOUS and each attachment refusal (ATTACHMENT_NOT_OWNED, another service user's upload in the same organization, is a 403 where the description said 400 "otherwise"), and replyConstraints is described as what a person's Inbox reply may carry, enforced when that reply is written and never against a snapshot.
  • MCP refusals a client can branch on. The MCP page listed INVALID_PARAMS and UNKNOWN_METHOD as string codes; the transport answers those cases as JSON-RPC -32602 and -32601 before any tool runs, and the page now says so. The second confirmation found the hole: a blank name, runId or query passed the advertised schema and reached the engine's own INVALID_PARAMS, so the schemas carry minLength: 1 (and \S for the trimmed query) and a blank is the same -32602 a missing field gets. The developer gate on save_automation, deploy_automation and set_trigger answered {error, hint} with no code; it carries FORBIDDEN_DEVELOPER_SETTINGS, the store's own role refusal. A call the request budget refuses inside a batch is documented: -32000 with data.retryAfterMs in its own slot, HTTP 200, no Retry-After.
  • One year of HSTS. Strict-Transport-Security was 180 days; it is max-age=31536000 on every producer of the header — the platform app, the API doors, the web and docs sites, and the proxy's own edge refusals — still without includeSubDomains or preload, because self-hosted deployments run on varied domains (an apex with plain-HTTP siblings included) and a preload listing is the operator's own submission. The hardening page names the lifetime.
  • Precision in the reference. The browser-session import 403 no longer names an environment variable to a caller who cannot act on it; it points at GET /me and capabilities.deploymentEditor. testsCheckedAt says what null beside a true or false verdict means (a verdict recorded before 0.5.24 kept the time). The reply-cap text says which models carry the 2,048-token thinking floor (the thinking-budget dialect) and what a few-hundred-token cap does on an effort-level reasoning model (the GLM and DeepSeek families): an empty, complete, length, billed reply — so give a reasoning model a few thousand tokens or lower reasoningEffort. Each keyset list's own limit maximum (100 or 200, 500 for task comments) is stated with the loop; the X-Tale-Api-Version sentence names /api/v1 and the webhook doors, and the keyless doors that carry none; the delivery queue is a keyset page under deliveries; the webhook tutorial says upfront that REST has no authoring door (POST /automations answers 405), so the prerequisite is met in the app or over MCP.

The web site's changelog prerenders by size, not by count (#3337)

The prerendered changelog on the web site carried a fixed twelve release bodies. Six consecutive 0.5.x fix releases with 17–24 KB of notes each pushed those twelve to 308 KB, over the 300 KB the prerender suite holds the page to, which turned the nightly E2E run on main red after the 0.5.24 release. The cut is now a byte budget — the newest bodies within 72,000 characters of Markdown, at most twelve, the newest always — computed by one function the page and the suite share, so the server and the first client render agree and hydration matches. At the time of the fix that is four bodies and about 217 KB; every release stays in the stream and the rest mount on hydration from the manifest the bundle already ships, so a visitor sees the same page.

Behaviour changes

  • Strict-Transport-Security on every HTTPS response reads max-age=31536000 (was max-age=15552000): the platform app and the API doors with the platform image, the web and docs sites with theirs, the proxy's own refusals once the proxy is recreated (see Upgrading). Still no includeSubDomains, no preload.
  • An MCP tools/call with a blank name, runId or query (whitespace alone included) is refused at the transport as JSON-RPC -32602, exactly like a missing field, and no tool runs; it used to reach the engine and come back as an INVALID_PARAMS refusal in the result.
  • The MCP developer-gate refusal on save_automation, deploy_automation and set_trigger carries code: "FORBIDDEN_DEVELOPER_SETTINGS" beside error and hint.
  • The browser-session import 403 for an account outside the deployment-editor allowlist reads "Your account is not on the deployment editor allowlist; GET /api/v1/me answers capabilities.deploymentEditor for this key" — the environment variable's name left the sentence (the OpenAPI document still names it where it documents the gate).
  • The web site's changelog, in each language, prerenders only the newest release bodies within the byte budget; the rest render on hydration.

API contract changes

The OpenAPI document stays at 1.6.0: no operation, field, status or error code changes, and the contract fingerprint (operations and schema shapes; descriptions are outside it by design) is unchanged. What moved is what a client reads in the document and what three refusals carry.

Changed

  • Strict-Transport-Security — max-age=31536000 on every HTTPS response of every door (was max-age=15552000).
  • MCP tools/list — name on the automation tools and runId on get_run and cancel_run carry minLength: 1; query on search_catalog carries minLength: 1 and pattern: \S. A blank value answers JSON-RPC -32602 at the transport and no tool runs.
  • MCP save_automation, deploy_automation, set_trigger — the developer-capability refusal carries code: "FORBIDDEN_DEVELOPER_SETTINGS" (was error and hint alone); isError: true as before.
  • POST /browser-sessions/import — the 403 for an account outside the deployment-editor allowlist no longer names TALE_DEPLOYMENT_CONFIG_ADMINS; the code is unchanged.

Documented, unchanged on the wire (en, de, fr): POST /conversations/sync requires an existing contact (404 CONTACT_NOT_FOUND, 409 CONTACT_AMBIGUOUS), never re-homes a mirrored conversation (409 CONVERSATION_CONTACT_CONFLICT), answers 403 ATTACHMENT_NOT_OWNED for another service user's upload, and replyConstraints bounds a person's Inbox reply, never a snapshot; content on both chat sends is text only, and ChatModel.capabilities.vision and tags describe the model, not an input this surface offers; maxOutputTokens bounds a reasoning model's reasoning too, with the 2,048 floor on the thinking-budget dialect alone and no floor on an effort-level model; testsCheckedAt is null beside a verdict recorded before 0.5.24 kept the time; each keyset list's limit maximum is declared per list (100, 200, or 500 for task comments) and a larger value is clamped; X-Tale-Api-Version rides every response from /api/v1 and the webhook doors, and the keyless doors (/api/health, /status, /status.json, /openapi.json) carry none; the delivery queue is a keyset page under deliveries; on the MCP endpoint a schema miss or an unknown tool is -32602 or -32601 with no code, an in-batch budget refusal is -32000 with data.retryAfterMs, and FORBIDDEN_DEVELOPER_SETTINGS is named; the hardening page states the HSTS lifetime; the webhook tutorial states that REST has no authoring door.

Known issues

  • Unchanged from v0.5.20, where each is described in full: the es/co-cc Colombian cédula detector still ships switched off and a locale-agnostic PII toggle still widens national-ID matching to every locale (the PII library is untouched in this range); thinking-block replay on the native Anthropic connector is not done and the live Max-plus-tool-call check is still owed; rag_search embedding calls inside a harness turn are unmetered; the product edit dialog cannot clear a field; the app's skill editor still carries the retired private visibility.
  • The x-tale-pagination extension is a declaration on the OpenAPI document; generated clients that do not read vendor extensions still branch on the two cursor names until cursor is retired.
  • The proxy change was not exercised on a running edge. It is one line — the max-age the entrypoint writes into the edge's own JSON refusals — and services/proxy has no test of its own; the platform's headers, which every other response carries, are covered by the server suites. The 0.5.24 edge rules, which 0.5.24 listed as exercised only in a local container, have served the hosted platform since 2026-09-14 and the sixth evaluation drove them on the wire (BODY_LENGTH_MISMATCH, the uncompressed HEAD length, the dot-segment fold-in), which closes that item.
  • A reply-language directive is a directive: the sixth evaluation measured six German replies out of six where the fifth measured four, but a model may still answer in the prompt's language and nothing on the wire marks a slip.
  • No image input on the REST chat send. A vision model reads an image over REST only on a thread the app continued with an image attachment; the design of an attachments field on the send is recorded as contract debt.
  • No REST door authors or deploys an automation — POST /automations answers 405 by design. Build and deploy in the app, or over the MCP endpoint's save_automation and deploy_automation; the REST key lists, reads, runs and wires triggers.
  • There is no single-file read on /projects/{id}/files: a poller waiting for one file's indexing after retry-indexing walks the folder listing.
  • The app's zip upload of a skill bundle rewrites the bundle and moves updatedAt even when the zip is byte-identical, where PUT /skills/{slug} writes nothing.
  • A tool call the reply cap cut keeps input: {} on the stored tool-call part; the raw text the model emitted is not on the transcript, so the timeline cannot show what was asked.
  • Folder names written before 0.5.24 keep their bytes; a sync engine's hub-path lookup can create an NFC twin beside a legacy NFD folder. No backfill ships.
  • Behind a Docker-published port, every IPv6 client arrives as the bridge gateway's address and shares one per-address rate-limit bucket and one audit address until the daemon runs with ip6tables and the proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.

Migration notes

  • No platform migration and no knowledge-database migration in this range; the schema is 0.5.24's. The task_labels_project_id_name_key constraint 0.5.22 kept for its rolling deploy is still in place; dropping it is a follow-up migration, not part of this release.
  • No configuration-file change and no new environment variable; .env.example is untouched.
  • The proxy image changes (the one-year Strict-Transport-Security on the edge's own JSON refusals). The proxy is in the stop-gated tier: a plain tale deploy leaves a running proxy untouched and names it in a hint, so pass --stop to take the new value (a brief downtime while db, object-store and proxy recreate); until then the edge's own refusals — the dot-segment 404, BODY_LENGTH_MISMATCH, UPSTREAM_UNAVAILABLE — keep the 180-day value while every response the platform answers carries one year. An own-Compose deployment pulls the new tale-proxy tag.
  • A browser that sees the new header pins the origin to HTTPS for a year from that response (it was 180 days); moving a deployment's public origin back to plain HTTP inside that window is refused by browsers that saw it, as before, for longer.
  • The platform, proxy, web (the changelog budget and the shared security headers) and docs (the shared headers and the updated pages in all three languages) images carry source changes. The db, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have no source change in this range. The CLI has no source change either; the release executables are rebuilt at this commit and report 0.5.25.

Upgrading

  • On the 0.5 line (0.5.0 – 0.5.24):

    tale update
    tale deploy --stop

    --stop recreates db, object-store and proxy so the edge's own refusals carry the one-year header (a brief downtime); a plain tale deploy applies everything else and leaves the running proxy on 0.5.24's value. Nothing migrates.

  • Managed deployments move by pinning the CLI and the runtime to this release's commit, preparing a new bundle and applying it with the pinned CLI — see Managed deployments on the CLI install page. As since 0.5.24, the bundle's backend-local phases run under the interpreted CLI (cli/tale.mjs) that the setup-cli action and bun run --filter @tale/cli build produce beside the executable; the executable from the release page has no interpreted bundle beside it and cannot prepare a managed bundle. On a Linux x64 host whose CPU lacks AVX2, pass linux-baseline: 'true' to the setup-cli action so the bundle embeds the baseline executable.

  • New install:

    curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash
    mkdir tale-05 && cd tale-05
    tale init
    tale deploy

    On a CPU without AVX2 the downloaded executable aborts with Illegal instruction; build it from source with bun run build:linux-baseline in tools/cli instead.

What's Changed

  • fix(platform): close the 2026-09-14 API evaluation's sixth-pass findings by @larryro in #3337

Full Changelog: v0.5.24...v0.5.25