Skip to content

Tale v0.5.28

Choose a tag to compare

@larryro larryro released this 15 Sep 07:28
2c7395b

0.5.28 is a small patch on the 0.5 line. Two changes since 0.5.27: a broken OneDrive or Google Drive folder sync is now visible in Documents and its owner is told once, and the ninth external API evaluation's findings are closed at their root. It ships one forward-only migration on the application database, an API contract that moves from 1.11.0 to 1.12.0 — additive, plus one correction that makes the document valid to a strict OpenAPI 3.0 client — and nothing else that changes shape: no environment variable, no configuration file, no image in the stop-gated tier. The upgrade is tale update followed by a plain tale deploy.

Highlights

A broken cloud sync is visible, and its owner is told once (#3360)

A synced OneDrive or Google Drive folder whose sync had stopped working was silent. The hub listing decorated only active sync configs, so a config in error lost its (synced) label and read as a plain folder. A dead grant — the owner's refresh token revoked, which only invalid_grant, interaction_required, consent_required and login_required cause — froze the mirror at its last good run; the fifteen-minute scan retried forever and nobody was told. The one signal was the connect dialog, if the owner happened to open Upload → OneDrive.

Now the config row remembers its failure episode. The folder's Source cell carries a Sync failed or Reconnect needed badge that opens the cause, when the failures began and whose account the sync runs under — with the reconnect button for that member, and for everyone else who to ask or the option to take the sync over with a new sync import. The owner also gets one bell row and one actionable email per episode: at once for a dead grant, which only reconnecting fixes, and after an hour for anything else, so a vendor blip that self-heals on the next tick never writes one. A run that reaches the source again clears the episode and marks the row read. Sync runs now emit realtime hints, so an open Documents page shows all of this — and a healthy run's new files — without a reload.

A Google Drive folder also reads Google Drive (synced) rather than the hardcoded OneDrive (synced).

The ninth API evaluation pass (#3361)

The ninth external black-box evaluation of the REST and MCP API ran against 0.5.27 and contract 1.11.0 — about 2,900 requests across ten lanes, with no 5xx, no data loss, no cross-tenant leak and no auth bypass. Every reported finding was re-verified before anything changed, and that pass killed two of them: a retrieval "wrong citation" claim could not be reproduced in two controlled runs, and "robots.txt wildcards are not implemented" was overstated — * and prefix rules do work, and only the $ end-anchor fails open. Neither is claimed as fixed here.

Five findings were real and are closed: the crawler forwarded the deployment's Sentry trace headers to every site it visited and introduced itself as a bare node; MessagePart's discriminator and one 3.1-style nullable type list made the served openapi.json unusable to a strict 3.0 validator; and a deleted conversation teardown could be undone by the next content snapshot, with nothing on the crash-recovery receipt to say a teardown had landed. What round I leaves for later is recorded in the contract debt ledger and listed under Known issues.

Behaviour changes

  • A synced folder or file whose sync is failing keeps its (synced) decoration and shows a Sync failed or Reconnect needed badge in the Source column, which opens a dialog with the cause, the failing-since time, the account the sync runs under and — for that member — the reconnect button. The column is widened to fit it.
  • The member who set up a sync receives a cloud_sync_failed bell row and actionable email, once per failure episode: immediately when their Microsoft 365 or Google Drive connection has expired, after an hour for any other cause, and again only if a retryable failure escalates to an expired connection. A successful run clears the episode and marks the row read. The bell and email links open the listing that holds the synced item.
  • A sync run emits realtime hints for the documents it creates, refreshes or prunes and for the folders it flips, creates or reaps, so an open Documents page updates itself instead of waiting out its five-minute cache.
  • A Google Drive synced folder is labelled Google Drive (synced).
  • The crawler identifies itself on both fetch legs — the in-process probe and the sandboxed render worker — as TaleBot/<version> (+https://docs.tale.dev/platform/knowledge/crawling), so a site owner can address it by name in robots.txt (User-agent: TaleBot) and tell it apart from a scraper in their logs.
  • The backend stamps no sentry-trace or baggage header onto any outgoing request.
  • A content snapshot for a conversation mirror the source has torn down answers 409 CONVERSATION_CLOSED at any version instead of reopening the thread with its messages; mirror the source conversation under a new externalId to start again.

API contract changes

The OpenAPI document moves from 1.11.0 to 1.12.0 (dated 2026-09-15). The surface is unchanged at 80 paths and 127 operations; the schema count rises from 51 to 58 as the message parts become named schemas. The Error.code enum keeps its 149 values — CONVERSATION_CLOSED already existed and is now reached by a second path.

Added

  • Seven named part schemas — TextPart, ReasoningPart, AttachmentPart, ToolCallPart, ToolResultPart, ApprovalPart, HumanInputPart — behind MessagePart's type discriminator with an explicit mapping. A generated client gets a class per kind; the vocabulary is still additive, so an unknown kind is rendered as opaque rather than failing the message.
  • sourceDeleted and status on the GET /api/v1/conversations/sync receipt: whether the source tore the mirror down, and the Inbox conversation's status (closed once torn down; null only when the Inbox row itself is gone). An engine resuming from a crash reads sourceDeleted before pushing its next snapshot.

Changed

  • POST /api/v1/conversations/sync — a content snapshot onto a torn-down mirror answers 409 CONVERSATION_CLOSED at any version. The teardown is final; it is not lifted by restoring the contact.
  • recipientId on GET /api/v1/notifications/sync declares nullable: true instead of a type: ["string", "null"] list. It was the one site that made the whole document invalid to an OpenAPI 3.0 validator, and crashed at least one; the other 165 nullable properties already declared it this way. A guard now fails the build on any 3.1 type list, and a second guard checks that every discriminator mapping resolves to a schema whose required type is that kind alone.

Documented, unchanged on the wire (en, de, fr): a send that is still queued is visible only on the generation poll — GET …/threads/{id}/messages does not list the turn yet and …/messages/{messageId} can answer 404 for the id the send named, until a worker opens it; a webhook delivery the deployed inputs schema refuses is answered 400 AUTOMATION_INPUT_INVALID to the sender and moves none of the trigger's health stamps, so a binding refusing every delivery reads the same as one never called — verify deliveries from the sender's side; and the crawler's identity string.

Security

  • The crawler no longer carries the deployment's Sentry trace headers to third-party sites (#3361). With SENTRY_DSN set, the SDK stamped sentry-trace and baggage — carrying the release, the environment and the Sentry public ingest key — onto every outgoing fetch, tracing sampled or not, so every site the crawler visited received them. The backend now sets tracePropagationTargets: []; nothing outbound is ours to trace, and a wire-level test asserts a plain outgoing request carries neither header. A deployment that crawled third-party sites on an earlier version with SENTRY_DSN set should assume its DSN public key, release and environment reached those hosts; that key authorises event ingest into the Sentry project, so rotate the DSN if unsolicited events into it would be a problem.
  • A torn-down conversation mirror stays down (#3361). Following the documented crash-recovery path could resurrect a conversation the source had deleted, along with its messages, because a content snapshot reopened the closed thread and nothing on the receipt said a teardown had landed. That snapshot now answers 409, and the receipt reports the teardown.
  • The crawler names itself, so a site owner can refuse or throttle it specifically rather than blocking an anonymous client.

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; 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.
  • Cloud sync, left for later: there is no Sync now action — the cadence is the fifteen-minute scan, so a reconnected account waits for the next run. A config whose owner leaves the organization is still deactivated silently by a different door, and a source-deleted item is still a status stamp with no bell.
  • Documents indexed before 0.5.27 keep one vector per repeated passage until they are re-indexed; the content hash is unchanged, so only an explicit retry-indexing (or a content change) re-embeds them. A site that has not been scanned since 0.5.27 has no stored robots rules until its next scan.
  • The rail's navigation memory has had part of its manual round: the R5 round drove six EN/DE/FR desktop and phone cases covering parts of NAV-F16–NAV-F19; the remaining section, the second-account cases and NAV-B6–NAV-B9 are still unrun.
  • The docs chrome is duplicated between the documentation site and the design-system site rather than shared.
  • A reply-language directive is a directive: 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.
  • 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 still not on the transcript.
  • 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.
  • Recorded as contract debt by the ninth evaluation, each with its design in the ledger: a queued send is invisible on the message list until a worker opens it (the generation poll is its only view); a webhook delivery the deployed inputs schema refuses moves no trigger stamp, so the binding reads as never called; the MCP run_deployed tool keys its idempotency apart from start_run and REST, so a key shared across them hard-fails instead of answering the documented duplicate marker; robots.txt $ end-anchors and Allow: lines are not honoured (prefix and * rules are), and a page is fetched three to four times per scan; a cancelled run answers trace: null and effects: null where a failed run answers both.
  • Still open from the seventh and eighth evaluations: a run carries no usage or cost; approvals and asks have no REST twins; a task cannot be archived or deleted over REST; a webhook bind does not say whether the deployed inputs schema admits a delivery; an exhausted repeatUntil is only a trace note; Website carries no scanStartedAt and the crawler has no page cap, path filter or stop verb of the caller's; website search has no dense leg and its substring fallback stamps score: 0 silently; no Idempotency-Key on the task start; no queue position on a queued send; a corrupt Office document still fails as indexer_error and is retried five times where a PDF lands malformed; no /.well-known/security.txt; no changelog feed on tale.dev; no SDK, collection or per-code table beyond the Error.code enum; GET /notifications rows carry type as a free string and nothing pushes them to a machine caller; a skill keeps no version history on the machine door; the per-task circuit breaker is not built; the messages a conversation snapshot applied are readable only in the app.
  • Two of 0.5.27's known issues are closed: the edge's Expect rule has been exercised on a running deployment since the hosted platform moved to 0.5.27, and ui.tale.dev is live.

Migration notes

  • One migration, forward-only and rolling-safe. 0105 cloud_sync_failure_episode on the application database, applied by the backend at boot inside the advisory lock while the previous image keeps serving: error_since_ms and failure_notified_at_ms, both nullable bigint, added with ADD COLUMN IF NOT EXISTS to app.onedrive_sync_configs and app.google_drive_sync_configs. It is metadata-only and idempotent; the previous image neither reads nor writes either column, and every write to those tables names its columns. No backfill runs: a config already sitting in error opens its episode on its next failed run. There is no down migration by design. 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.
  • No new environment variable, and no configuration file changes shape.
  • No image in the stop-gated tier changes. The proxy and db images carry no source change in this range, so a plain tale deploy is the whole upgrade — no --stop, no downtime window.
  • The platform image (the backend, the app, the message catalogs) and the docs image (the en, de and fr pages for crawling, documents and the API reference) carry source changes. The web, ui-docs, proxy, db, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have none. The CLI has no source change in this range; the release executables report 0.5.28.
  • @tale/ui and @tale/marketing-ui are pinned by this release as the ui-v0.5.28 and marketing-ui-v0.5.28 tags on their snapshot branches; a consumer outside the monorepo installs "@tale/ui": "github:tale-project/tale#ui-v0.5.28".

Upgrading

  • On the 0.5 line (0.5.0 – 0.5.27):

    tale update
    tale deploy

    The migration runs at boot. Nothing in this release needs --stop; a deployment crossing from a version older than 0.5.27 should read that release's notes, which do.

  • 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. 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

  • feat(platform): surface broken cloud syncs and notify the owner once by @larryro in #3360
  • fix(platform): close the 2026-09-15 API evaluation's round-i findings by @larryro in #3361

Full Changelog: v0.5.27...v0.5.28