{"releases":[{"tag":"v0.5.77","version":"0.5.77","name":null,"body":"## Highlights\n\n- When an agent's workspace has reached its limit of four concurrent executions, a new run\n  waits for a place instead of failing. Finishing a run wakes the oldest waiter in that\n  workspace; waiting does not consume an automatic retry.\n  [Task automation](https://docs.tale.dev/platform/projects/task-automation)\n  · [#4481](https://github.com/tale-project/tale/pull/4481)\n- Coding agents can discover their granted workspace tools even when they call\n  `workspace_status` through the `workspace_tool` wrapper. The bridge routes that exact\n  discovery call to the existing status endpoint. Tool permissions and session authentication\n  still apply; an agent does not gain a tool by discovering it.\n  [#4496](https://github.com/tale-project/tale/pull/4496)\n- An agent that implemented a task cannot delegate the review of its own result, even if it has\n  the review-delegation permission. An independent manager can still hand the captured review\n  to an eligible reviewer through the existing guarded workflow.\n  [Review delegation](https://docs.tale.dev/platform/projects/task-automation#delegate-review)\n  · [#4487](https://github.com/tale-project/tale/pull/4487)\n- **Message information** shows the model and provider used for a reply, token usage and timing,\n  and the serving provider, region or specific model version when that information is available.\n  An unreported region stays **Not reported** rather than being inferred from a provider's name.\n  [Reply details](https://docs.tale.dev/platform/chat/basics#reply-details)\n  · [#4493](https://github.com/tale-project/tale/pull/4493)\n- Operators can collect a fresh managed-deployment acceptance receipt with `tale deploy accept`.\n  It checks the completed deployment, running images and migration ledgers, and verifies that\n  the canonical HTTPS origin reaches the frontend and API processes observed locally. A\n  different installation running the same version is refused.\n  [CLI install](https://docs.tale.dev/self-hosted/install/cli-install#verify-the-current-deployment)\n  · [#4492](https://github.com/tale-project/tale/pull/4492)\n  · [#4497](https://github.com/tale-project/tale/pull/4497)\n- German and French interface wording follows the existing product labels more consistently,\n  including **Arbeitsbereich**, **Inhaber** and **Conservation légale**. The German website's\n  navigation, page eyebrows and product demos use **Agenten** and **Richtlinien**; some page titles\n  and text still say Agents and Governance\n  ([#4483](https://github.com/tale-project/tale/issues/4483)). Stored settings and translation keys\n  are unchanged.\n  [#4489](https://github.com/tale-project/tale/pull/4489)\n  · [#4490](https://github.com/tale-project/tale/pull/4490)\n\n## Upgrade notes\n\n- Back up the application and knowledge databases, configuration, keys and external stores\n  before deploying. Relative to v0.5.76, this release adds no database migration, operator\n  environment variable or Compose service. When upgrading from an earlier version, retain the\n  [v0.5.76 upgrade requirements](https://github.com/tale-project/tale/blob/v0.5.76/.github/release-notes/v0.5.76.md),\n  including the ordered knowledge-corpus migrations and the prior-release requirements linked\n  there.\n- Deploy the matching platform, sandbox spawner and sandbox-runtime images to receive the\n  workspace-capacity and tool-discovery repairs. The platform, web, docs, UI docs, AI gateway and\n  sandbox images now run Bun 1.4.2 instead of 1.3.12, and the sandbox runtime's `bun` is pinned to\n  1.4.2 as well. The runtime upgrade uses a full deployment; a configuration-only update cannot\n  install these changes. Existing tool and secret grants are preserved.\n  [#4486](https://github.com/tale-project/tale/pull/4486)\n- Use this release's CLI and compatible runtime for deployment acceptance. Prepare the bundle\n  with its full deployment source reference and complete the normal deployment first. Older\n  bundles remain deployable, but cannot provide the new acceptance proof without the\n  source-derived migration inventory and compatible serving processes. Acceptance requires\n  the bundle, full CLI and deployment source commits, and the independently selected version\n  without the `v` prefix. It observes state at that time; it does not promise that routing or\n  processes remain unchanged later.\n- Acceptance does not apply configuration, restart services or run migrations. It takes the\n  deployment lock and creates and removes a private temporary bundle copy. Docker and HTTPS\n  observations share a 120-second budget. Preparation elapsed time counts toward that budget,\n  but filesystem copy and cleanup cannot be interrupted by it. Use an external process\n  supervisor when a whole-command deadline is required. The public serving-process identities\n  are correlation data, not credentials. They arrive in a new `Tale-Serving-Identity` response\n  header on Tale's health endpoints, including the frontend's `/api/health` and the API's\n  `/api/health/ready`, so a proxy in front of Tale must pass it through; the JSON bodies are\n  unchanged.\n  [Acceptance requirements](https://docs.tale.dev/self-hosted/install/cli-install#verify-the-current-deployment)\n- The release candidate source contract (`.github/release-candidate-contract.json`) adds backend\n  integration's **Verify current deployment acceptance ledgers** step. An adopter that pins the\n  contract's digest must review the new digest before adopting this release.\n  [#4492](https://github.com/tale-project/tale/pull/4492)\n- If you grant `task_delegate_review`, deploy this release first: v0.5.76 accepts a delegation\n  from the implementation agent of the reviewed work.\n  [#4487](https://github.com/tale-project/tale/pull/4487)\n- Known issues carried into this release include the projects overview's older **Environment**\n  wording ([#4454](https://github.com/tale-project/tale/issues/4454)). For replies that call tools,\n  the timing breakdown can include tool execution within model phases; do not treat those\n  phases or output speed as isolated model benchmarks.\n  [#4498](https://github.com/tale-project/tale/pull/4498)\n\n## API contract changes\n\nNone in this range. The contract stays at 3.17.0: 141 operations.\n\n\n## What's Changed\n* fix(deps): align Bun images and client build toolchains by @yannickmonney in https://github.com/tale-project/tale/pull/4486\n* test(platform): stabilize Home panel Alt+Arrow browser test by @yannickmonney in https://github.com/tale-project/tale/pull/4485\n* feat(cli): verify current managed deployment acceptance by @yannickmonney in https://github.com/tale-project/tale/pull/4492\n* feat(platform): redesign message info and show where a reply ran by @yannickmonney in https://github.com/tale-project/tale/pull/4493\n* fix(platform): park a run refused at the sandbox exec cap, not fail it by @yannickmonney in https://github.com/tale-project/tale/pull/4481\n* fix(platform): refuse review delegation by the source implementer by @yannickmonney in https://github.com/tale-project/tale/pull/4487\n* fix(platform): match de/fr strings to the shipped UI labels by @yannickmonney in https://github.com/tale-project/tale/pull/4489\n* docs: retry a failed release without rebuilding published images by @yannickmonney in https://github.com/tale-project/tale/pull/4491\n* fix(web): use the app's German names Agenten and Richtlinien by @yannickmonney in https://github.com/tale-project/tale/pull/4490\n* fix(sandbox): route nested workspace status discovery safely by @yannickmonney in https://github.com/tale-project/tale/pull/4496\n* fix(cli): bind deployment acceptance to serving processes by @yannickmonney in https://github.com/tale-project/tale/pull/4497\n* docs: prepare v0.5.77 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4503\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.76...v0.5.77","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.77","publishedAt":"2026-10-07T20:18:10Z"},{"tag":"v0.5.76","version":"0.5.76","name":null,"body":"## Highlights\n\n- Website scans skip rendering a page only when its plain HTML carried all of the text the browser\n  showed at the page's last render. In v0.5.75, nine tenths of the text was enough, so a change\n  drawn only by the page's JavaScript could go unnoticed. Pages whose plain HTML falls short are\n  rendered on every scan. This was a known issue in v0.5.75.\n  [Crawling](https://docs.tale.dev/platform/knowledge/crawling)\n  · [#4451](https://github.com/tale-project/tale/pull/4451)\n- A comment you are editing in a task discussion keeps its editor and draft when new comments push\n  the discussion past 100 rows, whether or not the editor has focus. An open delete confirmation\n  or actor preview stays open the same way. This was a known issue in v0.5.75.\n  [#4460](https://github.com/tale-project/tale/pull/4460)\n- Keyboard focus can now move into a task's agent preview and reach **View more**. Escape closes\n  the preview and returns focus to where it opened. This was a known issue in v0.5.75.\n  [#4457](https://github.com/tale-project/tale/pull/4457)\n- If the code for the embedding alert, the **New project** dialog or the document preview fails to\n  load, for example while offline, the dashboard stays in place. The alert is left out, the\n  **New project** dialog closes, and the document preview says \"The document preview could not be\n  loaded. Try again.\" with a **Try again** button, instead of replacing the chat transcript. This\n  was a known issue in v0.5.75.\n  [#4458](https://github.com/tale-project/tale/pull/4458)\n- When a task holds more than 64 deliverables, a deliverable replaced under the same name now\n  counts as the newest, so the next run stages it. Deliverables written before this release keep\n  their earlier order until they are written again. This was a known issue in v0.5.75.\n  [#4452](https://github.com/tale-project/tale/pull/4452)\n- Resumed task runs receive the current task description, including an explicit notice when it\n  has been cleared. Their prompt reflects edits made since the earlier run instead of relying\n  only on the description retained in the conversation.\n  [#4479](https://github.com/tale-project/tale/pull/4479)\n- A project agent explicitly granted `task_delegate_review` can hand a pending native agent review\n  to another independent agent in the same project. The handoff checks the captured approval,\n  source run and evidence revision, preserves the task's status, implementation assignment and\n  future reviewer settings, and starts no run. The recipient needs `task_review` and its own\n  eligible review run to decide the review.\n  [Task automation](https://docs.tale.dev/platform/projects/task-automation)\n  · [#4482](https://github.com/tale-project/tale/pull/4482)\n- Usage charts add up the selected days, weeks or months in the database, so a large\n  organization's 90-day view no longer reaches the 20,000-row read limit early and shows partial\n  totals. The current and previous periods stay separate even when they share a week or month.\n  This was a known issue in v0.5.75.\n  [#4474](https://github.com/tale-project/tale/pull/4474)\n- Governance's **Model access** and **Default models** editors show their scope, role and mode\n  options, such as Team, Admin or Allowlist, in the session's language; German and French\n  sessions saw them in English before. **Budgets** also translates role options and table targets.\n  Stored rules are unchanged.\n  [#4462](https://github.com/tale-project/tale/pull/4462)\n  · [#4478](https://github.com/tale-project/tale/pull/4478)\n- When an immediate **Model access** save fails, the editor restores the previous controls,\n  including after confirming a change that affects default models. Fresh saved configuration\n  updates the open editor and closes edit or delete dialogs whose selected rule may have changed.\n  A later failed save preserves an earlier successful save while the cached policy is unchanged.\n  [#4266](https://github.com/tale-project/tale/pull/4266)\n- The agent details panel uses the names shown elsewhere in the interface: **Connectors** and\n  **Plattform-Tools** in German, and **Skills** and **Connectors** in French, where \"Compétences\"\n  is the name of a different Governance feature.\n  [#4461](https://github.com/tale-project/tale/pull/4461)\n- Runtime selectors and related guides consistently use **Agent runtime** in English,\n  **Agent-Laufzeit** in German and **Environnement d'agent** in French. Saved runtime values,\n  API fields and documentation URLs are unchanged.\n  [#4480](https://github.com/tale-project/tale/pull/4480)\n- The website's German and French pages use the app's names for Knowledge and Automations\n  (Wissen and Automatisierungen, Connaissances and Automatisations) and correct several German\n  product terms.\n  [#4472](https://github.com/tale-project/tale/pull/4472)\n- Screen readers no longer announce the language, website, Shopify and Enter-key icons on their\n  own; an icon is announced only when it is given a name. The contacts table's language column has\n  a header that screen readers announce as \"Language\", and the composer's send hint is read as\n  \"Enter to send\".\n  [#4468](https://github.com/tale-project/tale/pull/4468)\n- Math rendering uses KaTeX 0.18.2, which fixes GHSA-238p-pmpm-9mq7 (CVE-2026-103923, low\n  severity): earlier versions could treat properties inherited from an already polluted\n  `Object.prototype`, such as `trust`, as renderer options. KaTeX 0.18 prefixes its internal CSS\n  class names; Tale's own styles don't use them.\n  [#4422](https://github.com/tale-project/tale/pull/4422)\n- A project's **Environment** tab says that its credentials are stored for the project and aren't\n  passed to agent runs yet, and points to the agent's **Secrets** grants instead. Its editor applies\n  the server's rule for names (an uppercase letter first, then uppercase letters, digits or\n  underscores, at most 64 characters) and explains an invalid name in the session's language. The\n  projects overview documents the tab in English, German and French, including who can manage the\n  credentials and how to replace or remove a value.\n  [Project credentials](https://docs.tale.dev/platform/projects/overview#environment-credentials)\n  · [#4469](https://github.com/tale-project/tale/pull/4469)\n  · [#4463](https://github.com/tale-project/tale/pull/4463)\n- The CLI reference documents `tale deploy --bundle <directory> --configuration-only`. It applies\n  only managed instructions, agent tool grants and automations to the healthy runtime of an\n  already-ready deployment, without a pre-deploy snapshot or restart; use it when only those\n  change and runtime and identity inputs stay the same. The new `agent-tools` resource updates\n  an existing agent's tool set while preserving its model, instructions and secret grants.\n  German and French guides also correct interface labels, including provider, storage and\n  approval controls.\n  [CLI install](https://docs.tale.dev/self-hosted/install/cli-install)\n  · [#4470](https://github.com/tale-project/tale/pull/4470)\n  · [#4464](https://github.com/tale-project/tale/pull/4464)\n  · [#4477](https://github.com/tale-project/tale/pull/4477)\n  · [#4482](https://github.com/tale-project/tale/pull/4482)\n- The guides describe the **Password change required** screen. Members see it at sign-in after an\n  administrator sets or resets their password, or when it expires under the organization's\n  rotation policy, and set a new password before continuing; an administrator's reset also signs\n  the member out of all sessions.\n  [When a password change is required](https://docs.tale.dev/platform/member/preferences#when-a-password-change-is-required)\n  · [#4465](https://github.com/tale-project/tale/pull/4465)\n\n## Upgrade notes\n\n- Back up the application and knowledge databases, configuration, keys and external stores\n  before deploying. On startup, the knowledge database container (`knowledge-db` in Compose)\n  applies the knowledge-corpus migration `00000000000014_website_url_strict_change_check.sql` from\n  this release's `db` image to `tale_knowledge`. It adds nullable `etag_v2`, `last_modified_v2`\n  and `probe_hash_v2` columns to `public_web.website_urls`, clears the change checks that earlier\n  releases stored once, and adds a trigger that discards any check written to the earlier columns.\n  The previous platform keeps working on the migrated database during a rolling deploy, but its\n  scans render every page, because its checks are discarded. Upgrade the knowledge database\n  container to this release's `db` image and wait for its migrations to finish before starting the\n  new platform images. Keep the columns and the trigger during a rollback: the migration has no\n  down step. The first scan of each website after the upgrade renders every HTML page again; later\n  scans skip unchanged pages as described above.\n  [Upgrade and recover](https://docs.tale.dev/self-hosted/operate/upgrades)\n  · [#4451](https://github.com/tale-project/tale/pull/4451)\n- If upgrading directly from v0.5.74 or earlier, also apply the\n  [v0.5.75 upgrade notes](https://github.com/tale-project/tale/blob/v0.5.75/.github/release-notes/v0.5.75.md);\n  these steps remain required even if you skip running v0.5.75. Upgrade `knowledge-db` to the\n  v0.5.76 release `db` image and wait for knowledge-corpus migrations\n  `00000000000013_website_url_change_check.sql` and\n  `00000000000014_website_url_strict_change_check.sql` to complete, in that order, before starting\n  the new platform images. The backend applies `0153_two_factor_first_required_sign_in.sql` on\n  startup; it is forward-only, so retain its column during an image rollback. Update any pinned\n  `SANDBOX_RUNTIME_IMAGE` to the matching v0.5.76 runtime image, which includes the sharp security\n  update. Use the release's Tale CLI when updating retained 0.5 Compose runtimes so it can adopt\n  the `static-assets` named volume; existing data volumes and host mounts must still match the\n  managed topology.\n- Relative to v0.5.75, this release adds no application database migration, environment variable,\n  or Compose service; knowledge-corpus migration 14 remains required as described above.\n  To adopt `agent-tools` or captured review delegation, first deploy this release's runtime and\n  use its matching Tale CLI. Older runtimes refuse the new capability. Grants are explicit:\n  an `agent-tools` resource replaces the complete tool set, so include every grant to retain;\n  `[]` clears it. Review the native plan and read back the applied tools. This release does not\n  automatically grant review or delegation authority, change secret grants, or route pending\n  reviews. Runtime upgrades still use a full deployment; configuration-only applies to later\n  changes against that ready compatible runtime.\n  [CLI install](https://docs.tale.dev/self-hosted/install/cli-install)\n  · [#4482](https://github.com/tale-project/tale/pull/4482)\n- Known issues in this release:\n  - The projects overview still describes the **Environment** editor's earlier name check and the\n    tab's earlier wording about task runtimes. Where they differ, the tab and its editor are\n    current.\n    [#4454](https://github.com/tale-project/tale/issues/4454)\n\n## API contract changes\n\nThe contract moved from 3.16.0 to 3.17.0 (141 → 141 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n3.17.0 — 2026-10-07: the project-agent tools vocabulary gains `task_delegate_review`, an explicit grant to transfer a pending native agent review with exact source and evidence preconditions. Native task_get includes its bounded delegation receipt for reconciliation. Tale CLI can manage an agent's tools through a conditional tools-only configuration facet; saved models, instructions and secret grants remain independent. No public REST delegation or agent-verdict endpoint is added. Additive.\n\n\n## What's Changed\n* fix(platform): stage re-delivered outputs by last write by @yannickmonney in https://github.com/tale-project/tale/pull/4452\n* fix(platform): prevent unsafe crawl fast-path skips by @yannickmonney in https://github.com/tale-project/tale/pull/4451\n* fix(platform): align agent details locale terminology by @yannickmonney in https://github.com/tale-project/tale/pull/4461\n* fix(platform): retain comment edits across log window threshold by @yannickmonney in https://github.com/tale-project/tale/pull/4460\n* docs(platform): explain project environment credentials by @yannickmonney in https://github.com/tale-project/tale/pull/4463\n* fix(platform): contain optional lazy chunk failures by @yannickmonney in https://github.com/tale-project/tale/pull/4458\n* fix: localize governance model access labels by @yannickmonney in https://github.com/tale-project/tale/pull/4462\n* docs: match localized guide labels to shipped UI by @yannickmonney in https://github.com/tale-project/tale/pull/4464\n* fix(deps): update dependency katex to v0.18.2 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4422\n* fix(ui): make shared icons decorative by default by @yannickmonney in https://github.com/tale-project/tale/pull/4468\n* docs(cli): document configuration-only deploy by @yannickmonney in https://github.com/tale-project/tale/pull/4470\n* fix(platform): preserve actor preview keyboard focus by @yannickmonney in https://github.com/tale-project/tale/pull/4457\n* fix(platform): poll home-panel browser scroll assertions by @yannickmonney in https://github.com/tale-project/tale/pull/4473\n* docs: describe required password screen by @yannickmonney in https://github.com/tale-project/tale/pull/4465\n* fix(web): align localized product names and enforce terminology by @yannickmonney in https://github.com/tale-project/tale/pull/4472\n* fix: align project secret names and copy by @yannickmonney in https://github.com/tale-project/tale/pull/4469\n* fix(platform): aggregate usage chart buckets in SQL by @yannickmonney in https://github.com/tale-project/tale/pull/4474\n* fix(platform): refresh task descriptions on resumed runs by @yannickmonney in https://github.com/tale-project/tale/pull/4479\n* docs: align de/fr guide labels with shipped UI by @yannickmonney in https://github.com/tale-project/tale/pull/4477\n* fix(platform): localize budget role labels by @yannickmonney in https://github.com/tale-project/tale/pull/4478\n* fix(platform): reconcile model access after failed saves by @yannickmonney in https://github.com/tale-project/tale/pull/4266\n* fix(platform): unify agent runtime terminology across locales by @yannickmonney in https://github.com/tale-project/tale/pull/4480\n* feat(platform): enable guarded review delegation by @yannickmonney in https://github.com/tale-project/tale/pull/4482\n* docs: add v0.5.76 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4471\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.75...v0.5.76","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.76","publishedAt":"2026-10-07T12:58:42Z"},{"tag":"v0.5.75","version":"0.5.75","name":null,"body":"## Highlights\n\n- First pages load faster. Each page now brings its interface text by topic, together with the\n  code that reads it, instead of the whole catalog: the cold load drops from 891 to 792 KB gzip,\n  and every first page carries 26 KB gzip less render-blocking CSS. A German or French session\n  fetches about 36 KB gzip of its language before the first frame instead of the whole 124 KB\n  catalog. Switching language fetches the new language's text for the pages already open, and\n  the next page opens in that language. The document preview and the **New project** dialog load\n  on demand, the embedding alert loads only for an organization that needs it, and the API docs\n  page and its stylesheet load only when opened. Language files are named by locale, such as\n  `de-auth-….js`, so a browser's network panel shows which language a session loads.\n- Long task, chat, project and comment lists render the rows near the current view with shared\n  row measurements, and keep keyboard focus, edits, menus and drag state. Repeated markdown and\n  pagination work is reused, organization hints are requested in batches, and each active read\n  refreshes once after a reconnect.\n  [Task views](https://docs.tale.dev/platform/projects/tasks)\n- Website scans render a page again only when it changed. A scan probes each page once; changed\n  pages also require a browser fetch. A `304 Not Modified` answer, or plain HTML whose text reads\n  as it did at the last visit, leaves the page as it is, and only a changed page is rendered in\n  the browser and indexed again. Pages drawn by their JavaScript are still rendered on every scan.\n  On a measured 700-page site, a scan of unchanged pages drops from about 8,000 requests and 2 GiB\n  to about 700 requests of compressed HTML. **Scan now** runs the same scan as the schedule.\n  [Crawling](https://docs.tale.dev/platform/knowledge/crawling)\n  · [#4439](https://github.com/tale-project/tale/pull/4439)\n- Admins can paste a Claude OAuth token as an Anthropic **Subscription key** and replace it later\n  from the credential's row menu. Claude Code receives it as `CLAUDE_CODE_OAUTH_TOKEN`. A pasted\n  token does not refresh; the AI gateway broker remains the way to refresh automatically.\n  [Providers](https://docs.tale.dev/platform/admin/providers)\n  · [#4369](https://github.com/tale-project/tale/pull/4369)\n- Task previews show a project agent's details, and the task timeline no longer shows a deleted\n  agent's id or preview. Failed agent runs have simpler controls with an aligned retry action,\n  and the hint for commenting on a task assigned to an agent is clearer. Long dropdown menus\n  stay within the viewport, and skill menus in dialogs scroll with the mouse wheel again.\n  [#4438](https://github.com/tale-project/tale/pull/4438)\n- Feedback filtered to comments now finds older matching comments even when the newest ratings\n  have none. Usage charts keep the selected period's totals in the summary cards, comparisons\n  and detail tables when you change the chart's granularity, for example from daily to monthly.\n  [#4328](https://github.com/tale-project/tale/pull/4328),\n  [#4411](https://github.com/tale-project/tale/pull/4411)\n- Task agents stage at most the newest 64 prior task deliverables into a run. Older deliverables\n  remain on the task, and the run is told when older outputs were omitted. Attached files are\n  unaffected.\n  [#4448](https://github.com/tale-project/tale/pull/4448)\n- Two-factor grace periods are anchored to the first sign-in that required two-factor\n  authentication. Shortening the grace period takes effect at once, and lengthening it extends\n  from the same anchor. Accounts already in a grace period before this release keep their stored\n  deadline, because their original sign-in time is unknown.\n  [#4437](https://github.com/tale-project/tale/pull/4437)\n- Image processing uses sharp 0.35.5 with librsvg 2.63.2, which fixes GHSA-wq5f-xc86-pv6w\n  (CVE-2026-96889): a memory-safety flaw in SVG decoding that can allow remote code execution\n  under certain conditions on glibc-based Linux. The platform, web and sandbox runtime images\n  carry the update.\n  [#4450](https://github.com/tale-project/tale/pull/4450)\n- All projects board and list views no longer show the **Archived** badge of the project kept in\n  the URL; an archived project's own pages still show it.\n  [#4416](https://github.com/tale-project/tale/pull/4416)\n\n## Upgrade notes\n\n- Back up the application and knowledge databases, configuration, keys and external stores\n  before deploying. On startup, the knowledge database container (`knowledge-db` in Compose)\n  applies the knowledge-corpus migration `00000000000013_website_url_change_check.sql` from this\n  release's `db` image to `tale_knowledge`. It adds a `probe_hash` column to\n  `public_web.website_urls` and clears the stored `etag` and `last_modified` values once. The\n  change is additive and safe during a rolling deploy, because the previous platform image\n  neither reads nor writes these columns. The new crawler keeps its change check in them, so\n  upgrade the knowledge database container to this release's `db` image and wait for its\n  migrations to finish before starting the new platform images. Keep the columns during a\n  rollback: the migration has no down step. The first scan of each website after the upgrade\n  still renders every HTML page; later scans skip unchanged pages.\n  [Upgrade and recover](https://docs.tale.dev/self-hosted/operate/upgrades)\n  · [#4439](https://github.com/tale-project/tale/pull/4439)\n- The backend applies database migration `0153_two_factor_first_required_sign_in.sql` when it\n  starts. It adds a nullable `first_required_sign_in_at_ms` column to `app.two_factor_grace`, so\n  the previous backend keeps working on the migrated database during a rolling deploy; existing\n  rows keep their stored deadline. Keep the column during an image rollback: numbered migrations\n  are forward-only. No environment variable is added.\n  [#4437](https://github.com/tale-project/tale/pull/4437)\n- Update a pinned `SANDBOX_RUNTIME_IMAGE` to this release's image as well: changing the\n  application alone does not update it, and its document tools carry the sharp security update.\n  [#4450](https://github.com/tale-project/tale/pull/4450)\n- This release's Tale CLI can update a retained 0.5 Compose runtime to a release that adds a\n  named volume, such as `static-assets` from v0.5.74. Compose creates the volume and recreates\n  only the service that mounts it. Earlier CLIs refused such an update with \"Existing runtime\n  mounts differ from the managed topology.\" Every existing data volume and host mount must still\n  match the managed topology: a missing host mount, or a named volume that already exists\n  without its mount, is still refused as drift.\n  [#4436](https://github.com/tale-project/tale/pull/4436)\n- Known issues in this release:\n  - In a task discussion, an unsaved comment edit, an open delete confirmation or an actor\n    preview can be dropped when the discussion grows past 100 rows while that row is out of\n    view. Save an edit before loading earlier comments in a long discussion.\n    [#4431](https://github.com/tale-project/tale/issues/4431)\n  - If the code for the embedding alert or the **New project** dialog fails to load, for example\n    while offline, the page shows the error screen instead of the dashboard until it reloads once\n    the service answers. A document preview that fails to load replaces the chat transcript; the\n    composer stays.\n    [#4434](https://github.com/tale-project/tale/issues/4434)\n  - Usage charts read daily rows for every granularity, so a large organization viewing 90 days\n    reaches the 20,000-row read cap sooner; the totals are then marked partial.\n    [#4440](https://github.com/tale-project/tale/issues/4440)\n  - In a task's agent preview, keyboard focus can't reach **View more**: the preview closes as\n    focus moves into it. Open the agent from the project's agents tab instead.\n    [#4438](https://github.com/tale-project/tale/pull/4438)\n  - A page whose plain HTML carries at least nine tenths of its visible text is checked by that\n    text alone, so a change only in its JavaScript-drawn part isn't picked up until the plain text\n    changes too.\n    [#4439](https://github.com/tale-project/tale/pull/4439)\n  - When a task holds more than 64 deliverables, a run that replaces an existing deliverable under\n    the same name can leave that newest copy out of the next run's staged inputs; it stays on the\n    task.\n    [#4448](https://github.com/tale-project/tale/pull/4448)\n\n## API contract changes\n\nNone in this range. The contract stays at 3.16.0: 141 operations.\n\n\n## What's Changed\n* fix(cli): allow additive managed runtime volumes by @yannickmonney in https://github.com/tale-project/tale/pull/4428\n* feat(platform): add a pasted Claude OAuth token as a subscription key by @Israeltheminer in https://github.com/tale-project/tale/pull/4369\n* fix(platform): filter feedback comments before pagination by @yannickmonney in https://github.com/tale-project/tale/pull/4328\n* fix(platform): preserve usage totals across chart granularities by @yannickmonney in https://github.com/tale-project/tale/pull/4411\n* fix(cli): correct managed volume inventory lookup by @yannickmonney in https://github.com/tale-project/tale/pull/4436\n* fix(platform): render a crawled page again only when it changed by @larryro in https://github.com/tale-project/tale/pull/4439\n* feat(platform): polish task agent UI and retry flows by @Israeltheminer in https://github.com/tale-project/tale/pull/4438\n* fix(tasks): bound retained output staging by @yannickmonney in https://github.com/tale-project/tale/pull/4448\n* fix(platform): anchor two-factor grace periods by @yannickmonney in https://github.com/tale-project/tale/pull/4437\n* fix(platform): hide archived badge on All projects views by @yannickmonney in https://github.com/tale-project/tale/pull/4416\n* test(cli): allow cold PowerShell startup by @yannickmonney in https://github.com/tale-project/tale/pull/4449\n* fix(deps): update dependency sharp to v0.35.5 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4450\n* docs: add v0.5.75 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4447\n* docs: update v0.5.75 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4456\n* test(platform): settle dashboard shell lazy alert by @yannickmonney in https://github.com/tale-project/tale/pull/4446\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.74...v0.5.75","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.75","publishedAt":"2026-10-07T00:20:50Z"},{"tag":"v0.5.74","version":"0.5.74","name":null,"body":"## Highlights\n\n- Connected tasks can follow the source's full business workflow. A source integration can\n  publish actions with required fields for verification, closure or reopening, including two\n  stages that share the same Tale column. People submit the complete form from the task's\n  details; the source checks their verified identity, permissions and business rules before\n  accepting the change. Pending requests and accepted or refused decisions remain visible after\n  a reload. The new API also projects source-approved completion and archival. Source projection\n  refuses a task with any pending captured native agent review; Tale's existing review decisions\n  must resolve or explicitly transfer that review first. Integrations must adopt this workflow\n  explicitly.\n  [Tasks](https://docs.tale.dev/platform/projects/tasks)\n  · [#4395](https://github.com/tale-project/tale/pull/4395)\n- Coding agents can use repository-scoped SSH keys with the native sandbox's OpenSSH and\n  `netcat-openbsd`, through its existing egress proxy and verified host keys. Credentialed turns\n  use the workspace owner's Git author name and email even when no GitHub connector token is\n  granted. Repository access still requires a named agent secret and an allowed network route;\n  a Member-started turn receives no agent secrets.\n  [Project agents](https://docs.tale.dev/platform/projects/project-agents)\n  · [#4395](https://github.com/tale-project/tale/pull/4395)\n- Large boards, task lists and Home panels mount the rows near the current view instead of\n  every row. Task details start their reads together, load the first screen of activity first,\n  and defer reviewer and dependency pickers until needed. Long conversations leave older\n  messages dormant, reuse highlighted code, and load math rendering when a reply needs it.\n  Keyboard navigation keeps the focused task available across a long board lane's render\n  window.\n  [Task views](https://docs.tale.dev/platform/projects/tasks)\n- Recovery after an outage or deployment is clearer. The browser checks application and\n  database readiness, refreshes failed reads when service returns, and leaves failed writes for\n  an explicit retry. Installed service workers can show the connection screen during a failed\n  navigation and detect recovery. Shared immutable assets let either application colour serve\n  files needed by tabs opened on the other colour; a missing module triggers one reload after\n  service is ready, with a manual reload action if the problem remains.\n  [Troubleshooting](https://docs.tale.dev/self-hosted/operate/observability/troubleshooting)\n  · [#4385](https://github.com/tale-project/tale/pull/4385)\n- A mail draft stays locked while attachments upload and the message sends, including when its\n  compose pane is reopened. A failed send retains the draft for revision and retry; a successful\n  send clears the submitted draft. Project uploads also retain the current folder when an older\n  folder deletion finishes late, and switching projects clears the previous folder selection.\n  [#4362](https://github.com/tale-project/tale/pull/4362),\n  [#4364](https://github.com/tale-project/tale/pull/4364)\n- Failed reads no longer look like empty settings, a healthy runtime or a quiet period. This\n  covers personalization, Inbox availability, deployed automations, website status, MCP\n  organization access, pending retention, Documents folders, project agents, live task runs and\n  harness health and turns. Cloud-import setup keeps **Connect** unavailable until its checks\n  load successfully.\n  [#4342](https://github.com/tale-project/tale/pull/4342),\n  [#4360](https://github.com/tale-project/tale/pull/4360),\n  [#4356](https://github.com/tale-project/tale/pull/4356),\n  [#4359](https://github.com/tale-project/tale/pull/4359),\n  [#4366](https://github.com/tale-project/tale/pull/4366),\n  [#4346](https://github.com/tale-project/tale/pull/4346),\n  [#4375](https://github.com/tale-project/tale/pull/4375),\n  [#4371](https://github.com/tale-project/tale/pull/4371),\n  [#4374](https://github.com/tale-project/tale/pull/4374),\n  [#4370](https://github.com/tale-project/tale/pull/4370),\n  [#4379](https://github.com/tale-project/tale/pull/4379),\n  [#4378](https://github.com/tale-project/tale/pull/4378)\n- Authenticator entries and backup-code downloads can name the client, product and environment.\n  For example, `TOTP_CLIENT_NAME=Acme` with `TOTP_ENVIRONMENT=te` produces\n  `Acme Tale Platform TE`. Existing authenticator entries keep their saved names and secrets.\n  [Environment reference](https://docs.tale.dev/self-hosted/configuration/environment-reference)\n  · [#4380](https://github.com/tale-project/tale/pull/4380)\n- Sandbox runtime upgrades reuse a common toolchain and independent harness layers, reducing\n  duplicated image data when one harness changes. Headless Chromium remains available for\n  Playwright and its MCP launcher without a second browser download; the document tools and\n  managed harnesses remain baked into the image.\n  [#4367](https://github.com/tale-project/tale/pull/4367)\n\n## Upgrade notes\n\n- Back up the application database, configuration, keys and external stores before deploying.\n  The backend applies additive migrations `0151_task_external_status.sql` and\n  `0152_task_external_status_requests.sql` at startup. They add source projection receipts and\n  immutable human requests and decisions; they do not rewrite existing task history. Keep these\n  tables and their data during an image rollback: numbered migrations are forward-only.\n  [Upgrade and recover](https://docs.tale.dev/self-hosted/operate/upgrades)\n- API contract **3.16.0** adds source workflow metadata and human intent to task status reads,\n  plus `PUT /api/v1/projects/{id}/tasks/{taskId}/external-status` for decisions already validated\n  by a custom source. Existing intake consumers retain their previous open/closed behavior\n  until they opt in. GitHub and GlitchTip issue tasks cannot use this projection route. Read the\n  current lifecycle revision, validate the actual person's complete request at the source, and\n  persist its decision before replying. Handle conflicts by rereading and validating the newer\n  intent; a retry must not overwrite it. Native human forms require a verified active account,\n  and API keys cannot impersonate a form submitter.\n  [API reference](https://docs.tale.dev/develop/api-reference)\n  · [#4395](https://github.com/tale-project/tale/pull/4395)\n- For an integration adopting these forms, deploy Tale first, then its compatible source schema\n  and service, then its bridge worker. Preserve source and Tale receipts and decision history\n  through rollback. Stop the bridge before returning to an older application, and verify that\n  any downgraded source or worker can read the retained records before restarting it. Older Tale\n  versions ignore the new tables and can resume the legacy intake status policy.\n- Use the matching sandbox, egress and runtime images when enabling SSH coding access; changing\n  the application alone does not update an already pinned `SANDBOX_RUNTIME_IMAGE`. Drain active\n  work through the normal deployment procedure. Grant only the repository key the agent needs,\n  keep its private bytes in the secret environment and `ssh-agent`, and retain host-key\n  verification. The new tools use the existing egress policy and grant no repository access by\n  themselves.\n  [Sandbox and SSH configuration](https://docs.tale.dev/self-hosted/configuration/environment-reference)\n- The generated deployment and repository Compose definitions include `static-assets`. Custom\n  Compose setups must mount the same volume at `/app/static-assets` on every web replica. Web\n  readiness waits for publication; retired assets remain available for seven days after their\n  last refresh. During the first upgrade from a version without this support, old replicas\n  still lack the shared fallback. A first browser visit during an outage also needs the edge's\n  unavailable page because no service worker is installed yet.\n  [Handover and recovery](https://docs.tale.dev/self-hosted/operate/upgrades#understand-the-handover)\n- `TOTP_CLIENT_NAME` is optional: 1–40 letters, digits, spaces or `&`, `'`, `.`, `+`, `-`, trimmed.\n  Unset, or `Tale`, names new entries `Tale Platform`. `TOTP_ENVIRONMENT` still accepts 1–32\n  letters, digits, underscores or hyphens; unset or `pr` adds no environment suffix. Invalid\n  values prevent backend startup. Supply these settings to the web and backend roles when\n  customizing them. They change newly generated setup links and downloaded backup-code names,\n  without rotating secrets or renaming entries already saved on a device.\n  [Domain identity](https://docs.tale.dev/self-hosted/configuration/environment-reference#domain-identity-required-at-first-boot)\n\n## API contract changes\n\nThe contract moved from 3.15.0 to 3.16.0 (139 → 141 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations:\n- `GET /api/v1/projects/{id}/tasks/{taskId}/status`\n- `PUT /api/v1/projects/{id}/tasks/{taskId}/external-status`\n\nRemoved operations: none.\n\n### Changelog\n\n3.16.0 — 2026-10-05: task `/status` reads a lifecycle activity revision, verified member provenance and the accepted external projection receipt. Custom sources opt into `/external-status` to project business decisions they already validated, including completion and atomic archival. Exact source binding, conditional native revision and monotonic source lifecycle ordering protect concurrent native moves; no Tale approval is claimed and captured native agent reviews remain protected. Additive.\n\n\n## What's Changed\n* fix(platform): stop stale folders from steering project file uploads by @yannickmonney in https://github.com/tale-project/tale/pull/4364\n* fix(platform): surface MCP organization read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4366\n* fix(platform): recover failed Inbox availability discovery by @yannickmonney in https://github.com/tale-project/tale/pull/4360\n* fix(platform): recover deployed automation reads by @yannickmonney in https://github.com/tale-project/tale/pull/4356\n* fix(platform): allow retry after failed website status sync by @yannickmonney in https://github.com/tale-project/tale/pull/4359\n* fix(platform): surface pending retention read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4346\n* fix(sandbox): reduce runtime image and upgrade disk usage by @yannickmonney in https://github.com/tale-project/tale/pull/4367\n* fix(platform): name a failed harness-health read, not a healthy runtime by @yannickmonney in https://github.com/tale-project/tale/pull/4370\n* fix(platform): freeze the compose draft while it sends by @yannickmonney in https://github.com/tale-project/tale/pull/4362\n* fix(platform): name the client and product in authenticator entries by @yannickmonney in https://github.com/tale-project/tale/pull/4380\n* fix(platform): distinguish unavailable personalization preferences by @yannickmonney in https://github.com/tale-project/tale/pull/4342\n* fix(platform): retain task subject on live-run read failure by @yannickmonney in https://github.com/tale-project/tale/pull/4374\n* fix(platform): name a failed folder-list read on the Documents hub by @yannickmonney in https://github.com/tale-project/tale/pull/4375\n* fix(platform): surface project agent read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4371\n* fix(platform): name a failed cloud-import setup check, hold Connect by @yannickmonney in https://github.com/tale-project/tale/pull/4378\n* fix(platform): name a failed harness turns read, not a quiet period by @yannickmonney in https://github.com/tale-project/tale/pull/4379\n* fix(platform): make availability recovery reliable by @yannickmonney in https://github.com/tale-project/tale/pull/4385\n* fix(platform): land every delete on its overview page by @yannickmonney in https://github.com/tale-project/tale/pull/4381\n* fix(deps): update dependency proxy-addr to v2.0.8 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4418\n* feat(platform): state every backend domain's rules in a spec that tests hold by @larryro in https://github.com/tale-project/tale/pull/4420\n* fix(platform): synchronize external task lifecycles by @yannickmonney in https://github.com/tale-project/tale/pull/4395\n* docs: author release notes for v0.5.74 by @yannickmonney in https://github.com/tale-project/tale/pull/4399\n* fix(docs): unify documentation entry points and site navigation by @yannickmonney in https://github.com/tale-project/tale/pull/4408\n* fix(deps): patch release audit vulnerabilities by @yannickmonney in https://github.com/tale-project/tale/pull/4423\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.73...v0.5.74","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.74","publishedAt":"2026-10-06T06:31:32Z"},{"tag":"v0.5.73","version":"0.5.73","name":null,"body":"## Highlights\n\n- Docker inside agent sandboxes starts on demand. The engine starts with the first Docker\n  command, and health checks no longer wake it. It stops again after five minutes without clients\n  when no container is running, restarting or paused and none has a restart policy. The next\n  command restarts it with the same images, volumes and workspace. Agents need no setting for\n  this.\n  [Sandbox infrastructure](https://docs.tale.dev/self-hosted/configuration/environment-reference#sandbox-infrastructure)\n  · [#4173](https://github.com/tale-project/tale/pull/4173)\n- Long agent runs recover faster and fail explicitly when they reach a limit. After a reconnect,\n  a run resumes from durable checkpoints instead of replaying its whole history, and streamed\n  output is updated incrementally. Session start, file staging and output replay have explicit\n  time, memory and storage budgets; a run that exhausts one fails with a reason. Recovery visits\n  unreachable runs in turn, so they no longer keep reachable runs waiting, and one slow direct-chat\n  answer no longer holds the worker slots of answers that already finished. A failed sandbox\n  create keeps the workspace's data for a retry. Operators can also opt new workspaces into a\n  lighter profile without Docker and set Claude Code's reasoning effort (see the upgrade notes).\n  [#4176](https://github.com/tale-project/tale/pull/4176),\n  [#4177](https://github.com/tale-project/tale/pull/4177),\n  [#4200](https://github.com/tale-project/tale/pull/4200)\n- Sandboxes clean up more safely. After repeated agent rotations, process cleanup keeps its hold\n  until the last successor finishes and signals only process groups it still owns. A session on\n  a connected device keeps its route when its creation overlaps a cleanup.\n  [#4324](https://github.com/tale-project/tale/pull/4324),\n  [#4277](https://github.com/tale-project/tale/pull/4277)\n- Operators can keep project and agent instructions, standing-task descriptions and native\n  automation definitions, deployments and schedules in reviewed source files and apply them with\n  the Tale CLI. `tale deploy --configuration-only` applies such instruction and workflow changes\n  without a snapshot, rollout or restart, once a full deployment of this release has completed.\n  Configuration refuses to re-enable a disabled schedule or change one paused after failures, and\n  it does not grant secrets, assign tasks, select models or change agent equipment. Upgrading\n  changes no instructions or workflows by itself.\n  [Apply changes without a rollout](https://github.com/tale-project/tale/blob/main/tools/cli/README.md#apply-instruction-and-workflow-changes-without-a-rollout)\n  · [#4306](https://github.com/tale-project/tale/pull/4306)\n- The authenticated backend metrics add\n  `tale_backend_automation_trigger_scan_last_success_timestamp_seconds`, the database time of the\n  last schedule scan that actually completed. An alert on it notices when scheduled automations\n  stop being scanned while the API still answers. It reads zero without a verified completion in\n  the last ten minutes, and it measures scanning, not whether each workflow succeeds. With backend\n  tracing on, agent work also reports spans for acquiring the sandbox, gateway provisioning,\n  staging, execution, persistence and harvest.\n  [Metrics](https://docs.tale.dev/self-hosted/configuration/observability-config#metrics)\n  · [#4305](https://github.com/tale-project/tale/pull/4305),\n  [#4176](https://github.com/tale-project/tale/pull/4176)\n- With `SENTRY_DSN` set, the sandbox service also reports its failures, such as failed requests\n  and background tasks, through the backend's privacy filter. Browser, backend and sandbox events\n  share one `SENTRY_ENVIRONMENT` label, which a managed deployment can now choose without being\n  renamed.\n  [Choose where errors go](https://docs.tale.dev/self-hosted/configuration/observability-config#choose-where-errors-go)\n  · [#4334](https://github.com/tale-project/tale/pull/4334)\n- A project agent's `updatedAt` now advances with every change, including two saves in the same\n  millisecond or a save after the server clock moved back. A conditional update with\n  `expectedUpdatedAt` therefore refuses a stale save instead of overwriting newer configuration.\n  [#4333](https://github.com/tale-project/tale/pull/4333)\n- A project's **Environment** tab shows a failed read of its secrets with **Try again** instead\n  of an empty editor, so **Save** can no longer overwrite secrets the page could not read.\n  [#4317](https://github.com/tale-project/tale/pull/4317)\n- Every password check inside the app now counts toward the same temporary lock as a failed\n  sign-in: confirming the password for a passkey, turning two-factor on or off, creating backup\n  codes, showing the authenticator secret and changing the password. Adding a passkey on a session\n  signed in more than a day ago asks for the password first instead of failing with \"Session is\n  not fresh\", and an account without a password is offered **Sign in again**. The two-factor\n  password prompt is cleared when it closes, so a reopened prompt asks for the password again.\n  [Add a passkey](https://docs.tale.dev/platform/admin/two-factor-authentication#add-a-passkey)\n  · [#4336](https://github.com/tale-project/tale/pull/4336),\n  [#4340](https://github.com/tale-project/tale/pull/4340),\n  [#4341](https://github.com/tale-project/tale/pull/4341),\n  [#4349](https://github.com/tale-project/tale/pull/4349)\n- Authenticator entries can name the deployment they belong to. With `TOTP_ENVIRONMENT=te`, newly\n  generated setup QR codes and links name the entry `Tale <TE>`; unset or `pr` keeps `Tale`.\n  Showing the authenticator secret again now gives the same entry name as the original setup.\n  Entries already saved in an authenticator app keep their names.\n  [Domain identity](https://docs.tale.dev/self-hosted/configuration/environment-reference#domain-identity-required-at-first-boot)\n  · [#4355](https://github.com/tale-project/tale/pull/4355)\n- Other screens that fail to load also say so and offer **Try again**, instead of showing zero,\n  an empty list, editable defaults or an endless spinner. This covers your teams and passkeys in\n  the account settings, the organization settings, **Usage**, **Trash**, the audit log's block\n  counters, the **Embedding** settings, the governance policies, the legal hold pickers, the\n  member list for data subject requests, a project's audience, a task agent's **Details**, the\n  automation editor, automation metrics, and an automation run's details and agent transcript.\n  Governance policy editors stay locked until their policy loads, a data subject request can't be\n  filed until its member list loads, and automation metrics keep earlier figures, marked as\n  possibly outdated, when a refresh fails. A project that fails to load keeps its header and tabs\n  instead of saying that it may have been deleted.\n  [See your teams](https://docs.tale.dev/platform/member/preferences#teams)\n  · [#4296](https://github.com/tale-project/tale/pull/4296),\n  [#4300](https://github.com/tale-project/tale/pull/4300),\n  [#4272](https://github.com/tale-project/tale/pull/4272),\n  [#4314](https://github.com/tale-project/tale/pull/4314),\n  [#4287](https://github.com/tale-project/tale/pull/4287),\n  [#4292](https://github.com/tale-project/tale/pull/4292),\n  [#4302](https://github.com/tale-project/tale/pull/4302),\n  [#4275](https://github.com/tale-project/tale/pull/4275),\n  [#4294](https://github.com/tale-project/tale/pull/4294),\n  [#4288](https://github.com/tale-project/tale/pull/4288),\n  [#4304](https://github.com/tale-project/tale/pull/4304),\n  [#4278](https://github.com/tale-project/tale/pull/4278),\n  [#4344](https://github.com/tale-project/tale/pull/4344),\n  [#4343](https://github.com/tale-project/tale/pull/4343),\n  [#4345](https://github.com/tale-project/tale/pull/4345),\n  [#4354](https://github.com/tale-project/tale/pull/4354)\n- Saving no longer loses work. Project agents and teams lock their fields until a save settles,\n  a refused member edit keeps the dialog open with your changes, and the add-credential wizard\n  holds **Back** while it saves. A refused API-key creation is reported and keeps its name and\n  expiry for a retry. A skill save that lands after its dialog closed no longer replaces a later\n  draft, and creating a blank skill under a taken name reports that it exists instead of\n  overwriting it.\n  [#4256](https://github.com/tale-project/tale/pull/4256),\n  [#4286](https://github.com/tale-project/tale/pull/4286),\n  [#4293](https://github.com/tale-project/tale/pull/4293),\n  [#4269](https://github.com/tale-project/tale/pull/4269),\n  [#4267](https://github.com/tale-project/tale/pull/4267),\n  [#4299](https://github.com/tale-project/tale/pull/4299),\n  [#4255](https://github.com/tale-project/tale/pull/4255)\n- Drafts survive what happens around them. The automation editor keeps edits made during\n  **Save anyway**, and its trigger and project sections keep unsaved changes when another session\n  saves. A Knowledge entry being edited keeps its draft when a newer version arrives and asks\n  before discarding it. Compose keeps the chosen mailbox and sender when credentials fail to load\n  and holds **Send** until they load, and an answer typed for one automation question never\n  carries over to the next.\n  [#4313](https://github.com/tale-project/tale/pull/4313),\n  [#4321](https://github.com/tale-project/tale/pull/4321),\n  [#4337](https://github.com/tale-project/tale/pull/4337),\n  [#4332](https://github.com/tale-project/tale/pull/4332),\n  [#4338](https://github.com/tale-project/tale/pull/4338)\n- Forms match what is saved. The **Custom instructions** editor counts to the 3,200 characters\n  that saving allows instead of advertising 5,000, **Edit member** checks the name before saving,\n  legal matter fields use the API's length limits, and a long automation name no longer yields a\n  slug that saving refuses. Login policy delays keep fractions of a second, and a custom\n  provider's model field shows the IDs that **Save** sends.\n  [#4297](https://github.com/tale-project/tale/pull/4297),\n  [#4237](https://github.com/tale-project/tale/pull/4237),\n  [#4310](https://github.com/tale-project/tale/pull/4310),\n  [#4279](https://github.com/tale-project/tale/pull/4279),\n  [#4261](https://github.com/tale-project/tale/pull/4261),\n  [#4315](https://github.com/tale-project/tale/pull/4315)\n- Records show what is stored. In a workflow run with several agent steps, each step shows its own\n  transcript, and an older step without a recorded execution shows none instead of another step's.\n  AI events in the audit log keep their diagnostic metadata with secrets redacted, an unset\n  product price or stock shows a dash instead of reading as free, an expanded feedback comment\n  loads in full, and a task created as Done or Cancelled gets a completion time.\n  [#4239](https://github.com/tale-project/tale/pull/4239),\n  [#4244](https://github.com/tale-project/tale/pull/4244),\n  [#4274](https://github.com/tale-project/tale/pull/4274),\n  [#4298](https://github.com/tale-project/tale/pull/4298),\n  [#4249](https://github.com/tale-project/tale/pull/4249)\n- Actions finish what they report. Deleting a task removes its whole subtree; tasks nested deeper\n  than 32 levels used to survive, the first of them as a new top-level task. Removing the logo or\n  a favicon in the branding settings deletes the stored image, and **Add device** completes only\n  when the device enrolled with its own join command connects. A notification whose mark as read\n  fails comes back instead of staying hidden. SharePoint offers only one-time imports; it used to\n  offer Sync and report it completed although no sync was registered.\n  [#4226](https://github.com/tale-project/tale/pull/4226),\n  [#4271](https://github.com/tale-project/tale/pull/4271),\n  [#4257](https://github.com/tale-project/tale/pull/4257),\n  [#4309](https://github.com/tale-project/tale/pull/4309),\n  [#4199](https://github.com/tale-project/tale/pull/4199)\n- Lists and search stay accurate. Contact search runs on the server and also matches phone\n  numbers, and a sorted large contact list renders in a window that grows as you scroll. Website\n  search clears its results when you edit the query, a stale response no longer replaces newer\n  results, and the credential vendor filter can always be cleared. Products, Contacts and\n  Knowledge entries offer bulk delete only to members with write access. Connectors that left the\n  roster stay visible on an agent and can be removed, and a sandbox placement that cannot be read\n  shows **Unknown** instead of the server.\n  [#4243](https://github.com/tale-project/tale/pull/4243),\n  [#4303](https://github.com/tale-project/tale/pull/4303),\n  [#4352](https://github.com/tale-project/tale/pull/4352),\n  [#4281](https://github.com/tale-project/tale/pull/4281),\n  [#4353](https://github.com/tale-project/tale/pull/4353),\n  [#4312](https://github.com/tale-project/tale/pull/4312),\n  [#4320](https://github.com/tale-project/tale/pull/4320)\n- Chat and documents: a new chat that fails to start keeps its attachments, renaming a chat waits\n  for an input method to finish composing, a refused rename keeps the typed title for a retry,\n  and the document preview fits narrow screens.\n  [#4268](https://github.com/tale-project/tale/pull/4268),\n  [#4203](https://github.com/tale-project/tale/pull/4203),\n  [#4311](https://github.com/tale-project/tale/pull/4311),\n  [#4318](https://github.com/tale-project/tale/pull/4318)\n- Accessibility: task cards describe their blocked state, review recipient and comment count to\n  keyboard and screen-reader users, searchable select popovers have names, notification switches\n  keep focus while saving, global search keeps its selection on a visible result when the scope\n  narrows, the times on Home rows meet AA contrast, and dialogs block pointer input to the page\n  behind them from their first frame.\n  [#4295](https://github.com/tale-project/tale/pull/4295),\n  [#4178](https://github.com/tale-project/tale/pull/4178),\n  [#4265](https://github.com/tale-project/tale/pull/4265),\n  [#4308](https://github.com/tale-project/tale/pull/4308),\n  [#4171](https://github.com/tale-project/tale/pull/4171),\n  [#4307](https://github.com/tale-project/tale/pull/4307)\n- On the website, the comparisons with Flowise, Vellum and Vibe Kanban cite a primary source for\n  each claim, show their review date and state each product's current status.\n  [#4335](https://github.com/tale-project/tale/pull/4335)\n- For contributors, each of the seven validation workflows ends in a `CI ready` check. It passes\n  only when every applicable job ran and succeeded, and it fails on a failure, cancellation,\n  unexpected skip or missing scope. Merge groups run the full set.\n  [#4323](https://github.com/tale-project/tale/pull/4323)\n\n## Upgrade notes\n\n- The backend applies database migration `0150` when it starts. It adds a nullable recovery-check\n  time to project-agent and automation runs and two partial indexes, so recovery can visit runs in\n  turn. The previous backend keeps working on the migrated database during a rolling deploy.\n  Building the indexes reads both run tables once, which can lengthen the first start on an\n  instance with a long run history.\n- Update the platform, the sandbox service and the sandbox runtime image together. Checkpoint\n  recovery for long agent runs needs all three; with an older runtime, a run falls back to the\n  previous recovery. During a rolling upgrade, keep old sandbox services on the runtime image\n  they use until they are replaced, and don't move a runtime image tag that an old sandbox service\n  still uses: it cannot tell a passing stall from a confirmed failure. A new sandbox service with\n  an older runtime refuses new work on an unhealthy session and keeps its normal idle and lifetime\n  cleanup. Checkpoints and active execs last only for the runtime’s lifetime: restarting it loses\n  them while persistent workspace files remain. Preserve valuable work before restarting sessions.\n- Sandbox behavior that changes without configuration:\n  - A session now waits at most 5 seconds for shared build-cache setup, instead of 15, or a\n    quarter of its startup budget if that is shorter, before it uses its local builder.\n    `SANDBOX_BUILDKITD_PROVISION_TIMEOUT_MS` (100–60000) changes the wait.\n  - An organization's build-cache helper runs as many build steps at once as its whole CPUs\n    allow, instead of four. Its CPU limit, `SANDBOX_BUILDKITD_CPUS`, defaults to\n    `SANDBOX_AGENT_CPUS`, which is `2`. The bound applies when an idle helper is recreated;\n    `SANDBOX_BUILDKITD_CPUS=4` restores four steps and raises the helper's CPU limit to four.\n  - `SANDBOX_SESSION_CREATE_TIMEOUT_MS` (180 seconds by default) bounds every step of a session\n    start on Docker and Kubernetes, build-cache setup included. A cancelled request stops its\n    session start and keeps the workspace.\n  - Resuming a released warm session passes the same memory and disk admission as a new one, so\n    it can wait for capacity.\n  - On Kubernetes, session Pods get a startup probe on `/readyz` and a liveness probe on\n    `/livez`. Kubernetes restarts a session daemon that stops answering, even when its session is\n    pinned; Docker or egress failures alone don't restart it.\n- With `SENTRY_DSN` set, sandbox failures also appear in your reporting project after the\n  upgrade; the sandbox service sends errors only, never traces. The optional `SENTRY_ENVIRONMENT`\n  labels browser, backend and sandbox events. A managed deployment still defaults it to its\n  retained name and now keeps a label declared as an environment reference in its specification\n  instead of replacing it. Set the referenced variable at the destination: `tale deploy` refuses a\n  missing required reference before it changes the stack.\n  [Choose the error reporting environment](https://docs.tale.dev/self-hosted/install/cli-install#choose-the-error-reporting-environment)\n- A wrong password in an in-app check now writes a `login_attempt` audit row stamped\n  `metadata.passwordCheck`, which names the check; sign-in rows stay unstamped. Filter on that\n  stamp if you count sign-in attempts from the audit log. While an account is locked, these checks\n  refuse until the lock expires, and the password change form says how long that is.\n- Optional environment variables, documented in the\n  [environment reference](https://docs.tale.dev/self-hosted/configuration/environment-reference#sandbox-infrastructure),\n  change nothing until you set them:\n  - `SANDBOX_AGENT_PROFILE`, read by the backend API and worker. `agent-light` gives new agent and\n    workflow workspaces their coding tools and a persistent workspace without Docker or\n    build-cache helpers. Existing workspaces keep their profile.\n  - `TALE_SANDBOX_CLAUDE_EFFORT`, read by the backend API and worker, sets Claude Code's reasoning\n    effort to `low`, `medium`, `high` or `max`. Unset keeps the harness default. Compare\n    representative tasks before lowering it; shorter runs are not guaranteed.\n  - `SANDBOX_DOCKER_DATA_ROOT` and `SANDBOX_DOCKER_DATA_PATH` give the sandbox service a read-only\n    mount of Docker's data root, so disk admission checks that filesystem too. `tale deploy` adds\n    the mount; a raw Compose file needs the override shown in the reference. A configured mount\n    that cannot be read or verified blocks new and resumed sessions.\n\n  The repository's `compose.yml` and `tale deploy` pass the two backend variables to the backend\n  API and worker; a hand-written Compose file or manifest must pass them as well. Recreate both\n  services after changing them.\n- `TOTP_ENVIRONMENT` is a new optional backend variable for the name of newly generated\n  authenticator entries: `te` gives `Tale <TE>`, other labels of 1–32 characters must start with a letter or digit and\n  contain only letters, digits, `_` or `-`. They are uppercased, and `pr` or unset keeps `Tale`. Leave it unset rather than empty: an empty or\n  invalid value stops the backend from starting. It rotates no secret and renames no entry already\n  saved in an authenticator app.\n  [Domain identity](https://docs.tale.dev/self-hosted/configuration/environment-reference#domain-identity-required-at-first-boot)\n- `tale deploy --configuration-only` needs the receipt of a completed full deployment made with\n  this release's CLI and platform. A deployment whose receipt predates this capability must\n  complete one normal deployment first, and a refused configuration-only apply never falls back to\n  a full deployment.\n- Enable an alert on the schedule-scan metric only after this release is deployed and its\n  timestamp is seen advancing. Treat a missing series or a timestamp well in the future as\n  unhealthy.\n- Known limitation: the app's project-agent editor does not yet send the revision it opened with,\n  so saving it can still overwrite a change made after it opened, including instructions applied\n  through configuration ([#3598](https://github.com/tale-project/tale/issues/3598)).\n- The API contract stays at 3.15.0.\n\n## API contract changes\n\nNone in this range. The contract stays at 3.15.0: 139 operations.\n\n\n## What's Changed\n* fix(sandbox): improve agent throughput and sandbox reliability by @yannickmonney in https://github.com/tale-project/tale/pull/4177\n* fix(platform): disable add-credential Back while its save is pending by @yannickmonney in https://github.com/tale-project/tale/pull/4269\n* fix(platform): preserve notification switch focus while saving by @yannickmonney in https://github.com/tale-project/tale/pull/4265\n* fix(platform): preserve fractional login policy delays by @yannickmonney in https://github.com/tale-project/tale/pull/4261\n* fix(platform): correlate device setup with its join command by @yannickmonney in https://github.com/tale-project/tale/pull/4257\n* fix(platform): block edits during project-agent saves by @yannickmonney in https://github.com/tale-project/tale/pull/4256\n* fix(platform): bound large contact list search and sorting by @yannickmonney in https://github.com/tale-project/tale/pull/4243\n* fix(ui): name searchable select popover controls by @yannickmonney in https://github.com/tale-project/tale/pull/4178\n* fix(sandbox): preserve routes across create and cleanup races by @yannickmonney in https://github.com/tale-project/tale/pull/4277\n* ci: reduce redundant runner admissions and preserve main caches by @yannickmonney in https://github.com/tale-project/tale/pull/4284\n* fix(platform): keep active credential vendor filters reversible by @yannickmonney in https://github.com/tale-project/tale/pull/4281\n* fix(platform): hash daemon inputs for static checks by @yannickmonney in https://github.com/tale-project/tale/pull/4280\n* fix(platform): trim generated automation slug after truncation by @yannickmonney in https://github.com/tale-project/tale/pull/4279\n* fix(platform): restore attachments when new chat creation fails by @yannickmonney in https://github.com/tale-project/tale/pull/4268\n* fix(platform): show a dash for an unset product price and stock by @yannickmonney in https://github.com/tale-project/tale/pull/4274\n* fix(platform): say when your teams fail to load on the account page by @yannickmonney in https://github.com/tale-project/tale/pull/4296\n* fix(platform): recover task agent Details read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4294\n* fix(sandbox): reduce stream work and protect replacement sessions by @yannickmonney in https://github.com/tale-project/tale/pull/4200\n* fix(platform): scope agent transcripts to their timeline step by @yannickmonney in https://github.com/tale-project/tale/pull/4239\n* fix(platform): retain diagnostic metadata in AI audit details by @yannickmonney in https://github.com/tale-project/tale/pull/4244\n* fix(platform): delete task subtrees beyond depth 32 by @yannickmonney in https://github.com/tale-project/tale/pull/4226\n* fix(platform): cover notification review follow-ups and Watch wording by @yannickmonney in https://github.com/tale-project/tale/pull/4165\n* fix(platform): keep chat rename open while an IME composes by @yannickmonney in https://github.com/tale-project/tale/pull/4203\n* fix(platform): expose task card state to keyboard and screen readers by @yannickmonney in https://github.com/tale-project/tale/pull/4295\n* fix(platform): stamp terminal tasks created directly by @yannickmonney in https://github.com/tale-project/tale/pull/4249\n* fix(platform): keep a later skill draft when a dismissed save lands by @yannickmonney in https://github.com/tale-project/tale/pull/4299\n* fix(platform): show a failed passkey list read with a retry by @yannickmonney in https://github.com/tale-project/tale/pull/4300\n* fix(platform): show legal hold picker read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4302\n* fix(platform): expose verified scheduler scan liveness by @yannickmonney in https://github.com/tale-project/tale/pull/4305\n* feat(cli): provision managed workflows without runtime disruption by @yannickmonney in https://github.com/tale-project/tale/pull/4306\n* fix(platform): persist individual branding image removals by @yannickmonney in https://github.com/tale-project/tale/pull/4271\n* fix(platform): show failed Usage and Trash reads, not zero or empty by @yannickmonney in https://github.com/tale-project/tale/pull/4272\n* fix(ui): isolate modal pointers in the first style pass by @yannickmonney in https://github.com/tale-project/tale/pull/4307\n* fix(platform): show the model ids Save sends after a source switch by @yannickmonney in https://github.com/tale-project/tale/pull/4315\n* fix(ui): preserve search selection when scope narrows by @yannickmonney in https://github.com/tale-project/tale/pull/4308\n* fix(platform): make document preview responsive on mobile by @yannickmonney in https://github.com/tale-project/tale/pull/4318\n* fix(platform): avoid server claims for unknown sandbox placement by @yannickmonney in https://github.com/tale-project/tale/pull/4320\n* fix(platform): show unavailable equipped connectors by @yannickmonney in https://github.com/tale-project/tale/pull/4312\n* fix(platform): guard blank skill creation against overwrite by @yannickmonney in https://github.com/tale-project/tale/pull/4255\n* fix(platform): validate member names before saving by @yannickmonney in https://github.com/tale-project/tale/pull/4237\n* fix(platform): show API-key creation failures by @yannickmonney in https://github.com/tale-project/tale/pull/4267\n* fix(cli): accept GitHub's run fan-out skew in the release gate walk by @yannickmonney in https://github.com/tale-project/tale/pull/4331\n* test(platform): pin session-probe gate branches and batch notifications by @yannickmonney in https://github.com/tale-project/tale/pull/4191\n* fix(platform): restrict SharePoint imports to one-time copies by @yannickmonney in https://github.com/tale-project/tale/pull/4199\n* fix(sandbox): reduce agent overhead and harden session recovery by @yannickmonney in https://github.com/tale-project/tale/pull/4173\n* fix(platform): exclude generated catalog logs from test cache by @yannickmonney in https://github.com/tale-project/tale/pull/4329\n* fix(sandbox): bound agent runs and harden session recovery by @yannickmonney in https://github.com/tale-project/tale/pull/4176\n* fix(platform): surface block counter read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4314\n* fix(platform): load full feedback comments on expansion by @yannickmonney in https://github.com/tale-project/tale/pull/4298\n* fix(platform): block team edits while saving by @yannickmonney in https://github.com/tale-project/tale/pull/4286\n* fix(platform): preserve project audience on failed team reads by @yannickmonney in https://github.com/tale-project/tale/pull/4275\n* fix(platform): align custom instruction length limits by @yannickmonney in https://github.com/tale-project/tale/pull/4297\n* fix(platform): handle embedding policy read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4287\n* fix(platform): block governance edits after failed policy reads by @yannickmonney in https://github.com/tale-project/tale/pull/4292\n* fix(platform): guard website search responses by @yannickmonney in https://github.com/tale-project/tale/pull/4303\n* fix(platform): show a failed secrets read, not an empty editor by @yannickmonney in https://github.com/tale-project/tale/pull/4317\n* fix(platform): preserve automation edits made during saves by @yannickmonney in https://github.com/tale-project/tale/pull/4313\n* fix(platform): keep project-agent revisions monotonic by @yannickmonney in https://github.com/tale-project/tale/pull/4333\n* fix(sandbox): preserve rotation holds and bound process cleanup by @yannickmonney in https://github.com/tale-project/tale/pull/4324\n* fix(platform): recover automation run detail read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4288\n* fix(platform): restore AA contrast for Home row times by @yannickmonney in https://github.com/tale-project/tale/pull/4171\n* fix(platform): preserve agent transcript read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4304\n* fix(platform): align legal matter field validation with API limits by @yannickmonney in https://github.com/tale-project/tale/pull/4310\n* fix(platform): keep failed member edits open by @yannickmonney in https://github.com/tale-project/tale/pull/4293\n* fix(sandbox): report failures with canonical environments by @yannickmonney in https://github.com/tale-project/tale/pull/4334\n* ci: enforce complete native merge readiness by @yannickmonney in https://github.com/tale-project/tale/pull/4323\n* fix(platform): keep unsaved trigger and project edits on refresh by @yannickmonney in https://github.com/tale-project/tale/pull/4321\n* docs(web): source the Flowise, Vellum and Vibe Kanban comparisons by @yannickmonney in https://github.com/tale-project/tale/pull/4335\n* fix(platform): restore notifications after failed read requests by @yannickmonney in https://github.com/tale-project/tale/pull/4309\n* fix(platform): preserve knowledge drafts across versions by @yannickmonney in https://github.com/tale-project/tale/pull/4337\n* fix(platform): isolate automation answers by ask identity by @yannickmonney in https://github.com/tale-project/tale/pull/4338\n* fix(platform): preserve compose drafts on credential read failure by @yannickmonney in https://github.com/tale-project/tale/pull/4332\n* fix(platform): preserve failed chat rename drafts by @yannickmonney in https://github.com/tale-project/tale/pull/4311\n* fix(platform): confirm the password before adding a passkey by @yannickmonney in https://github.com/tale-project/tale/pull/4336\n* fix(platform): count password confirmations toward the sign-in lock by @yannickmonney in https://github.com/tale-project/tale/pull/4340\n* fix(platform): recover automation editor from detail read errors by @yannickmonney in https://github.com/tale-project/tale/pull/4278\n* docs: show the account's sign-in methods on the two-factor page by @yannickmonney in https://github.com/tale-project/tale/pull/4341\n* fix(platform): name authenticator entries by environment by @yannickmonney in https://github.com/tale-project/tale/pull/4355\n* feat(cli): publish the release candidate source contract by @yannickmonney in https://github.com/tale-project/tale/pull/4347\n* fix(platform): clear website search results on query edits by @yannickmonney in https://github.com/tale-project/tale/pull/4352\n* fix(platform): distinguish failed automation metrics reads by @yannickmonney in https://github.com/tale-project/tale/pull/4345\n* fix(platform): distinguish organization settings read failures by @yannickmonney in https://github.com/tale-project/tale/pull/4344\n* fix(platform): clear closed two-factor password prompts by @yannickmonney in https://github.com/tale-project/tale/pull/4349\n* feat(platform): state the task rules in a spec that tests hold by @larryro in https://github.com/tale-project/tale/pull/4351\n* fix(platform): name a failed project read instead of a blank Overview by @yannickmonney in https://github.com/tale-project/tale/pull/4354\n* fix(platform): gate content bulk delete by write permission by @yannickmonney in https://github.com/tale-project/tale/pull/4353\n* fix(platform): handle failed data subject member reads by @yannickmonney in https://github.com/tale-project/tale/pull/4343\n* fix(platform): serialize saved task attachment changes by @yannickmonney in https://github.com/tale-project/tale/pull/4339\n* fix(platform): surface failed project chat reads by @yannickmonney in https://github.com/tale-project/tale/pull/4361\n* fix(ui): preserve failed bulk action selection for retry by @yannickmonney in https://github.com/tale-project/tale/pull/4357\n* fix(platform): recover failed task agent latest-run reads by @yannickmonney in https://github.com/tale-project/tale/pull/4358\n* fix(platform): keep an unsaved project rename draft on a live rename by @yannickmonney in https://github.com/tale-project/tale/pull/4363\n* fix(platform): let an explicit favicon choice retire the derived one by @yannickmonney in https://github.com/tale-project/tale/pull/4365\n* fix(platform): preserve task archive intervals in historical metrics by @yannickmonney in https://github.com/tale-project/tale/pull/4259\n* docs: add v0.5.73 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4368\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.72...v0.5.73","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.73","publishedAt":"2026-10-05T15:13:03Z"},{"tag":"v0.5.72","version":"0.5.72","name":null,"body":"## Highlights\n\n- Chat can use OpenAI's GPT-6 models. GPT-6 Astra, GPT-6 Sol, GPT-6 Luna and GPT-6.1 Sol are\n  offered through an OpenAI API key or environment-variable credential, and Tale calls Astra and\n  GPT-6.1 Sol through the Responses API, the only way they make tool calls. Project agents and\n  automations run those two on Codex; another runtime now says so instead of refusing without a\n  reason. Subscription credentials are marked **Tasks and automations only** in Settings, and the\n  chat model list names the subscriptions it leaves out.\n  [Run the GPT-6 models](https://docs.tale.dev/platform/admin/providers#run-the-gpt-6-models)\n  · [#4181](https://github.com/tale-project/tale/pull/4181)\n- A standing task that stays in **Backlog** or **To do** while its agent works is retried\n  automatically after a retryable failure, as long as its status, assignee, archive state and\n  review stay unchanged. Before, the task kept reporting a pending retry and held an idle agent.\n  [Triggers](https://docs.tale.dev/platform/automations/triggers)\n  · [#4187](https://github.com/tale-project/tale/pull/4187)\n- A workflow step whose sandbox workspace is being destroyed now fails at once with the reason,\n  instead of waiting up to two hours for room and then running in a fresh, empty workspace.\n  [Sandboxes](https://docs.tale.dev/platform/admin/sandboxes)\n  · [#4144](https://github.com/tale-project/tale/pull/4144)\n- A data-subject erasure completes only once the sandbox service confirms that the person's agent\n  workspaces are deleted. Until then its receipt reads **Partial**, and **Retry** finishes it\n  later. The audit log's **Sandbox workspace deleted** event also waits for that confirmation.\n  [Data subject requests](https://docs.tale.dev/platform/admin/governance/data-subject-requests)\n  · [#4094](https://github.com/tale-project/tale/pull/4094)\n- Sandboxes hold up better under sustained load. They release finished command output,\n  disconnected readers and stalled streams, cap held-open input at 8 MiB, rotate the logs of\n  nested Docker containers, replay long runs faster and no longer repeat cleanup work that used\n  CPU. A package-cache volume deleted shortly after it was prepared comes back writable, so\n  package installs no longer fail with permission errors.\n  [#4174](https://github.com/tale-project/tale/pull/4174),\n  [#4182](https://github.com/tale-project/tale/pull/4182),\n  [#4184](https://github.com/tale-project/tale/pull/4184),\n  [#4185](https://github.com/tale-project/tale/pull/4185),\n  [#4188](https://github.com/tale-project/tale/pull/4188),\n  [#4154](https://github.com/tale-project/tale/pull/4154)\n- The sandbox runtime updates all nine bundled agent runtimes (Claude Code 2.1.285, Codex 0.160.0,\n  Cursor 2026.10.01, Gemini CLI 0.62.0, Hermes 0.19.0, OpenClaw 2026.9.8, OpenCode 1.18.34,\n  Pi 1.0.1 and Qwen Code 0.24.7) on Node 24.21.0. Qwen Code no longer starts the Playwright MCP\n  server on every turn; the server starts with the first browser tool call.\n  [Harnesses](https://docs.tale.dev/platform/agents/harnesses)\n  · [#4140](https://github.com/tale-project/tale/pull/4140)\n- Fixes you can see in the app:\n  - **Add credential** shows a loading state while the provider or connector catalog loads,\n    instead of reporting that the deployment ships no provider files.\n    [#4161](https://github.com/tale-project/tale/pull/4161)\n  - Home drops a deleted or archived project's tasks and chats without a reload\n    ([#4162](https://github.com/tale-project/tale/pull/4162)), and shows the **Draft** badge for\n    a draft of literal code such as `<Button />`\n    ([#4204](https://github.com/tale-project/tale/pull/4204)).\n  - Comments written by automations are labelled as automation instead of \"Deleted agent\".\n    [#4164](https://github.com/tale-project/tale/pull/4164)\n  - A recorded approval decision no longer answers with an error when waking the run fails\n    afterwards, and the approval card offers **Try again** when the approval cannot be loaded or\n    keeps **Approve** and **Reject** when a decision was not recorded.\n    [Approvals in workflows](https://docs.tale.dev/platform/automations/approvals-in-workflows)\n    · [#3881](https://github.com/tale-project/tale/pull/3881)\n  - Adding teams to a project's audience one after another keeps every team\n    ([#4197](https://github.com/tale-project/tale/pull/4197)), and a renamed team shows its new\n    name in the open detail panel and editor\n    ([#4193](https://github.com/tale-project/tale/pull/4193)).\n  - Product **Create** stays disabled while the product is saved, so a repeated click cannot send\n    a second request ([#4198](https://github.com/tale-project/tale/pull/4198)), and the product\n    list formats prices in the reader's locale\n    ([#4205](https://github.com/tale-project/tale/pull/4205)).\n  - A queued or failed outbound mail and a chat message keep the attachment bytes they list, so\n    deleting the file no longer breaks the send or a later chat turn.\n    [#4157](https://github.com/tale-project/tale/pull/4157)\n  - A website's scan-failure alert no longer comes and goes with the page filter\n    ([#4135](https://github.com/tale-project/tale/pull/4135)), and the arrow, Home and End keys\n    select the focused option of a segmented control such as that filter\n    ([#4163](https://github.com/tale-project/tale/pull/4163)).\n- The front pages of the website, the documentation and the UI documentation are reworked, and\n  the pricing table names the data processing agreement as the service only Enterprise includes.\n  [#4139](https://github.com/tale-project/tale/pull/4139),\n  [#4148](https://github.com/tale-project/tale/pull/4148)\n- Operators can sample backend HTTP and worker spans through the existing Sentry integration. It\n  is off by default.\n  [Observability](https://docs.tale.dev/self-hosted/configuration/observability-config)\n\n## Upgrade notes\n\n- The backend applies database migration `0149` when it starts. It adds the state that standing\n  task retries need and an index on task activity, and it retires the pending retries of failed\n  standing-task runs from earlier versions without starting catch-up work; their history stays.\n  Building the index can lengthen the first start on an instance with a long task history.\n- Two optional environment variables, documented in the\n  [environment reference](https://docs.tale.dev/self-hosted/configuration/environment-reference),\n  change nothing until you set them:\n  - `SANDBOX_DOCKER_WORKLOADS` limits which workloads may start Docker inside the sandbox when\n    the deployment enables it. The default, `project,workflow`, keeps today's behavior; `project`\n    runs workflow agents without Docker. It applies to sessions created afterwards. The\n    repository's `compose.yml` and `tale deploy` pass it to the sandbox service; a hand-written\n    Compose file or manifest must pass it as well.\n  - `BACKEND_SENTRY_TRACES_SAMPLE_RATE`, `0` by default, samples backend HTTP and worker spans.\n    It needs `SENTRY_DSN` and a destination that accepts Sentry transactions. Recreate the\n    backend API and worker services after changing it.\n- Without an explicit `SANDBOX_AGENT_MEMORY`, a sandbox session without Docker inside now\n  defaults to `4g` of memory and a session with Docker to `8g`, even when other sessions in the\n  deployment use Docker.\n- The sandbox runtime image grows to about 6.0 GB on amd64 with the refreshed agent runtimes.\n  Allow for the larger pull and disk use.\n- Update the sandbox service and connected sandbox devices together with the platform. Older\n  versions cannot confirm workspace deletion, so erasure receipts remain **Partial** until those\n  services and devices are updated; retrying alone cannot fix the missing confirmation.\n- On Kubernetes, sandbox Pods that are still Pending count against admission, and maintenance\n  removes a Pod left Pending past its startup deadline plus 60 seconds while keeping its\n  workspace volume. A deleted workspace counts as erased once its volume is handed to the storage\n  provisioner; with a `Retain` reclaim policy, its files stay until you remove the volume.\n  [Kubernetes](https://docs.tale.dev/self-hosted/install/kubernetes)\n- Known limitations: bytes that a queued mail or a chat message keeps are not removed when that\n  mail or chat ends, and a chat attachment can keep a document's bytes after a retention\n  destruction ([#4208](https://github.com/tale-project/tale/issues/4208)). GPT-6 models in chat\n  do not carry their reasoning across tool rounds within a turn.\n- The API contract stays at 3.15.0.\n\n## API contract changes\n\nNone in this range. The contract stays at 3.15.0: 139 operations.\n\n\n## What's Changed\n* fix(sandbox): release resources safely under concurrent load by @yannickmonney in https://github.com/tale-project/tale/pull/4150\n* fix(platform): end itest /events tails and exit a truncated run by @yannickmonney in https://github.com/tale-project/tale/pull/4133\n* docs(manual): document the mock provider wiring for setup mode A by @yannickmonney in https://github.com/tale-project/tale/pull/4137\n* test(platform): keep the RAG sweep's clock off the watchdog lanes by @yannickmonney in https://github.com/tale-project/tale/pull/4145\n* fix(platform): fail a step whose workspace is being destroyed by @yannickmonney in https://github.com/tale-project/tale/pull/4144\n* fix(sandbox): keep Qwen Code discovery from starting Playwright MCP by @yannickmonney in https://github.com/tale-project/tale/pull/4140\n* feat(marketing-ui): rework the public website front pages by @yannickmonney in https://github.com/tale-project/tale/pull/4139\n* fix: reduce stream retention and add performance telemetry by @yannickmonney in https://github.com/tale-project/tale/pull/4156\n* fix(sandbox): refresh and harden all bundled harnesses by @yannickmonney in https://github.com/tale-project/tale/pull/4155\n* fix(sandbox): keep erasure pending until workspace bytes are deleted by @yannickmonney in https://github.com/tale-project/tale/pull/4094\n* fix(platform): decide a website's scan alert from the site's counts by @yannickmonney in https://github.com/tale-project/tale/pull/4135\n* fix(sandbox): repair pruned cache volumes, keep the destroy mount lookup by @yannickmonney in https://github.com/tale-project/tale/pull/4154\n* fix(web): name the DPA in the pricing table and resync de-CH by @yannickmonney in https://github.com/tale-project/tale/pull/4148\n* fix(platform): keep bytes a pending mail or a chat message lists by @yannickmonney in https://github.com/tale-project/tale/pull/4157\n* fix(sandbox): bound agent execution and recover session state by @yannickmonney in https://github.com/tale-project/tale/pull/4174\n* feat(platform): run GPT-6 models in chat, subscriptions only in tasks by @yannickmonney in https://github.com/tale-project/tale/pull/4181\n* fix(sandbox): release retained command memory and bound stdin by @yannickmonney in https://github.com/tale-project/tale/pull/4185\n* docs(manual): qualify database-unavailable reporting expectations by @yannickmonney in https://github.com/tale-project/tale/pull/4180\n* fix(sandbox): bound logs and repair persistent storage cleanup by @yannickmonney in https://github.com/tale-project/tale/pull/4182\n* fix: show catalog loading state in credential picker by @yannickmonney in https://github.com/tale-project/tale/pull/4161\n* fix(platform): invalidate home lists on project hints by @yannickmonney in https://github.com/tale-project/tale/pull/4162\n* fix(platform): label reserved workflow comment authors as automation by @yannickmonney in https://github.com/tale-project/tale/pull/4164\n* docs(manual): match RESP-F23 and RESP-F24 to the phone Home by @yannickmonney in https://github.com/tale-project/tale/pull/4175\n* fix(sandbox): speed replay and harden session streams by @yannickmonney in https://github.com/tale-project/tale/pull/4188\n* fix(ui): check segmented options on keyboard navigation by @yannickmonney in https://github.com/tale-project/tale/pull/4163\n* fix(cli): verify release runs from the canonical main merge by @yannickmonney in https://github.com/tale-project/tale/pull/4074\n* fix(platform): retain saved project audience before readback by @yannickmonney in https://github.com/tale-project/tale/pull/4197\n* fix(sandbox): bound replay consumers and preserve agent output by @yannickmonney in https://github.com/tale-project/tale/pull/4170\n* fix(sandbox): prevent accumulated cleanup and streaming CPU work by @yannickmonney in https://github.com/tale-project/tale/pull/4184\n* fix(platform): preserve product create submission lifecycle by @yannickmonney in https://github.com/tale-project/tale/pull/4198\n* fix(platform): resolve selected teams from current list by @yannickmonney in https://github.com/tale-project/tale/pull/4193\n* fix(platform): retry unchanged standing agent tasks by @yannickmonney in https://github.com/tale-project/tale/pull/4187\n* fix(platform): preserve literal plaintext draft badges in Home by @yannickmonney in https://github.com/tale-project/tale/pull/4204\n* fix(platform): format product list prices in the reader's locale by @yannickmonney in https://github.com/tale-project/tale/pull/4205\n* fix(platform): preserve approval decisions and scope card recovery by @yannickmonney in https://github.com/tale-project/tale/pull/3881\n* fix(platform): narrow the contacts list by its locale filter by @yannickmonney in https://github.com/tale-project/tale/pull/4207\n* fix(platform): date the Indexed dialog from epoch milliseconds by @yannickmonney in https://github.com/tale-project/tale/pull/4206\n* fix(platform): apply the row menu's rule to contact bulk delete by @yannickmonney in https://github.com/tale-project/tale/pull/4215\n* fix(sandbox): harden agent runs and resource recovery by @yannickmonney in https://github.com/tale-project/tale/pull/4189\n* fix: gate package tags on release notes and trim Prepare's checkout by @yannickmonney in https://github.com/tale-project/tale/pull/4179\n* fix(platform): isolate project overview drafts on navigation by @yannickmonney in https://github.com/tale-project/tale/pull/4229\n* fix(platform): reject blank DSAR cooling-off hours by @yannickmonney in https://github.com/tale-project/tale/pull/4213\n* fix(platform): preserve attachments when editing chat messages by @yannickmonney in https://github.com/tale-project/tale/pull/4223\n* fix(platform): clear the product fields an edit empties by @yannickmonney in https://github.com/tale-project/tale/pull/4230\n* fix(platform): preserve reviewer focus and explain policy refusals by @yannickmonney in https://github.com/tale-project/tale/pull/4232\n* fix(platform): refuse comment and dependency writes on archived tasks by @yannickmonney in https://github.com/tale-project/tale/pull/4219\n* docs: add v0.5.72 release notes by @yannickmonney in https://github.com/tale-project/tale/pull/4228\n* fix(platform): arbitrate concurrent knowledge entry writes by @yannickmonney in https://github.com/tale-project/tale/pull/4225\n* fix(platform): refuse a malformed body on project duplicate by @yannickmonney in https://github.com/tale-project/tale/pull/4236\n* docs(platform): align phone Home project narrowing guides by @yannickmonney in https://github.com/tale-project/tale/pull/4240\n* fix(platform): show active automation cursor by @yannickmonney in https://github.com/tale-project/tale/pull/4245\n* fix(platform): preserve task comment drafts edited while saving by @yannickmonney in https://github.com/tale-project/tale/pull/4246\n* fix(platform): require an identity when creating contacts by @yannickmonney in https://github.com/tale-project/tale/pull/4247\n* fix(platform): discard a pending image upload with its product draft by @yannickmonney in https://github.com/tale-project/tale/pull/4253\n* fix(platform): read quoted CSV cells across line breaks by @yannickmonney in https://github.com/tale-project/tale/pull/4235\n* fix(platform): redact sandbox placements for developers by @yannickmonney in https://github.com/tale-project/tale/pull/4234\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.71...v0.5.72","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.72","publishedAt":"2026-10-04T21:17:19Z"},{"tag":"v0.5.71","version":"0.5.71","name":null,"body":"## Highlights\n\n- Codex agents can select GPT-6.1 Sol through an OpenAI subscription credential. The model\n  catalog now records its Responses API requirement, so incompatible native chat and agent\n  runtimes refuse it before sending a tool request.\n  [Provider setup](https://docs.tale.dev/platform/admin/providers)\n- Subscription broker cooldowns immediately following the same agent's rate-limit failure\n  no longer consume an extra automated-start allowance. Manager agents can read whether a\n  failed task run still has a pending automatic retry.\n  [#4143](https://github.com/tale-project/tale/pull/4143)\n- Self-hosted setup gains read-only `tale doctor` diagnostics, more reliable startup and\n  local host/port handling, and `tale dev --stop` to stop development containers while\n  preserving their data. Unix and Windows installation guidance includes recovery steps.\n  [#4142](https://github.com/tale-project/tale/pull/4142)\n- Home names a failed chat or task read and offers a retry instead of presenting an empty\n  result. [#4136](https://github.com/tale-project/tale/pull/4136)\n- Cleanup retains file bytes while another live reference still needs them.\n  [#4129](https://github.com/tale-project/tale/pull/4129)\n- Project owners can see why the agent review setting is unavailable and which organization\n  grant enables it. [#4132](https://github.com/tale-project/tale/pull/4132)\n- GitHub and package discovery now point readers to the published guides, and full releases\n  require authored highlights and upgrade notes before publication.\n  [#4134](https://github.com/tale-project/tale/pull/4134)\n\n## Upgrade notes\n\n- The changes from v0.5.70 add no database migration or required environment variable. Follow\n  the [self-hosted upgrade guide](https://docs.tale.dev/self-hosted/operate/upgrades) to install\n  the updated model catalog.\n- To use GPT-6.1 Sol with a subscription, configure an OpenAI subscription credential and\n  choose Codex for the agent. The subscription must include access to this model. Direct chat\n  uses Chat Completions and does not support this model's tool calls.\n- Native task tools use contract 3.15.0. Managers must preserve an armed automatic retry\n  when `task_get.agentRuns[].retryPending` is `true`; `false` does not authorize a restart,\n  and a missing field on an older deployment means unknown. Reconcile current task state\n  and respect provider waits before starting work.\n- Release operators must merge the intended version's authored notes before selecting its\n  candidate. See [the release runbook](https://github.com/tale-project/tale/blob/main/.github/RELEASING.md).\n\n## API contract changes\n\nNone in this range. The contract stays at 3.15.0: 139 operations.\n\n\n## What's Changed\n* fix(platform): say why an ungranted agent cannot be the reviewer by @larryro in https://github.com/tale-project/tale/pull/4132\n* feat: improve GitHub discovery and current release metadata by @yannickmonney in https://github.com/tale-project/tale/pull/4134\n* fix(platform): delete cleanup bytes only when nothing holds them by @yannickmonney in https://github.com/tale-project/tale/pull/4129\n* fix(platform): name a failed Home read instead of an empty view by @yannickmonney in https://github.com/tale-project/tale/pull/4136\n* feat(cli): smooth self-hosted setup and recovery by @yannickmonney in https://github.com/tale-project/tale/pull/4142\n* fix(platform): reconcile cooldown and task retry state by @yannickmonney in https://github.com/tale-project/tale/pull/4143\n* fix: preserve discovery links and package metadata by @yannickmonney in https://github.com/tale-project/tale/pull/4141\n* docs(cli): keep onboarding compatible with the released CLI by @yannickmonney in https://github.com/tale-project/tale/pull/4151\n* fix(platform): enable Sol 6.1 through OpenAI subscriptions by @yannickmonney in https://github.com/tale-project/tale/pull/4149\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.70...v0.5.71","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.71","publishedAt":"2026-10-03T16:59:51Z"},{"tag":"v0.5.70","version":"0.5.70","name":null,"body":"## API contract changes\n\nThe contract moved from 3.9.0 to 3.14.0 (139 → 139 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n3.14.0 — 2026-10-03: a run's `waitingFor` gains `room`, an agent step whose start waits for sandbox room (its `detail` reads `room:<nodeId>`) where it read `agent` before; no one to page. Additive.\n\n\n## What's Changed\n* fix(platform): show a failed website scan as a red alert with its reason by @larryro in https://github.com/tale-project/tale/pull/4064\n* feat(platform): let project agents triage task metadata by @yannickmonney in https://github.com/tale-project/tale/pull/4072\n* fix(platform): treat an embedding 429 as a rate limit, pace and retry it by @larryro in https://github.com/tale-project/tale/pull/4071\n* fix(platform): stop subscribing every backend read to the session probe by @yannickmonney in https://github.com/tale-project/tale/pull/4067\n* feat(platform): fold the Home panel from a project's page by @larryro in https://github.com/tale-project/tale/pull/4075\n* fix(platform): let audit logs be kept for 180 days by @yannickmonney in https://github.com/tale-project/tale/pull/4080\n* fix(platform): bound video toolchain provisioning and its children by @yannickmonney in https://github.com/tale-project/tale/pull/4079\n* fix(platform): keep task attachment bytes alive and name a gone one by @larryro in https://github.com/tale-project/tale/pull/4085\n* fix(sandbox): delete a destroyed workspace in the background by @yannickmonney in https://github.com/tale-project/tale/pull/4088\n* fix(platform): run a sandbox Destroy as a job, not in the request by @yannickmonney in https://github.com/tale-project/tale/pull/4087\n* feat(platform): let independent agents review task results by @yannickmonney in https://github.com/tale-project/tale/pull/4076\n* fix: name budget-rule keys, explain empty chat replies, land docs labels by @yannickmonney in https://github.com/tale-project/tale/pull/4012\n* feat(platform): guard repairs from settled agent reviews by @yannickmonney in https://github.com/tale-project/tale/pull/4100\n* fix(platform): refuse new work in a sandbox being destroyed by @yannickmonney in https://github.com/tale-project/tale/pull/4095\n* fix(platform): contain video-toolchain children that leave their group by @yannickmonney in https://github.com/tale-project/tale/pull/4096\n* fix(deps): clear the braces, OpenTelemetry core and colord advisories by @yannickmonney in https://github.com/tale-project/tale/pull/4108\n* fix(platform): reclaim a rejected upload only when nothing holds it by @yannickmonney in https://github.com/tale-project/tale/pull/4109\n* fix(sandbox): free idle compute, bound helpers, admit by memory and disk by @yannickmonney in https://github.com/tale-project/tale/pull/4098\n* fix(platform): wait for sandbox room instead of failing the run by @yannickmonney in https://github.com/tale-project/tale/pull/4099\n* fix(sandbox): end the processes an exec leaves behind by @yannickmonney in https://github.com/tale-project/tale/pull/4097\n* perf(sandbox): ask the docker daemon less on every session create by @yannickmonney in https://github.com/tale-project/tale/pull/4116\n* fix(platform): keep a product image's bytes while anything holds them by @yannickmonney in https://github.com/tale-project/tale/pull/4115\n* perf(sandbox): start the Playwright MCP server only when a turn uses it by @yannickmonney in https://github.com/tale-project/tale/pull/4117\n* perf(sandbox): run the Codex binary without its Node launcher by @yannickmonney in https://github.com/tale-project/tale/pull/4118\n* docs: position Tale around collaborative agent work by @yannickmonney in https://github.com/tale-project/tale/pull/4119\n* feat(web): redesign marketing sites and publish localized guides by @yannickmonney in https://github.com/tale-project/tale/pull/4120\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.69...v0.5.70","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.70","publishedAt":"2026-10-03T13:00:33Z"},{"tag":"v0.5.69","version":"0.5.69","name":null,"body":"## Highlights\n\n- Find failed or skipped website pages directly from the crawl summary. The page list now has\n  All, Failed and Skipped filters with counts and separate pagination, so investigating an\n  incomplete crawl no longer requires paging through successful results. [#4065](https://github.com/tale-project/tale/pull/4065)\n- Task notifications now follow current project access. Someone removed from a restricted\n  project stops receiving new task details, and queued email checks access again before sending.\n  Existing notification history and subscriptions are retained; notifications resume if access\n  is restored. [#4056](https://github.com/tale-project/tale/pull/4056)\n\n## Upgrade notes\n\n- The changes from v0.5.68 add no database migration or environment variable. Follow the\n  [self-hosted upgrade guide](https://docs.tale.dev/self-hosted/operate/upgrades) for your deployment.\n- REST integrations can filter website pages with `state=failed` or `state=skipped`. Keep the\n  same filter when following a pagination cursor; a cursor from another filter is refused.\n  [API reference](https://docs.tale.dev/develop/api-reference)\n\n## API contract changes\n\nNone in this range. The contract stays at 3.9.0: 139 operations.\n\n\n## What's Changed\n* feat(platform): filter a website's pages to the failed or skipped ones by @larryro in https://github.com/tale-project/tale/pull/4065\n* fix(platform): recheck task access before notification delivery by @yannickmonney in https://github.com/tale-project/tale/pull/4056\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.68...v0.5.69","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.69","publishedAt":"2026-10-02T16:46:36Z"},{"tag":"v0.5.68","version":"0.5.68","name":null,"body":"## API contract changes\n\nThe contract moved from 3.8.0 to 3.9.0 (139 → 139 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n3.9.0 — 2026-10-01: project agents answer `managed` — true for the organization's standard agent, which Tale sets up in a project without agents of its own under the new `standard_agent` governance policy and keeps in line with it. Saving one answers 409 `PROJECT_AGENT_MANAGED`; deleting it works as for any agent. A review relay that moves a task the standard agent holds to `in_progress` can answer `STANDARD_AGENT_OFF` (403) or `STANDARD_AGENT_UNAVAILABLE` (409). Additive.\n\n\n## What's Changed\n* test(web): pin the release feed in the changelog timeline tests by @yannickmonney in https://github.com/tale-project/tale/pull/4024\n* feat(platform): hand a chat's work to a project agent in one step by @yannickmonney in https://github.com/tale-project/tale/pull/4023\n* ci(e2e): retain diagnostics after recovered flakes by @yannickmonney in https://github.com/tale-project/tale/pull/4022\n* test(platform): keep the backend:integration lanes off real vendors by @yannickmonney in https://github.com/tale-project/tale/pull/4011\n* feat(platform): delete sandbox workspaces whose owner is gone or unused by @yannickmonney in https://github.com/tale-project/tale/pull/4028\n* feat(platform): give projects without agents a standard agent by @yannickmonney in https://github.com/tale-project/tale/pull/4029\n* fix(platform): read Gmail envelopes for the inbox triage digest by @larryro in https://github.com/tale-project/tale/pull/4034\n* fix(platform): stop reporting restart answers from the browser by @yannickmonney in https://github.com/tale-project/tale/pull/4033\n* fix(platform): resolve chat models past an unreadable provider catalog by @yannickmonney in https://github.com/tale-project/tale/pull/4021\n* feat(platform): triage the Inbox and draft replies in the mail packs by @larryro in https://github.com/tale-project/tale/pull/4036\n* test(platform): keep a held delegated start off the audit chain head by @larryro in https://github.com/tale-project/tale/pull/4027\n* fix(platform): refresh the standard agent's skills at each start by @yannickmonney in https://github.com/tale-project/tale/pull/4039\n* fix(platform): scope the mail sync to the Inbox and learn its address by @larryro in https://github.com/tale-project/tale/pull/4038\n* fix(platform): wait out a restarting database when the backend boots by @yannickmonney in https://github.com/tale-project/tale/pull/4031\n* docs(manual): recount the platform suites after #4036 and #4038 by @yannickmonney in https://github.com/tale-project/tale/pull/4043\n* fix(deps): update dependency hono to v4.13.7 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4026\n* fix(platform): report timed-out knowledge probes as down by @yannickmonney in https://github.com/tale-project/tale/pull/4046\n* fix(platform): keep a governance switch from reverting a newer save by @yannickmonney in https://github.com/tale-project/tale/pull/4057\n* fix(deps): keep the backend alive when a Postgres server process dies by @yannickmonney in https://github.com/tale-project/tale/pull/4061\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.67...v0.5.68","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.68","publishedAt":"2026-10-02T04:10:57Z"},{"tag":"v0.5.67","version":"0.5.67","name":null,"body":"4 PRs since v0.5.66. Two migrations (0142, 0143), both rolling-deploy safe, applied at boot. No proxy, compose or `.env` change: `tale update`, then `tale deploy` as usual.\n\n## Projects and tasks\n\n- A run that fails for good is said and told: the task shows **The agent couldn't finish this task** with the cause, who can fix it, **Details** and **Retry**; the run's starter and the task's watchers get the new **Agent run failed** notification (email when a mailbox is connected), under the existing **Agent escalations** preference (#4017)\n- The retry job retires every run it refuses for good (budget spent, agent gone, starter lost the right, circuit breaker, busy agent) and announces it once; a run card reads a failure as pending only while an armed retry is coming (#4017)\n- Run cards follow every state change live; the 2 s poll is gone. Starting a run marks the task's unread failure notifications read (#4017)\n- **Assignee** in a project without agents offers Editors **Create an agent…** (opens **New agent** over the task, keeps the draft, assigns the new agent); everyone else is told who can add one. The coding-harness warning is gone (#4017)\n- Members get a reader state on the **Agents** tab; after assigning, whoever can start the agent sees **Start agent** and \"The agent waits until you start it.\" (#4017)\n- The bell and `GET /api/v1/notifications/sync` name task statuses by the board's labels instead of raw ids (#4017)\n\n## Knowledge — websites\n\n- Rendered pages no longer fail with `net::ERR_ABORTED` after two or three pages: the crawler's browser session gets room for Chromium (pids cap 128 → 512) (#4016)\n- Page text keeps its word boundaries; a site that splits its text into inline tags is indexed as words, not letters (#4016)\n- Unclosed tags and repeated meta attributes convert in milliseconds instead of blocking the backend for minutes (#4016)\n- A redirect within the site is indexed once, under the address it lands on (#4016)\n- Pages crawled before an embedding model existed are embedded by the next scan; saving a model or repairing its credential rescans the affected sources; without a model the Websites page says **Chat can't search these websites yet** (#4016)\n- Scans of different sites run in parallel, carry a heartbeat, and resume within minutes after a restart, deploy or kill instead of sitting on **Scanning** for two hours (#4016)\n- **Scan now** in the row menu and the details dialog; the table follows a running scan without a reload (#4016)\n- Website write routes require `knowledgeWrite`: read-only Members get `403 RBAC_FORBIDDEN` (#4016)\n- Embedding failures read \"The embedding model couldn't process the pages.\" with the cause on hover; a browser that cannot start fails the batch with its own stderr instead of re-rendering for hours (#4016)\n- Pasted addresses with surrounding spaces or `HTTPS://` are accepted (#4016)\n\n## Agents — Gemini CLI\n\n- Every Gemini CLI kick (comment, automatic retry, answered ask, comment restart) starts a fresh conversation over the preserved workspace: the pinned CLI replays tool results twice on `--resume` (google-gemini/gemini-cli#29365) and the resumed conversation's first model call was refused (#4025)\n- Harness YAML gains `capabilities.resume`; the exec builder refuses a resume handle on a harness that declares `false` (#4025)\n- The Gemini parser stamps the provider's status (400, 429, …) on a failed turn, so the run's reason reads the provider's sentence and the kick's \"400 → start fresh\" rule fires (#4025)\n\n## Docs and developer surfaces\n\n- Docs (EN/DE/FR): website crawling (search requirement, Scan now, redirects, restart behaviour, troubleshooting), task automation (\"When the agent can't finish\"), project agents, harnesses (#4016, #4017, #4025)\n- `@tale/ui` exports `use-trigger-tooltip-guard` (#4017)\n- CLI release-fixture Git steps are bounded at 15 s and report a diagnostic instead of a bare SIGTERM (#4018)\n\n## API contract changes\n\nNone in this range. The contract stays at 3.8.0: 139 operations.","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.67","publishedAt":"2026-10-01T04:17:27Z"},{"tag":"v0.5.66","version":"0.5.66","name":null,"body":"## API contract changes\n\nNone in this range. The contract stays at 3.8.0: 139 operations.\n\n\n## What's Changed\n* ci(platform): retain backend integration evidence by @yannickmonney in https://github.com/tale-project/tale/pull/4002\n* fix(platform): fail the image build when its production install fails by @yannickmonney in https://github.com/tale-project/tale/pull/4004\n* ci(platform): give the Type check job a 6 GiB Node heap by @yannickmonney in https://github.com/tale-project/tale/pull/4007\n* fix(sandbox): pin Gemini CLI to 0.59.0 by @larryro in https://github.com/tale-project/tale/pull/4001\n* fix(platform): hand over a drained job only once its claim ended by @yannickmonney in https://github.com/tale-project/tale/pull/4003\n* chore(deps): update dependency axios to v1.20.0 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4009\n* fix(deps): update dependency dompurify to v3.4.16 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/4008\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.65...v0.5.66","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.66","publishedAt":"2026-09-30T18:46:08Z"},{"tag":"v0.5.65","version":"0.5.65","name":null,"body":"## API contract changes\n\nNone in this range. The contract stays at 3.8.0: 139 operations.\n\n\n## What's Changed\n* docs(docs): retake all screenshots and fill the docs gaps they exposed by @yannickmonney in https://github.com/tale-project/tale/pull/3879\n* ci: refuse committed merge-conflict markers by @yannickmonney in https://github.com/tale-project/tale/pull/3962\n* chore(deps): update dependency brace-expansion to v5.0.12 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3965\n* chore(deps): update dependency fast-uri to v3.1.8 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3966\n* fix(deps): update dependency nodemailer to v10.0.9 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3967\n* fix(sandbox): move the model gateway to Bifrost v2.2.4 by @yannickmonney in https://github.com/tale-project/tale/pull/3961\n* test(cli): isolate the CLI release tag cases from the runner identity by @yannickmonney in https://github.com/tale-project/tale/pull/3973\n* feat(platform): name the serving release in workspace_status by @yannickmonney in https://github.com/tale-project/tale/pull/3972\n* fix(platform): repeat a gateway call its busy store turned away by @yannickmonney in https://github.com/tale-project/tale/pull/3975\n* fix(platform): floor a cut call's cached prompt at the cache rate by @yannickmonney in https://github.com/tale-project/tale/pull/3976\n* fix(platform): stop the task dialog's close flash and board re-renders by @yannickmonney in https://github.com/tale-project/tale/pull/3948\n* feat(platform): enhance responsive Home list navigation & project scope filtering by @Israeltheminer in https://github.com/tale-project/tale/pull/3960\n* fix(deps): update dependency ip-address to v10.7.1 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3968\n* test(platform): require every backend:integration lane to run and pass by @yannickmonney in https://github.com/tale-project/tale/pull/3981\n* fix(platform): reconnect a lapsed cloud import, warn on a partial one by @yannickmonney in https://github.com/tale-project/tale/pull/3984\n* fix(platform): queue a comment's retry on the audit chain it ends on by @yannickmonney in https://github.com/tale-project/tale/pull/3980\n* fix(docs): keep every docs address answering and lint every link by @yannickmonney in https://github.com/tale-project/tale/pull/3986\n* fix(platform): keep Automations to owners, admins and developers by @yannickmonney in https://github.com/tale-project/tale/pull/3983\n* fix(platform): let OpenCode wait for a slow image generation by @yannickmonney in https://github.com/tale-project/tale/pull/3990\n* fix(platform): book every input token of an agent turn by @yannickmonney in https://github.com/tale-project/tale/pull/3994\n* fix(platform): decode Gmail bodies and render synced mail content by @larryro in https://github.com/tale-project/tale/pull/3987\n* fix(platform): tell agents the real size of each generated image by @yannickmonney in https://github.com/tale-project/tale/pull/3991\n* ci: run the real-Postgres integration suite in CI by @yannickmonney in https://github.com/tale-project/tale/pull/3988\n* fix(platform): preserve newer cloud import picker selections by @yannickmonney in https://github.com/tale-project/tale/pull/3995\n* fix(platform): recheck agent occupancy before an automated retry by @yannickmonney in https://github.com/tale-project/tale/pull/3985\n* fix(platform): stop charging crawled pages for render sandbox faults by @larryro in https://github.com/tale-project/tale/pull/3997\n* fix(platform): keep a flash-suffixed model id on Gemini CLI runs by @larryro in https://github.com/tale-project/tale/pull/3998\n* feat(platform): let operators size the crawler's document fetch cap by @larryro in https://github.com/tale-project/tale/pull/3999\n* fix(cli): capture the model gateway's store in snapshots and restore by @yannickmonney in https://github.com/tale-project/tale/pull/3979\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.64...v0.5.65","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.65","publishedAt":"2026-09-30T15:24:26Z"},{"tag":"v0.5.64","version":"0.5.64","name":null,"body":"## Publication incomplete\n\nThe image Release workflow passed, but all five tag-bound CLI builds failed synthetic tag-validation tests; this release has no CLI assets and is not ready for the Ops deployment pipeline. The cause is tracked in [#3970](https://github.com/tale-project/tale/issues/3970), with the [failed CLI run](https://github.com/tale-project/tale/actions/runs/36658878748) retained. The `v0.5.64` tag remains fixed at `ebf4546fb1455a236c1046f3d73ef78c2e1d0109`. [v0.5.65](https://github.com/tale-project/tale/releases/tag/v0.5.65) (`cc325fc5`) supersedes this incomplete publication: it carries the fixture fix (#3973) and publishes every image and CLI asset. Use v0.5.65.\n\n## API contract changes\n\nThe contract moved from 3.5.0 to 3.8.0 (136 → 139 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations:\n- `GET /api/v1/openai/models`\n- `POST /api/v1/anthropic/v1/messages`\n- `POST /api/v1/openai/chat/completions`\n\nRemoved operations: none.\n\n### Changelog\n\n3.8.0 — 2026-09-29: the project agent `tools` vocabulary gains `task_start_agent` — an agent's live run puts another agent of the same project to work on a task (assign, start, and a message the run addresses first), answering to whoever that run answers to. Additive: agents saved without it keep their grants.\n\n\n## What's Changed\n* fix(manual): recount the platform suite totals by @yannickmonney in https://github.com/tale-project/tale/pull/3807\n* fix(platform): close the #3785 mail stamp pass review findings by @yannickmonney in https://github.com/tale-project/tale/pull/3808\n* fix(platform): close the session-lapse leftovers after #3795 by @yannickmonney in https://github.com/tale-project/tale/pull/3804\n* fix(platform): close the RAG watchdog review round after #3782 by @yannickmonney in https://github.com/tale-project/tale/pull/3803\n* fix(platform): raise one toast per failed write and guard the rule by @yannickmonney in https://github.com/tale-project/tale/pull/3806\n* chore(deps): update dependency fast-uri to v3.1.7 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3796\n* docs(docs): retake the skills and governance screenshots by @yannickmonney in https://github.com/tale-project/tale/pull/3809\n* fix(deps): update dependency undici to v7.29.1 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3797\n* fix(deps): update dependency ip-address to v10.5.1 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3798\n* fix(deps): update dependency nodemailer to v10 [security] by @renovate[bot] in https://github.com/tale-project/tale/pull/3799\n* feat(platform): let agents generate images under an opt-in policy by @yannickmonney in https://github.com/tale-project/tale/pull/3811\n* fix(platform): keep custom provider edits whole and version-checked by @yannickmonney in https://github.com/tale-project/tale/pull/3778\n* fix(platform): make Inbox assignment, selection and activity reliable by @yannickmonney in https://github.com/tale-project/tale/pull/3784\n* feat(platform): let members create tasks and work their own by @yannickmonney in https://github.com/tale-project/tale/pull/3810\n* docs(docs): retake every docs screenshot and the README gallery by @larryro in https://github.com/tale-project/tale/pull/3817\n* feat(platform): serve OpenAI- and Anthropic-compatible model endpoints by @yannickmonney in https://github.com/tale-project/tale/pull/3812\n* fix(manual): recount the manual suite totals by @yannickmonney in https://github.com/tale-project/tale/pull/3859\n* docs(docs): capture image generation and the model endpoints by @yannickmonney in https://github.com/tale-project/tale/pull/3861\n* fix(platform): dedupe video links per chat, keep cues, explain refusals by @yannickmonney in https://github.com/tale-project/tale/pull/3884\n* fix(platform): close the #3806 cloud import and bulk send findings by @yannickmonney in https://github.com/tale-project/tale/pull/3905\n* fix(platform): search with the board filters and recover failed reads by @yannickmonney in https://github.com/tale-project/tale/pull/3906\n* fix(platform): one notch strip; say so when the account will not load by @yannickmonney in https://github.com/tale-project/tale/pull/3907\n* fix(platform): keep cleared and typed values in task history by @yannickmonney in https://github.com/tale-project/tale/pull/3813\n* fix(cli): stop publishing the sandbox's ports on managed hosts by @yannickmonney in https://github.com/tale-project/tale/pull/3815\n* fix(ui): give filter radios one Tab stop and the arrow keys by @yannickmonney in https://github.com/tale-project/tale/pull/3853\n* fix(platform): close the #3803 and #3808 review findings by @yannickmonney in https://github.com/tale-project/tale/pull/3931\n* fix(platform): recover failed knowledge, history and folder reads by @yannickmonney in https://github.com/tale-project/tale/pull/3814\n* fix(platform): refresh connector lists live, warn truthfully, keep focus by @yannickmonney in https://github.com/tale-project/tale/pull/3895\n* fix(platform): recheck queued retries and move live workflow parents by @yannickmonney in https://github.com/tale-project/tale/pull/3908\n* fix(sandbox): keep session container names within a DNS label by @yannickmonney in https://github.com/tale-project/tale/pull/3938\n* fix(platform): close the findings of the live feature test run by @yannickmonney in https://github.com/tale-project/tale/pull/3940\n* fix(platform): require the editor role for hub folder writes by @yannickmonney in https://github.com/tale-project/tale/pull/3936\n* fix(platform): hand a focused retry to the list when a refresh swaps it by @yannickmonney in https://github.com/tale-project/tale/pull/3937\n* fix(platform): hold API-key creation to one rule, on the server by @yannickmonney in https://github.com/tale-project/tale/pull/3941\n* test(ui): compare the resizable panel's width to half a pixel by @yannickmonney in https://github.com/tale-project/tale/pull/3945\n* feat(platform): create a task from a chat by @yannickmonney in https://github.com/tale-project/tale/pull/3946\n* fix(platform): keep a turn's images and model within one allowance by @yannickmonney in https://github.com/tale-project/tale/pull/3947\n* docs(docs): lead the projects tutorial with what a member can do by @yannickmonney in https://github.com/tale-project/tale/pull/3950\n* fix(platform): let OpenClaw wait for a slow image generation by @yannickmonney in https://github.com/tale-project/tale/pull/3952\n* fix(platform): read the chat before drafting its task by @yannickmonney in https://github.com/tale-project/tale/pull/3953\n* fix(platform): truthful skill reads, lossless labels, keyboard access by @yannickmonney in https://github.com/tale-project/tale/pull/3894\n* fix(platform): keep folders listed when their documents fail to load by @yannickmonney in https://github.com/tale-project/tale/pull/3944\n* fix(ui): handle keyboard selection in dropdown menu items with keepOpen by @Israeltheminer in https://github.com/tale-project/tale/pull/3957\n* fix(platform): pin action bar to sticky footer in compact automation editor by @Israeltheminer in https://github.com/tale-project/tale/pull/3958\n* feat(platform): remember section entry points across main navigation tiles by @Israeltheminer in https://github.com/tale-project/tale/pull/3959\n* fix(platform): type navigation restore history state by @yannickmonney in https://github.com/tale-project/tale/pull/3964\n* feat(platform): let a schedule or another agent start a project agent by @yannickmonney in https://github.com/tale-project/tale/pull/3942\n* feat(platform): let project agents page the queue and read run state by @yannickmonney in https://github.com/tale-project/tale/pull/3963\n* ci: validate complete pinned release candidates by @yannickmonney in https://github.com/tale-project/tale/pull/3956\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.63...v0.5.64","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.64","publishedAt":"2026-09-30T02:14:33Z"},{"tag":"v0.5.63","version":"0.5.63","name":null,"body":"## API contract changes\n\nThe contract moved from 3.3.0 to 3.5.0 (136 → 136 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n3.5.0 — 2026-09-29: an organization may reserve organization-wide skills. Under its `skill_sharing` policy (Editors and above, or owners and administrators, plus members granted the `tale:skills.publish` capability), `PUT /api/v1/skills/{slug}` answers 403 `SKILL_PUBLISH_FORBIDDEN` to a key holder outside that set for a save that would create a `visibility: org` skill, widen one to `org` or change an `org` skill in place; `GET /api/v1/me` answers `capabilities.skillPublish`.\n\n\n## What's Changed\n* feat(sandbox): bake the document skills' libraries into the runtime by @yannickmonney in https://github.com/tale-project/tale/pull/3787\n* feat(platform): show who created and last edited a skill by @yannickmonney in https://github.com/tale-project/tale/pull/3786\n* test: bound viewportAtRest and close the #3783 review's test gaps by @yannickmonney in https://github.com/tale-project/tale/pull/3802\n* feat(platform): let admins reserve organization-wide skill sharing by @yannickmonney in https://github.com/tale-project/tale/pull/3801\n* fix(cli): accept the sandbox device hub's loopback port in a runtime by @yannickmonney in https://github.com/tale-project/tale/pull/3805\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.62...v0.5.63","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.63","publishedAt":"2026-09-29T05:55:59Z"},{"tag":"v0.5.62","version":"0.5.62","name":null,"body":"## API contract changes\n\nThe contract moved from 2.1.0 to 3.3.0 (136 → 136 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n3.3.0 — 2026-09-28: `Task` carries its schedule and how it repeats, read only: `startDate` and `dueDate` (epoch ms, present when set), `repeat` — the new `TaskRepeat` rule (frequency, interval, anchors, `timezone`, and `createOn: \"dueDate\"` when the next task is also created on the due date) or null when the task does not repeat — and `repeatNextTaskId`, the task that continues its series once one exists. An approved review (`POST …/tasks/{taskId}/review`) that closes a repeating task continues its series as a close in the app does.\n\n\n## What's Changed\n* feat: run organizations' sandboxes on devices they connect by @yannickmonney in https://github.com/tale-project/tale/pull/3504\n* fix(platform): destroy the container of a failed agent session by @yannickmonney in https://github.com/tale-project/tale/pull/3505\n* fix(platform): make embedding credential refusals terminal by @yannickmonney in https://github.com/tale-project/tale/pull/3507\n* fix(platform): answer a database restart with 503, not a crash or 500 by @yannickmonney in https://github.com/tale-project/tale/pull/3506\n* feat(platform): fold project metrics from the live task rows by @larryro in https://github.com/tale-project/tale/pull/3508\n* fix(platform): treat a cancelled request as neither failure nor outage by @yannickmonney in https://github.com/tale-project/tale/pull/3510\n* fix(platform): recover a tab that outlived a deploy by @yannickmonney in https://github.com/tale-project/tale/pull/3509\n* fix(platform): bound conversation timestamps, tolerate stored ones by @yannickmonney in https://github.com/tale-project/tale/pull/3511\n* fix(platform): merge each live transcript flush into the op row by @yannickmonney in https://github.com/tale-project/tale/pull/3512\n* fix(platform): send an email that carries only an attachment by @yannickmonney in https://github.com/tale-project/tale/pull/3514\n* fix(platform): show members the password rules they are held to by @yannickmonney in https://github.com/tale-project/tale/pull/3515\n* fix(platform): report browser defects, not extension or refusal noise by @yannickmonney in https://github.com/tale-project/tale/pull/3513\n* fix(platform): spare a session with a live turn from the expiry sweep by @yannickmonney in https://github.com/tale-project/tale/pull/3516\n* fix(platform): re-target the pending review when the reviewer changes by @yannickmonney in https://github.com/tale-project/tale/pull/3517\n* feat(platform): audit each retention run and what it destroyed by @yannickmonney in https://github.com/tale-project/tale/pull/3518\n* fix(platform): name the title limit when task_create refuses by @yannickmonney in https://github.com/tale-project/tale/pull/3529\n* feat(platform): fan out @mentions in task descriptions by @yannickmonney in https://github.com/tale-project/tale/pull/3519\n* fix(platform): show the server's refusal reasons in the app by @yannickmonney in https://github.com/tale-project/tale/pull/3520\n* fix(platform): give synced files no extractor reads a terminal status by @yannickmonney in https://github.com/tale-project/tale/pull/3540\n* feat(platform): index inbound email bodies for chat retrieval by @yannickmonney in https://github.com/tale-project/tale/pull/3530\n* fix(platform): let task runs call connectors for the run's starter by @yannickmonney in https://github.com/tale-project/tale/pull/3528\n* fix(platform): log pg-boss's failed polls once per database outage by @yannickmonney in https://github.com/tale-project/tale/pull/3548\n* fix(platform): read the flat error envelope's code in backendFetch by @yannickmonney in https://github.com/tale-project/tale/pull/3539\n* chore(platform): label the dev toolchain step as external toolchains by @yannickmonney in https://github.com/tale-project/tale/pull/3521\n* fix(platform): stop reporting client aborts and unparseable bodies by @yannickmonney in https://github.com/tale-project/tale/pull/3536\n* fix(ui): answer 404 for a site path carrying a NUL byte by @yannickmonney in https://github.com/tale-project/tale/pull/3537\n* fix(platform): hold every stored date to the epoch bound by @yannickmonney in https://github.com/tale-project/tale/pull/3549\n* fix(platform): keep the ElevenLabs key out of the root .env.example by @yannickmonney in https://github.com/tale-project/tale/pull/3543\n* fix(platform): frame project sharing as settings field rows by @yannickmonney in https://github.com/tale-project/tale/pull/3522\n* test(platform): port the chat conversation search privacy suite by @yannickmonney in https://github.com/tale-project/tale/pull/3541\n* feat(platform): reap expired auth sessions once a day by @yannickmonney in https://github.com/tale-project/tale/pull/3545\n* fix(platform): sweep chat filter events under their retention policy by @yannickmonney in https://github.com/tale-project/tale/pull/3544\n* fix(platform): collect the render lane's failed sessions by @yannickmonney in https://github.com/tale-project/tale/pull/3547\n* fix(platform): disable filters over empty, unfiltered lists by @yannickmonney in https://github.com/tale-project/tale/pull/3525\n* feat(platform): make the contact-support URL configurable by @yannickmonney in https://github.com/tale-project/tale/pull/3523\n* fix(platform): give uploaded files no extractor reads a terminal status by @yannickmonney in https://github.com/tale-project/tale/pull/3556\n* fix(platform): retire the schedules and rows of deleted organizations by @yannickmonney in https://github.com/tale-project/tale/pull/3550\n* fix(platform): keep the org accent legible on every mark it paints by @yannickmonney in https://github.com/tale-project/tale/pull/3534\n* fix(docs): hash the docs tree in every task that reads it by @yannickmonney in https://github.com/tale-project/tale/pull/3553\n* feat(platform): refuse an embedding provider that cannot embed by @yannickmonney in https://github.com/tale-project/tale/pull/3527\n* fix(sandbox): give every harness the built-in visual-aspect-analyzer by @yannickmonney in https://github.com/tale-project/tale/pull/3535\n* fix(platform): give code and text previews an editor's line rhythm by @yannickmonney in https://github.com/tale-project/tale/pull/3524\n* fix(platform): name every refused limit in task tools and doors by @yannickmonney in https://github.com/tale-project/tale/pull/3546\n* feat(platform): add floating mobile navigation that compacts on scroll by @Israeltheminer in https://github.com/tale-project/tale/pull/3573\n* feat(platform): pause a schedule after five permanent failures in a row by @yannickmonney in https://github.com/tale-project/tale/pull/3533\n* refactor(platform): drop the retired sandbox tables by @yannickmonney in https://github.com/tale-project/tale/pull/3532\n* test(platform): keep shutdown clients alive through close by @yannickmonney in https://github.com/tale-project/tale/pull/3570\n* build(platform): hash the config tree and other files the tests read by @yannickmonney in https://github.com/tale-project/tale/pull/3560\n* fix(platform): page every settings list alike and create via addAction by @yannickmonney in https://github.com/tale-project/tale/pull/3531\n* fix(platform): restore reply files on undo and name their remove button by @yannickmonney in https://github.com/tale-project/tale/pull/3552\n* fix(platform): focus task dialogs when they open by @yannickmonney in https://github.com/tale-project/tale/pull/3562\n* fix(platform): name the refused field at the app doors and in forms by @yannickmonney in https://github.com/tale-project/tale/pull/3538\n* fix(platform): treat emailed attachments as mail in retrieval by @yannickmonney in https://github.com/tale-project/tale/pull/3555\n* fix(platform): make task list titles keyboard accessible by @yannickmonney in https://github.com/tale-project/tale/pull/3564\n* fix(platform): pin object-store to linux/amd64 by @Israeltheminer in https://github.com/tale-project/tale/pull/3571\n* fix(platform): list the caller's slugs on every org slug refusal by @yannickmonney in https://github.com/tale-project/tale/pull/3526\n* fix(docs): hash the ui i18n files the docs tests read by @yannickmonney in https://github.com/tale-project/tale/pull/3563\n* fix(platform): keep pinned sandboxes pinned through the drift reconcile by @yannickmonney in https://github.com/tale-project/tale/pull/3542\n* fix(platform): close the reviewer edge cases after re-targeting by @yannickmonney in https://github.com/tale-project/tale/pull/3554\n* fix(platform): preserve Inbox drafts after upload failures by @yannickmonney in https://github.com/tale-project/tale/pull/3566\n* fix(ui): stop a multi-button toast action from squeezing beside copy by @Israeltheminer in https://github.com/tale-project/tale/pull/3574\n* fix(platform): name task limits alike on every door and cut long imports by @yannickmonney in https://github.com/tale-project/tale/pull/3577\n* fix(platform): name a harness turn by the harness it ran on by @larryro in https://github.com/tale-project/tale/pull/3575\n* fix(platform): cancel pending Inbox editor work on teardown by @yannickmonney in https://github.com/tale-project/tale/pull/3578\n* fix(platform): extend 'Not supported' to thread attachments and old rows by @yannickmonney in https://github.com/tale-project/tale/pull/3579\n* fix(platform): send the live inbox editor document by @yannickmonney in https://github.com/tale-project/tale/pull/3568\n* fix(platform): take the audit chain before removing a trigger's run by @yannickmonney in https://github.com/tale-project/tale/pull/3594\n* fix(platform): never preload admin-only policies for a member by @yannickmonney in https://github.com/tale-project/tale/pull/3551\n* fix(platform): close the trigger doors of deleted organizations by @yannickmonney in https://github.com/tale-project/tale/pull/3576\n* fix(ui): answer 404 for a site path too long to name a file by @yannickmonney in https://github.com/tale-project/tale/pull/3648\n* test(platform): make the chat search fake parse the leg's SQL by @yannickmonney in https://github.com/tale-project/tale/pull/3649\n* test(platform): verify SOPS timeout termination directly by @yannickmonney in https://github.com/tale-project/tale/pull/3638\n* fix(platform): preserve drafts during session recovery by @yannickmonney in https://github.com/tale-project/tale/pull/3569\n* fix(platform): clear stale mail stamps in the nightly stamp pass by @yannickmonney in https://github.com/tale-project/tale/pull/3668\n* fix(platform): harden the configurable contact-support URL by @yannickmonney in https://github.com/tale-project/tale/pull/3650\n* fix(platform): keep source previews still and legible while they load by @yannickmonney in https://github.com/tale-project/tale/pull/3683\n* fix(sandbox): answer CPU usage on the first capacity poll by @larryro in https://github.com/tale-project/tale/pull/3690\n* test(platform): guard the inherited turbo inputs of the test task by @yannickmonney in https://github.com/tale-project/tale/pull/3686\n* fix(platform): close the schedule pause streak's review follow-ups by @yannickmonney in https://github.com/tale-project/tale/pull/3688\n* fix(platform): harden data migrations and scope RAG status hints by @yannickmonney in https://github.com/tale-project/tale/pull/3687\n* feat(platform): move automation versions into the workbench by @Israeltheminer in https://github.com/tale-project/tale/pull/3692\n* fix(ui): refine update toast action styling by @Israeltheminer in https://github.com/tale-project/tale/pull/3693\n* fix(platform): restore Safari viewport and lower mobile navigation by @Israeltheminer in https://github.com/tale-project/tale/pull/3694\n* fix(platform): name tasks, not ids, when dragging by keyboard by @yannickmonney in https://github.com/tale-project/tale/pull/3684\n* fix(platform): say a lapsed session in words on every surface by @yannickmonney in https://github.com/tale-project/tale/pull/3685\n* fix(platform): close the app-door refusal review follow-ups by @yannickmonney in https://github.com/tale-project/tale/pull/3696\n* test(platform): pin the chat search's empty term and cap edges by @yannickmonney in https://github.com/tale-project/tale/pull/3697\n* fix(platform): drain chat filter events past one batch per run by @yannickmonney in https://github.com/tale-project/tale/pull/3702\n* fix(ui): keep an open filter panel and a failed read's filter usable by @yannickmonney in https://github.com/tale-project/tale/pull/3703\n* fix(platform): describe project fields by their settings row's help by @yannickmonney in https://github.com/tale-project/tale/pull/3699\n* fix(platform): follow up task limits and imports after #3577 by @yannickmonney in https://github.com/tale-project/tale/pull/3689\n* fix(platform): prevent focus zoom in Safari browser tabs by @Israeltheminer in https://github.com/tale-project/tale/pull/3695\n* fix(platform): close the accent audit's remaining gaps by @yannickmonney in https://github.com/tale-project/tale/pull/3698\n* fix(platform): finish the settings-list page sizes and API key focus by @yannickmonney in https://github.com/tale-project/tale/pull/3700\n* fix(platform): close the fail-open embedding provider refusal by @yannickmonney in https://github.com/tale-project/tale/pull/3701\n* fix(platform): refresh watchdog ownership after settlement by @yannickmonney in https://github.com/tale-project/tale/pull/3727\n* fix(platform): keep chat sharing truthful, prefixed and keyboard-usable by @yannickmonney in https://github.com/tale-project/tale/pull/3728\n* fix(platform): close the #3686-#3688 review leftovers by @yannickmonney in https://github.com/tale-project/tale/pull/3740\n* fix(platform): release orphaned mail bytes and guard the stamp clear by @yannickmonney in https://github.com/tale-project/tale/pull/3739\n* fix(platform): strip HTML before truncating an inbox row's preview by @Israeltheminer in https://github.com/tale-project/tale/pull/3730\n* feat(platform): let tasks repeat, with a reusable recurrence picker by @yannickmonney in https://github.com/tale-project/tale/pull/3731\n* fix(platform): let the docs seeder settle on the project files list by @yannickmonney in https://github.com/tale-project/tale/pull/3760\n* test(platform): pin the embedding recommendations route's wire body by @yannickmonney in https://github.com/tale-project/tale/pull/3757\n* test(platform): pin the chat search's scan order and contact grouping by @yannickmonney in https://github.com/tale-project/tale/pull/3758\n* fix(platform): word lapsed-session failures and guard the rule by @yannickmonney in https://github.com/tale-project/tale/pull/3763\n* fix(platform): add or reconnect exactly the OAuth credential chosen by @yannickmonney in https://github.com/tale-project/tale/pull/3729\n* fix(platform): close the app-door refusal round-two review findings by @yannickmonney in https://github.com/tale-project/tale/pull/3765\n* fix(ui): keep the reader's focus when a filter bar turns disabled by @yannickmonney in https://github.com/tale-project/tale/pull/3764\n* fix: stop PR image cleanup failing on retired services by @yannickmonney in https://github.com/tale-project/tale/pull/3741\n* fix(platform): queue pinned sandbox recreates and unpin before destroy by @yannickmonney in https://github.com/tale-project/tale/pull/3762\n* fix(platform): close the accent audit's last review findings by @yannickmonney in https://github.com/tale-project/tale/pull/3748\n* fix(platform): close the task-limits review round after #3689 by @yannickmonney in https://github.com/tale-project/tale/pull/3749\n* fix(manual): judge NAV-B14's support-URL warning only in a deployment by @yannickmonney in https://github.com/tale-project/tale/pull/3761\n* fix(platform): preserve recurring task work and series by @yannickmonney in https://github.com/tale-project/tale/pull/3771\n* fix(ai): stagger gateway token refreshes and resume runs they cut by @yannickmonney in https://github.com/tale-project/tale/pull/3742\n* feat(platform): make the desktop Home panel resizable by @Israeltheminer in https://github.com/tale-project/tale/pull/3768\n* fix(ui): localize the date picker and let the keyboard clear it by @yannickmonney in https://github.com/tale-project/tale/pull/3759\n* feat(platform): tell agents what each equipped skill is for by @yannickmonney in https://github.com/tale-project/tale/pull/3775\n* fix(docs): send guessed section and /en addresses to a real page by @yannickmonney in https://github.com/tale-project/tale/pull/3769\n* fix(platform): stop promising skills and images the product does not have by @yannickmonney in https://github.com/tale-project/tale/pull/3770\n* fix(platform): re-queue renamed synced files and isolate watchdog faults by @yannickmonney in https://github.com/tale-project/tale/pull/3782\n* fix(platform): close the #3739 mail stamp pass review findings by @yannickmonney in https://github.com/tale-project/tale/pull/3785\n* test: read browser-test layouts once their eases end, not on a timer by @yannickmonney in https://github.com/tale-project/tale/pull/3783\n* fix(platform): scope automation runs to the projects the viewer can see by @yannickmonney in https://github.com/tale-project/tale/pull/3772\n* docs(docs): add a guide to using Tale from an editor or a script by @yannickmonney in https://github.com/tale-project/tale/pull/3800\n* fix(platform): let admins cap any member's API key in the budget editor by @yannickmonney in https://github.com/tale-project/tale/pull/3780\n* feat(platform): start new project agents with the document skills ticked by @yannickmonney in https://github.com/tale-project/tale/pull/3781\n* fix(platform): close the session-lapse review round after #3763 by @yannickmonney in https://github.com/tale-project/tale/pull/3795\n* feat(platform): ground chat answers about Tale in its documentation by @yannickmonney in https://github.com/tale-project/tale/pull/3773\n* feat(platform): show readers live automation navigation by @yannickmonney in https://github.com/tale-project/tale/pull/3779\n* fix(platform): apply the organization's custom instructions to agent runs by @yannickmonney in https://github.com/tale-project/tale/pull/3774\n* fix(ai): let a small broker pool serve while an account cools down by @yannickmonney in https://github.com/tale-project/tale/pull/3794\n* fix(platform): hold skill uploads to the owner and team-audience rules by @yannickmonney in https://github.com/tale-project/tale/pull/3776\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.61...v0.5.62","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.62","publishedAt":"2026-09-29T04:05:34Z"},{"tag":"v0.5.61","version":"0.5.61","name":null,"body":"One merged pull request (#3503) and nineteen direct commits since v0.5.60. No migrations, no API contract change (2.1.0), and no proxy or sandbox-runtime image change: run `tale update` and then `tale deploy` as usual.\n\n## Chat\n- A project member opening a chat someone else shared with the project now reads its messages under the read-only pill, and sees the version the owner has selected rather than the root's replaced branch; Copy, message info and Export stay, nothing that writes. (#3503)\n\n## Phones and narrow columns\n- Sign-in, sign-up, 2FA enrolment, the forced password change, onboarding and the root 404 scroll on a phone held sideways or at 200% zoom; a source guard keeps `min-h-dvh` out of the app. (9887f7223)\n- Settings field rows stack at full width until their own surface is 36rem wide, and a toggle's label and description wrap on a phone instead of truncating. (d65cfeb65, d94268887)\n- Metric strips, breakdown lists, chart pairs and card grids pick their columns from their own width; the branding preview joins the form only once the settings surface is 48rem wide. (855d66dc7, e9f6ce5af)\n- A list toolbar moves its primary action to a line of its own when search, Filter and the action do not fit; the task board shares the same toolbar. (48801017d)\n- A tab strip's actions yield and wrap before a tab slides under the buttons, so the automation editor's Runs tab stays in view at 768px. (838e8d7cb)\n- Automation version and run rows wrap the date instead of the message; chat health's recent errors read on two lines on a phone. (3c33b705b, 651bdb8e3)\n- On a phone the project chats' repeated \"Share with project\" label is hidden (the switch keeps its accessible name), OAuth app rows wrap their status and buttons, the retention drawer stacks its buttons, and a vendor's name stays readable in the credential picker. (6e0f63e97, d1a0d648a, c1b74da8a, 34f55770c)\n\n## Docs and marketing sites\n- The docs breadcrumb shows only the immediate parent below 1024px and truncates a deep trail inside its header strip. (26105b7f8, 0712bd971)\n- The marketing site's footer links wrap inside their column, and the pricing and hardware comparison tables scroll sideways on a phone instead of clipping. (8a7e3af29, 31b88a71f)\n- The UI docs describe field rows, grids and toolbars by their column; the platform's manual responsive suite adds the narrow page column and short viewports, and the app design contract states the rule: viewport breakpoints decide the shell, a page's layout answers to its own column. (67e863e02, 7242c9b0a)\n\n## API contract changes\n\nNone in this range. The contract stays at 2.1.0: 136 operations.\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.60...v0.5.61","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.61","publishedAt":"2026-09-27T12:13:50Z"},{"tag":"v0.5.60","version":"0.5.60","name":null,"body":"One merged pull request (#3498, the S3 fixes from the 2026-09-26 platform evaluation) and fourteen direct commits since v0.5.59. Migrations `0119`–`0121` (backfills, applied at boot); API contract 2.0.0 → 2.1.0; the proxy image changes, so run `tale update` and then `tale deploy --stop` (recreates db, object-store and proxy with a brief downtime).\n\n## Behaviour changes\n- Mounting the WebDAV root `/dav/<org>/` now lists two folders, `documents/` and `.trash/`; dot-segment paths (`..`, `%2e%2e`) are refused with 404 at the edge and in the app. (#3498)\n- OIDC ID tokens carry `acr: \"0\"` (better-auth hard-codes it); relying parties pinned to the bronze URN have not matched since v0.5.45, and the docs now say so. (#3498)\n- The `llm-gateway` network alias and its `NO_PROXY` entries, kept since the June rename to `sandbox-llm-gateway`, are gone. (4f6b3100a)\n- Deleting a project agent clears its task assignments; removing a member revokes every competence grant; feedback is attributed by the rated message. The three migrations backfill the rows written before. (#3498)\n\n## Chat\n- A stopped reply settles to its saved text without a reload; \"B is better\" switches the composer to model B; the losing Arena column goes to Trash at settle. (#3498)\n- The chat assistant finds a task by its `KEY-n` id; dictation reports a denied microphone; a new chat focuses the message box. (#3498)\n- A ⌘K chat hit shows plain prose cut around the match instead of raw markdown from the start of the message. (d94390c04)\n\n## Knowledge\n- Knowledge entries render as Markdown; the copyable id is labelled **Version ID**; Owners and Admins can pick any team for a document, synced teams included. (#3498)\n- Server refusal reasons reach the user for websites, products and contacts; folder duplicates show the right toast; product forms refuse negative or oversized prices and stock; bulk import names the refused rows instead of rejecting the file. (#3498)\n- Deleting the credential the embedding model uses is refused (`CREDENTIAL_IN_USE`); deleting a product or replacing its image releases the managed image, legal holds honoured. (#3498)\n\n## Projects and tasks\n- Board drops land in the lane under the pointer; opening a task pushes history; a subtask links to its parent; a deep link to a missing task says so. (#3498)\n- Projects pick an icon and colour; an archived project is read-only (`PROJECT_ARCHIVED`); a mixed upload ends in one summary toast; project rows open from the keyboard. (#3498)\n\n## Automations\n- Run lists name who started a run and why it waits; `startedVia` on run reads. (#3498)\n- One cron validator serves client and server; the Blank wizard previews the schedule and hands over the one-time webhook URL; revoking or rotating a webhook URL asks first. (#3498)\n- Paused and undeployed schedules say so; new trigger bindings start disabled; stopped runs show where they stopped and who stopped them; a deleted automation's runs stay reachable. (#3498)\n- Validation warns about a model the organization does not serve (`LLM_MODEL_UNAVAILABLE`); a connector node without a usable credential fails before an approval is requested. (#3498)\n\n## Governance and settings\n- Team names are unique per organization (`TEAM_NAME_TAKEN`, also on SCIM); deleting an organization requires typing its name; Branding Reset commits everything in its confirm. (#3498)\n- Lists say \"Showing the first N — scroll for more\"; a Member's admin deep link is refused once; blocked erasure receipts say when a hold was released; Trash names chats. (#3498)\n- Chat-health merges provider-less rows; harness-turn metrics add up and name the harness. (#3498)\n- Password policy rules are switches like every other toggle; the account's change-password section is titled **Password**; an unconfigured OAuth app is explained once, not under every row. (8fe16bf8b, b66ed24de, 858a7aa9e)\n\n## Developer surfaces and shell\n- Docs search indexes whole pages and keeps inline code, so error codes, headers and env vars are findable. (#3498)\n- Cold-load JavaScript drops from 2604 to 1928 KB gzipped, and the build guards the budget. (#3498)\n- The offline overlay fires on `offline` and while the backend is unreachable; light-theme muted text and sidebar ages meet AA contrast. (#3498)\n- The environment reference drops the `RAG_RERANKING_*` variables nothing reads; the image no longer sets `SANDBOX_STORAGE_INTERNAL_BASE_URL`. (bd2c51806, 6d04ce701)\n- Dead code and 23 unused platform dependencies are removed: unrendered UI, the Slack notification renderer, the Convex upload lane, app doors nothing called. (94145ddb5, acc87b91d, 271a8df5b, f1c5d4255, 5c89086b2, fe56bd8f9)\n\n## API contract changes\n\nThe contract moved from 2.0.0 to 2.1.0 (136 → 136 operations). Read the API reference's versioning section before upgrading a pinned client.\n\nAdded operations: none.\n\nRemoved operations: none.\n\n### Changelog\n\n2.1.0 — 2026-09-27: `startedVia` (`schedule` | `webhook` | `event`) on `RunSummary` and `Run` — which kind of trigger started a `trigger:<id>` run, read off the run's own input so it stays true after the binding changes kind; absent on a run a person or an API key started. A listing could not tell a scheduled run from a webhook delivery before. `DELETE /api/v1/products/{id}` answers 409 `LEGAL_HOLD_ACTIVE` under an organization-wide legal hold or a custodian hold on the uploader of the product's image, the way document and contact deletes do — the response is now documented on the operation.\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.59...v0.5.60","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.60","publishedAt":"2026-09-27T10:06:03Z"},{"tag":"v0.5.59","version":"0.5.59","name":null,"body":"## What's Changed\n* fix(platform): send no credential with a backend error event by @yannickmonney in https://github.com/tale-project/tale/pull/3499\n* feat(platform): run agents on Claude Opus 5.5 and offer Fable 5.1 by @yannickmonney in https://github.com/tale-project/tale/pull/3500\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.58...v0.5.59","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.59","publishedAt":"2026-09-27T08:16:52Z"},{"tag":"v0.5.58","version":"0.5.58","name":null,"body":"## What's Changed\n* feat(platform): put chats, tasks and the inbox in one Home panel by @yannickmonney in https://github.com/tale-project/tale/pull/3492\n* fix(ai): balance subscription accounts and enable OpenAI brokers by @yannickmonney in https://github.com/tale-project/tale/pull/3495\n* fix(platform): polish the UI details across every section by @yannickmonney in https://github.com/tale-project/tale/pull/3493\n* feat(platform)!: synchronize source issues and harden agent tasks by @yannickmonney in https://github.com/tale-project/tale/pull/3496\n* feat(ai-gateway): grey out an account whose session or week is spent by @yannickmonney in https://github.com/tale-project/tale/pull/3497\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.57...v0.5.58","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.58","publishedAt":"2026-09-27T00:51:20Z"},{"tag":"v0.5.57","version":"0.5.57","name":null,"body":"## What's Changed\n* perf(platform): name a foreign-model agent exec for replica cache affinity by @yannickmonney in https://github.com/tale-project/tale/pull/3490\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.56...v0.5.57","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.57","publishedAt":"2026-09-26T17:58:04Z"},{"tag":"v0.5.56","version":"0.5.56","name":null,"body":"One merged pull request since v0.5.55 (#3491): the S2 fixes from the 2026-09-26 platform evaluation. Migration `0114` (one nullable column, applied at boot); new sandbox-runtime image; `tale deploy` as usual.\n\n## Chat\n- Retry then Edit no longer loses a branch: forks attach to the fork point and the switcher counts every sibling. (#3491)\n- A share publishes the branch on screen, and **Include newer messages** re-takes that branch. (#3491)\n- Knowledge search hits carry the whole chunk instead of 500 characters; product and contact rows carry every user-facing field. (#3491)\n\n## Guardrails\n- Order numbers, dates and build numbers are no longer masked as `[PASSPORT]` / `[NZ_IRD]`: digits-only national-ID patterns need a context keyword or a valid check digit. (#3491)\n- Docs say the stored message is the masked text. (#3491)\n\n## Agents\n- Managed Codex runs start again: the runtime creates `CODEX_HOME`, the session key no longer lists a model twice, and Codex on a custom DeepSeek connector goes through its Anthropic endpoint. (#3491)\n- A failed run shows the provider's error message and status, not the assistant's last reply. (#3491)\n- Deleting a skill removes it from every agent that has it; an agent that still references a missing skill stops with `equipment_missing`. (#3491)\n\n## Automations\n- Two editor tabs no longer overwrite each other: saving from a stale version gets 409 `AUTOMATION_VERSION_STALE`, and the editor offers reload or save anyway. (#3491)\n\n## Governance and usage\n- Audit log covers team writes (app, Better Auth, SSO sync), API keys, WebDAV app passwords, connector credentials, branding and WebDAV document writes. (#3491)\n- **Top models** counts chat spend again (a zero audio-seconds field was classifying it as transcription). (#3491)\n- Legacy `user:` / `api-key:` / `trigger:` ledger rows count toward the person they belong to, including the personal cap. (#3491)\n- Typing a partial hex value into the accent colour field no longer freezes the page. (#3491)\n\n## Inbox and API\n- A `deleted` source snapshot closes the mirrored conversation and keeps its messages. (#3491)\n\n## App shell\n- A list whose load failed shows an error with **Retry** instead of the first-run empty state. (#3491)\n- The service worker caches the app shell again (offline page, no **Update available** prompt on a first visit); the prompt has **Later**. (#3491)","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.56","publishedAt":"2026-09-26T14:37:51Z"},{"tag":"v0.5.55","version":"0.5.55","name":null,"body":"## What's Changed\n* fix(ai-gateway): keep the account list when a re-read fails by @yannickmonney in https://github.com/tale-project/tale/pull/3487\n* fix(platform): put the email and profile claims back into Tale's ID tokens by @yannickmonney in https://github.com/tale-project/tale/pull/3488\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.54...v0.5.55","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.55","publishedAt":"2026-09-25T07:12:37Z"},{"tag":"v0.5.54","version":"0.5.54","name":null,"body":"## What's Changed\n* fix(platform): name the mailbox a thread arrived on by @Israeltheminer in https://github.com/tale-project/tale/pull/3478\n* fix(platform): show an API-thread reply before its app acknowledges it by @Israeltheminer in https://github.com/tale-project/tale/pull/3480\n* fix(platform): send a composed email from the mailbox you pick by @Israeltheminer in https://github.com/tale-project/tale/pull/3481\n* feat(platform): filter the Inbox by mailbox when a connector has several by @Israeltheminer in https://github.com/tale-project/tale/pull/3482\n* feat(platform): route conversations by the mailbox or API app they arrive on by @Israeltheminer in https://github.com/tale-project/tale/pull/3483\n* feat(platform): let an integration queue its conversation to a team by @Israeltheminer in https://github.com/tale-project/tale/pull/3484\n* refactor(ui): move the list-page hook into the design system by @yannickmonney in https://github.com/tale-project/tale/pull/3485\n* feat(ai-gateway): connect accounts on their own and name each plan by @yannickmonney in https://github.com/tale-project/tale/pull/3486\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.53...v0.5.54","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.54","publishedAt":"2026-09-24T19:50:23Z"},{"tag":"v0.5.53","version":"0.5.53","name":null,"body":"## What's Changed\n* fix(platform): pull the object store image from the fleet's GHCR mirror by @larryro in https://github.com/tale-project/tale/pull/3479\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.52...v0.5.53","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.53","publishedAt":"2026-09-24T14:58:46Z"},{"tag":"v0.5.52","version":"0.5.52","name":null,"body":"Two merged pull requests since v0.5.51 (#3476–#3477). No migration; `tale deploy` as usual.\n\n## Settings and governance\n- New **Settings › Governance › Competences** page: the register of grants (member, competence, status with its until/since date, granted by, evidence), an Active filter by default, a **Grant competence** dialog (platform capability or named qualification, expiry Never / 30 / 90 / 365 days, evidence) and a per-row **Revoke** with confirmation; revoked and expired grants stay as history. (#3476)\n- Competence listings return every live grant plus the newest revoked ones; a long history no longer hides an old grant that still vouches. (#3476)\n- Register refusals (already granted, reserved `tale:` name, not a member, already revoked) show inline in the grant form, in the admin's language. (#3476)\n- New docs guide `platform/admin/governance/competences` (EN/DE/FR); the API keys guide and the API reference point to it. (#3476)\n\n## SSO and members\n- Role-mapping rules can be reordered (drag handle, up/down); the first matching rule decides the role at sign-in, and the help text and the Enterprise SSO guide now say so. (#3477)\n- Settings › Account shows **Your role** with the translated role badge and a **Manage members** link for admins; the profile-menu tooltip shows the translated role. (#3477)\n- Teams table: long synced group names show in full on hover, and the Name column gets most of the width. (#3477)\n- Trash table: long owner names stay in their column and no longer paint over the **Trashed** badge. (#3477)","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.52","publishedAt":"2026-09-24T12:06:33Z"},{"tag":"v0.5.51","version":"0.5.51","name":null,"body":"Ten merged pull requests since v0.5.50 (#3466–#3475). One migration (0113, additive, applied on start); `tale deploy` as usual.\n\n## Inbox\n- A reply leaves from the mailbox that received the thread; an org with two mailboxes on one email connector no longer answers from the default one. (#3468)\n- Rows show the channel a conversation arrived on; the header names the connector or API source; the composer says where the reply goes; the Channel filter works again. (#3470)\n- Picking the current assignee again unassigns them; the assigned row is announced as selected. (#3469)\n- Composing without an email connector shows a warning with a link to connector settings (admins) or an ask-your-admin hint; Send says why it is off. (#3472)\n\n## Chat\n- Arena Mode shows the prompt at once and snaps each send to the top of both columns. (#3473)\n\n## Contacts and records\n- Phone fields accept digits and `+ ( ) . -` only; the API rejects a phone with letters. (#3467)\n- Record view dialogs drop the empty space and lead with identity facts (website name and ID, contact email); website pages have one search field. (#3472)\n\n## Automations\n- A trivial template expression no longer times out when the run carries large node outputs (\"evaluation timed out after 100ms\"). (#3475)\n- A finished automation run keeps its Run row and Details on the task. (#3475)\n\n## Settings and knowledge\n- Opening Settings › Sandboxes no longer removes idle project workspaces; they stay listed while the agent is idle. (#3474)\n- Reindexing from the status badge shows one \"Indexing started\" toast. (#3466)\n\n## AI Gateway\n- ChatGPT accounts show their usage instead of \"No usage read yet\". (#3471)\n- A **Resets in** column with a countdown bar beside each usage window; the product name is no longer translated. (#3471)","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.51","publishedAt":"2026-09-24T03:23:31Z"},{"tag":"v0.5.50","version":"0.5.50","name":null,"body":"Six merged pull requests since v0.5.49 (#3460–#3465). No migration; `tale deploy` as usual.\n\n## Inbox\n- One **Filter** button holds every facet: assignee (people and teams), read status, source. (#3464)\n- The Inbox nav item and the mobile tab bar show the unread count. (#3461)\n- Composing to an address the org does not know offers **Add \"…\" as a contact**; the recipient picker searches server-side. (#3465)\n\n## Chat\n- Chat follows the custom instructions saved under Settings › Preferences. The Memories switch is gone; nothing could use it. (#3462)\n- An edit the budget refuses keeps the draft; Arena judges a pair only when both sides answered; search covers edited branches. (#3463)\n\n## Lists and navigation\n- Documents and projects show who may open a row the same way; a restricted document no longer shows an empty Teams cell. (#3460)\n- The user menu's Teams row no longer truncates; **Try again** on the connection overlay really retries. (#3461)\n- Automation and metrics rows are keyboard reachable; sheets and dialogs return focus to their opener; the filter popover is named; table headers meet AA contrast. (#3463)\n\n## Settings and knowledge\n- The PII switch shows the saved state after reload; Discard restores the reveal switches; an icon can be cleared; image uploads no longer dirty the form. (#3463)\n- Embedding: a wrong base URL no longer leaves a document Indexing forever; models that ignore `dimensions` work; corrected settings re-queue failed entries; the batch cap is learned from the provider. (#3463)\n\n## Automations and API\n- Approval cards show the step's parameters; API-lane sends complete the draft; `run_deployed` idempotency is judged against the run; Improve with AI works. (#3463)\n- Also: playlist pastes report what was skipped, the docs skip link stays on the page, tutorials tell 403 from 404, folder-delete toasts speak words instead of JSON. (#3463)","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.50","publishedAt":"2026-09-22T14:24:16Z"},{"tag":"v0.5.49","version":"0.5.49","name":null,"body":"## What's Changed\n* fix(ui): never restore focus to the menu item a dialog was opened from by @yannickmonney in https://github.com/tale-project/tale/pull/3459\n* fix(ai-gateway): dress the panel in the design system it builds on by @yannickmonney in https://github.com/tale-project/tale/pull/3458\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.48...v0.5.49","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.49","publishedAt":"2026-09-22T09:05:09Z"},{"tag":"v0.5.48","version":"0.5.48","name":null,"body":"**0.5.48 carries one merged pull request — every change since 0.5.47.** 0.5.47 was tagged by the pipeline with generated notes on #3457 (the AI gateway drops its panel password and answers one token endpoint per vendor); nothing was tagged in between, and no number is skipped.\n\nOne theme in 0.5.48. **A reply that used tools no longer shakes when its turn settles.** On a long reply with tool steps — a weather lookup that reads a page four times, a knowledge search — the block above the answer that lists what the model did (`Thought for Ns` and its step rows) vanished for a third of a second the moment the turn ended and came back, so the whole answer jumped up by the block's height and back down; and a paragraph the model had written before its first tool call was held back until that same moment, then appeared above the streaming answer and remounted every block beneath it. Both were the same seam: the client's view of the reply held the streamed text across the gap between the stream's settle event and the refetched transcript row, but not the streamed parts, and read the settled rounds' text from a row that is refetched only at settle. Both are gone. A reply without tool steps never showed either, which is why a local check against a plain provider could not see it.\n\n## Highlights\n\n### A tool-using reply holds still when its turn settles (#3454)\n\nTwo settle-time jumps, measured in Google Chrome on a HiDPI display against platform.tale.dev with a reply that fetched four weather pages: the answer body moved up 142 px and back within about 300 ms (two layout shifts of 0.023 each), and roughly three hundred characters landed in one frame. The thread view kept a live row's text and reasoning through the gap between the stream's `settled` event and the transcript refetch, but its `parts` — the tool calls and results the thought timeline and the source cards draw — fell back to the placeholder row's empty list, so the timeline unmounted and the source cards with it; and the text of a round the model had already settled was read from that same placeholder row, which carries nothing until the finalized row lands, so the paragraph written before a tool call was invisible while the tool ran.\n\nThe reducer now holds the streamed parts the way it holds the text — set from the live channel (the newest of the streamed and held lists, served whenever it is longer than the row's own), and from a synthesized row's record so the handover to the real row keeps them; served through the settle gap while the placeholder's parts are shorter; cleared on finalize, where the finalized row's parts win on a tie as before — and reads the settled rounds' text and reasoning from those same parts for the live row and the synthesized row alike. What streams is byte-identical to the finalized text, so the reveal drains on without a re-render. Four reducer tests hold the live-row gap, the synthesized-row handover, the earlier-round text through settle and the synthesized row's text composition; each pair fails on the reducer without its commit. `CHAT-F41` carries the manual check.\n\n## Behaviour changes\n\n- **Chat, a reply with tool steps**: text the model wrote before a tool call is on screen while that tool runs, in its authored place above the later rounds, instead of appearing when the turn settles; the `Thought for Ns` header, its step rows and the source cards under the answer stay mounted through the turn's end; the answer body does not move when the stream settles, and the reveal keeps draining from where it was. A reply without tool steps is unchanged. The stale-live guard — the finalize race in which the channel still carries a prefix of text that already settled onto the row — keys off the same parts, so nothing is shown twice.\n- **Nothing else changes**: no user-visible string, catalog, setting, route, scheduled job, audit action or error code moves in this range; the platform's messages are untouched in every language.\n\n## API contract changes\n\n- **None in this range. The contract stays at 1.20.0**: 86 paths, 135 operations, 63 schemas, 167 `Error.code` values; the shipped `openapi.json` is byte-identical to 0.5.47's (and to 0.5.46's), and `X-Tale-Api-Version` answers `1.20.0`.\n- **MCP**: unchanged.\n- The thread stream's SSE protocol (`GET /chat/threads/:id/stream`: `idle`, `progress`, `settled`, `heartbeat`) is unchanged on the wire; the change is in how the browser holds what it already received.\n\n## Security\n\n- **No new surface.** The change is client-side state handling over data the browser already received on the thread stream; it adds no route, permission, secret or storage. No dependency changes: the lockfile is byte-identical to 0.5.47's, and no `package.json` moves.\n- The dependency-audit and image-configuration lane, the SAST gate and the platform image scan run on the release commit as on every push to `main`.\n\n## Known issues\n\n- **The fix is proved by reducer tests and by a real-browser A/B against a development stack, not yet against the platform fleet.** The measurement that found the jumps was taken on platform.tale.dev running 0.5.46; the same measurement after this release is deployed is the closing check, and these notes will be corrected if it disagrees.\n- **A cosmetic flicker in the chat list stays**: while a reply streams, the active conversation's relative-time label widens (`5s`, `20s`, `1m`) and a long title beside it is re-truncated at each tick. It is the sidebar, not the transcript, and it is the same in every version.\n- **0.5.47 shipped with generated notes.** Its one change, #3457, has no curated record: the AI gateway's panel no longer asks for a password (`AI_GATEWAY_PANEL_PASSWORD` and `AI_GATEWAY_SESSION_SECRET` are no longer read; a deployment that still sets them boots fine, and access to the panel is whatever fronts the origin), and `GET /api/tokens` is replaced by `GET /api/tokens/anthropic` and `GET /api/tokens/openai`, each answering `{ \"tokens\": [ … ] }` in the retired cc-gateway's field shape (`id` is a string). A caller of the old single endpoint must move to the vendor path.\n- Unchanged from v0.5.46, where each is described in full: the Entra app-role fix is not verified against a live Entra tenant and a rule whose **Matches value** is a role id no longer matches; the AI gateway is deployed on its own and documented in its README only, with no inference proxy, account rotation, multi-user panel or usage history; an Own Compose or own Kubernetes deployment keeps the egress probe it declares; ui.tale.dev reports nothing until its deployment sets the three collector variables.\n- Unchanged from v0.5.45, where each is described in full: a native OIDC client registered before 0.5.45 is refused by the CLI until its application type is backfilled with the one-statement `UPDATE` those notes give; ui.tale.dev ships one English tree; the old `oauthClient.type` and `public` columns stay; `team.memberCount` is a constant 0; the 0.5.42–0.5.44 window has no curated known-issues record, and the **Settings > Governance > Vision model** description (English, German and French) still says a model that already reads images never uses the vision model, which #3436 made inaccurate.\n- Unchanged from v0.5.41, where each is described in full: spend history booked before that release is not rewritten and a month-to-date total spans two pricing rules; off-peak and long-context pricing tiers are not modelled; cache prices are not surfaced; a pin to `deepseek-v4-flash` answers `CHAT_MODEL_UNKNOWN`; the Moonshot, Vercel and OpenRouter harness doors are declared from vendor documentation; the three lane-fix follow-ups (the Read hook's PDF guard, the resumed-retry replay, Z.ai's unused Anthropic door) are open; a shipped provider whose catalog carries no curated embedding entry is refused as an embedding provider; the custom-provider form's authoring limits and a bare listing's assumptions; `tale-vision --thinking disabled` is validated against a controlled upstream only; the chat scroll rounds `CHAT-F39` and `CHAT-F40` are browser-tested for the wheel and the follow latch only; the `bun dev` runtime-image step was not observed live.\n- Unchanged from v0.5.39, where each is described in full: a knowledge entry the REST door wrote before that release keeps `source: \"manual\"`; a scan reuses a robots verdict up to a minute old and a row stored earlier carries no sitemap list; the ask retraction is best-effort and forward-only; the app's own archive stays unfenced; `GET /api/v1/teams` is read-only and a complete set; the chat `content` cap counts UTF-16 code units; the `nullable` on a `oneOf` branch is the OAS 3.0 spelling; 0.5.38 shipped with generated notes and its four pull requests (#3424–#3427) have no known-issues record.\n- Unchanged from v0.5.37, where each is described in full: ledger rows booked before that release keep the subject they were booked under and a project agent can appear twice in **Top assistants** across the upgrade; `app.usage_events` is write-retired, not dropped; nothing in the schema forbids a door string in `usage_ledger.user_id`; the run list labels a keyed start `Started by api-key:…`; the `GOV-F20` round is manual; `llm` nodes are unmetered and a run carries no usage or cost.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; a task can be archived and restored over REST since 0.5.43 but still not deleted; 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`; 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; a run carries no usage or cost.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0112**; the knowledge database is unchanged; `db/migrate.ts` and `auth/auth.ts` are untouched, so Better Auth stays at 1.7.5 and its migrator has nothing to add — the boot runs the same idempotent `team.memberCount` default 0.5.45 introduced and nothing else.\n- **Rolling-deploy safe.** No schema, contract, catalog or message change; the previous and the new platform image read and write the same tables, and a browser that loaded the 0.5.47 bundle keeps working against a 0.5.48 backend (and the other way round) because nothing on the wire moves.\n- **No platform environment variable is added or removed**; the root `.env.example` is unchanged. No scheduled job, audit action, error code, catalog, provider definition or platform message key changes.\n- **`compose.yml` is unchanged**, and so are `services/proxy` and `services/db`: no image in the stop-gated tier changes — a plain `tale deploy` is the whole upgrade, no `--stop`, no downtime window.\n- **Images**: only the **platform** image carries a source change (the thread-view reducer in the browser bundle). The other eleven — `docs`, `ui-docs`, `web`, `proxy`, `db`, `ai-gateway`, `sandbox`, `sandbox-egress`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-llm-gateway` — are rebuilt at the tag as every release rebuilds them, from sources and a lockfile identical to 0.5.47's.\n- **The CLI does not change in this range**: `tools/cli`, `packages/shared`, the embedded configuration catalog and `compose.yml` are all untouched since 0.5.47, so the release executables are rebuilt at the tag and report 0.5.48 but behave as 0.5.47's. A managed deployment may move its runtime pin to this release under a 0.5.47 CLI; moving both together stays the documented practice.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.48` and `marketing-ui-v0.5.48` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.48\"`. Neither package changes in this range, so both tags are content-identical to their 0.5.47 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.47; there is no 0.5.42 deployment to be on):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`. A deployment crossing 0.5.44 also takes Better Auth 1.7's tables and columns and the `memberCount` default at boot — and, if it declares native OIDC clients created by an older CLI, needs the one-statement backfill in the 0.5.45 notes; one crossing 0.5.45 takes the egress probe that 0.5.46 introduced (the egress container is recreated by the deploy); one crossing 0.5.41 runs migration 0112 at boot as well; one crossing 0.5.39 runs migration 0111, one crossing 0.5.38 runs that release's `0108_approvals_one_pending_conversation_draft.sql`, one crossing 0.5.37 runs migration 0110, one crossing 0.5.36 runs migration 0109, one crossing 0.5.35 runs migration 0108 (`0108_trusted_header_keys.sql`) and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Nothing to re-point before you upgrade**: no catalog, model, environment variable, setting or contract field changes in this range. A browser tab that is open on a conversation picks the new bundle up at its next load; a reply already streaming in an old tab keeps the old behaviour until then.\n\n- **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.\n\n- **The AI gateway** is deployed on its own, not by `tale deploy`; its image is unchanged since 0.5.47 apart from the version label:\n\n  ```bash\n  cp services/ai-gateway/.env.example services/ai-gateway/.env   # fill in the two secrets\n  VERSION=0.5.48 docker compose -f compose.ai-gateway.yml pull\n  VERSION=0.5.48 docker compose -f compose.ai-gateway.yml up -d\n  ```\n\n  The pool lives in the `ai-gateway-data` volume; back it up, because losing it loses every authorized account, and keep `AI_GATEWAY_ENCRYPTION_KEY` with it. The panel answers on port 3004 and, since 0.5.47, asks for no password of its own.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- fix(platform): keep a tool-using reply still when its turn settles by @larryro in https://github.com/tale-project/tale/pull/3454\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.47...v0.5.48","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.48","publishedAt":"2026-09-22T07:02:29Z"},{"tag":"v0.5.47","version":"0.5.47","name":null,"body":"## What's Changed\n* feat(ai-gateway): drop the panel password, split the token endpoints by @yannickmonney in https://github.com/tale-project/tale/pull/3457\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.46...v0.5.47","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.47","publishedAt":"2026-09-22T03:28:38Z"},{"tag":"v0.5.46","version":"0.5.46","name":null,"body":"**0.5.46 carries six merged pull requests — every change since 0.5.45.** It follows the 0.5.45 curated notes directly: nothing was tagged in between, and no number is skipped.\n\nSix themes in 0.5.46. **Microsoft Entra ID app roles map to Tale roles**: an **App role** rule under _Auto-assign roles from the IdP_ never matched, so every user Entra signed in landed on the connection's default role however their app roles were assigned — the roles were read from a Microsoft Graph query that answers with role ids across the whole tenant and needs a permission the setup guide never asked for, while the ID token's own `roles` claim, which carries the role's Value and needs nothing, was never read. **A new standalone service and image, the AI gateway**, pools the Claude Pro/Max and ChatGPT Plus/Pro subscriptions a team already pays for and hands their OAuth tokens out through one endpoint; this is the first release to publish `tale-ai-gateway`, and it is not part of the platform stack that `tale deploy` runs. **That image could not be built as merged** — its Dockerfile copied three workspace manifests where `bun install` needs them all, and nothing in the pull-request pipeline built it — so the cut was held until the image built, booted and answered under a container test that now runs on every pull request that touches the service and in the release gate. **The sandbox egress proxy is probed with an HTTP request instead of a bare TCP connect**, which ends the error line the proxy wrote to its own log on every health interval, for the lifetime of every container, and proves more than the old probe did. **ui.tale.dev counts a pageview** for every guide a reader opens, when a deployment sets the collector variables it has always accepted. And **a managed deployment's configuration reader refuses out loud**: five security refusals in the fixed script the CLI runs inside the runtime's own container used to exit 0 with an empty answer, because Bun swallows an uncaught throw on that evaluation path. The contract stays at **1.20.0**, no migration ships, no platform environment variable changes, and no image in the stop-gated tier changes, so a plain `tale deploy` is the whole upgrade — it recreates the egress container with the new probe on the way.\n\n## Highlights\n\n### Entra ID app roles map to Tale roles (#3455)\n\nAn organization that defined app roles in its Entra app registration — Employee, Developer, Editor, Administrator — and assigned them to users or groups in the enterprise application found every user created as a member, with an **App role** rule that never fired; a security-group rule on the same connection worked. The Entra adapter read app roles from Graph `GET /me/appRoleAssignments`, which answers with app-role **ids**, GUIDs, across every application in the tenant — never the role's Value or display name an administrator types into the rule's **Matches value** — and only for a caller holding the delegated `AppRoleAssignment.ReadWrite.All` or `Directory.Read.All` permission, which the setup guide never asked for, so a normal employee's sign-in logged `[Entra ID] Failed to fetch app roles: Error: Graph API error: 403` and mapped no roles at all. Entra's own mechanism was never read: the ID token the token endpoint hands the backend in the code exchange carries a `roles` claim with the Value of every app role assigned to the user, directly or through a group, in the enterprise application, and needs no Graph permission. Present since the original Entra connection shipped (#354) and carried unchanged through the Postgres port (#3107).\n\nThe adapter now answers app roles from the ID token's `roles` claim and the Graph call is gone, together with the permission it needed. One base64url-correct decoder reads the token's payload for both the app roles and the authentication context (`acrs`, `amr`) the adapter already parsed — a claim with non-ASCII text, a name with an umlaut, survives it intact, where the previous `atob` did not. And because the Entra adapter resolves the user from Graph `/me` and so carried no raw claims, a **Claim** rule with a path such as `roles` could not work for Entra either; the callback now lets the ID token's claims stand in when an adapter carries none, so a Claim rule sees the token claims for Entra as it does for a generic OIDC connection. The docs say an App role rule matches the role's **Value**, that the roles arrive in the sign-in token and need no Graph permission, and the troubleshooting row says so too. Unchanged and by design: role mapping runs only at single-sign-on sign-in, so a member SCIM created keeps the default role until their first sign-in, and with _Auto-assign roles from the IdP_ on, every sign-in re-applies the mapped role, never off Owner.\n\n### A gateway for pooled Claude and ChatGPT subscriptions, as a new standalone service (#3452)\n\n`services/ai-gateway` is a new service on the Tale stack — a Hono API with a one-screen panel on `@tale/ui` — that holds the AI subscriptions a team already pays for, **Claude Pro/Max** and **ChatGPT Plus/Pro** accounts, and hands their OAuth tokens out through one endpoint. It does what the separate cc-gateway did for Claude accounts, rebuilt here and generalized so a second vendor is a module rather than a rewrite. `GET /api/tokens` answers with every account's access token, its expiry, its status and the environment variable that hands it to the vendor's CLI (`ANTHROPIC_AUTH_TOKEN` for Claude Code, `CODEX_ACCESS_TOKEN` for Codex); a background pass refreshes each token ahead of its expiry, so an account stays usable as long as its refresh token does; each account's session and weekly windows — plus any per-model cap the vendor reports — show as live bars. Both subscriptions are reached through the public OAuth client their own CLI ships with, with a PKCE S256 challenge, and they disagree on almost everything below that (Anthropic's console callback prints a code to copy, OpenAI's redirects to a loopback the browser cannot load; Anthropic names the account on a profile endpoint, OpenAI puts it in the `id_token`), so a provider module owns those five moves — authorize, exchange, refresh, identity, usage — and everything above it is vendor-agnostic. The pool is one JSON document under `AI_GATEWAY_DATA_DIR`, encrypted with AES-256-GCM at a pinned 16-byte tag, written atomically and serialized against itself; a tampered store fails loudly rather than decrypting to something plausible. The panel is behind a password and the token endpoint behind an API key, and neither door opens the other.\n\nThe service ships as its own image, `ghcr.io/tale-project/tale/tale-ai-gateway`, published by this release for the first time on both architectures beside the other eleven, with its own standalone Compose file, `compose.ai-gateway.yml`, its own environment file and its own manual test layer. It is **not** part of the platform stack: `tale deploy` neither knows nor runs it, the `proxy` routes nothing to it, and the platform does not call it. On the way, the React-service generator the service was scaffolded from produced a service that was red on arrival — `bun run gen` wrote into `tools/plop/` instead of the repository root, the container image started a server whose imports it could not resolve, and five smaller defects — and every one is fixed in the template, with a test that holds `gen` to `--dest .`; the component library gains an `OpenAIIcon` beside the existing `ClaudeIcon`.\n\n### The gateway image builds, and the pipeline proves it before a tag does (#3456)\n\nPreparing this release, a local build of the merged `tale-ai-gateway` Dockerfile failed at `bun install`: it copied three workspace manifests, and the repository's root manifest names `services/sandbox-runtime/daemon` explicitly, so bun refused with `Workspace not found`; its build-context ignore file also dropped `services/db/`, whose manifest the next copy needs. Nothing had built the Dockerfile before a tag would: the pull-request Build workflow's `ai-gateway` filter had no consuming job, and the release gate pulled the image without testing it — so the first build ever would have been this release's own `Build ai-gateway` jobs, and the release would have died there with no manifests, no `latest`, no GitHub release and no executables, the shape of the never-published 0.5.42. The Dockerfile now copies every workspace manifest, the set the ui-docs image copies plus its own, and its ignore file keeps the other service trees out of the context while re-including their manifests; the React-service generator the service was scaffolded from gets the same two changes. A container test in the shape the three sites already have — `bun run docker:test:ai-gateway` — builds the image, checks its title label, non-root user, `HEALTHCHECK` and size budget, waits for healthy, and probes `/api/health`, the panel shell and the three closed doors; it runs on a pull request that touches the service or the component library, and in the release gate against the pulled release image, whose final manifest verification now names the twelfth image too.\n\n### The egress proxy is probed with a request, not a connect (#3451)\n\nThe `sandbox-egress` container's log filled with one error per health interval, forever: `read_request_line: Client (file descriptor: 5) closed socket before read.` That line is tinyproxy's own, at error level, for any client that connects and closes without sending a request line — and the readiness probe, `nc -z 127.0.0.1 3128`, was exactly that shape, so every egress container wrote its own error line on every probe into the log an operator reads to find real failures. The probe is now one local HTTP request: tinyproxy answers a non-proxy request itself with its own 400 page, so the round trip never leaves the container, contacts no third party and logs nothing at the proxy's log level; the 400 is the healthy answer, so no `-f`, and `--noproxy '*'` keeps an `http_proxy` in the container's environment from redirecting the probe away from the proxy it is probing. It also proves more than the old probe did, measured against the image's own tinyproxy 1.11.2: a proxy that accepts but never answers passed the TCP probe as healthy and fails this one. The probe was defined in three places that had already drifted apart — `compose.yml`, the CLI's compose generator and the image's own `HEALTHCHECK` — and is now one exported constant feeding both compose pipelines and the image, with a parity test that fails if any of the three regresses to a bare TCP connect. The Own Compose health-check table, the Kubernetes readiness table and the Kubernetes manifest — whose `tcpSocket` probe had the same defect — follow in English, German and French.\n\n### ui.tale.dev counts a pageview for every guide a reader opens (#3450)\n\nThe component site was the one first-party Tale site that recorded nothing. Its server half had been there since 0.5.26 — the shared React server injects the analytics configuration and proxies the collector's script and send endpoint whenever a deployment supplies `UMAMI_URL`, `UMAMI_WEBSITE_ID` and `UMAMI_PROXY_TOKEN` — but the browser half never started the tracker, so no event was ever sent. The site now starts the tracker over the same router seam tale.dev and docs.tale.dev use, and reports only what a route loader resolved: the home route reports itself, a documentation page reports the canonical `/docs/<slug>` its loader derived, and a scanner's URL, a page's `.md` twin and the 404 route are never counted; queries, fragments and page titles never leave the browser, and Do Not Track and Global Privacy Control disable collection. Analytics stays off unless the deployment sets all three variables.\n\n### A managed deployment's configuration reader refuses out loud (#3453)\n\nWhen the CLI applies a managed deployment's configuration, it asks the sandbox spawner container to read the deployment's non-secret resource — `deployment.yml` or `deployment.json` under the container's read-only config mount — through a fixed script it evaluates there with `bun -e`. That script guards a security boundary: it refuses a config mount other than the one it proved, a symlinked or hard-linked file, a file over 64 KiB, a directory where the file should be, and a file that changed underneath the read. Every one of those guards was a bare throw at the top level — and because the script calls `require`, Bun 1.3 evaluates it as CommonJS, where an uncaught exception produces **no stderr and exit code 0**. So a refusal answered exactly like a success with an empty body; the caller still failed, on parsing that empty body, but surfaced `Spawner returned invalid JSON` with the reason it refused thrown away, and any future guard placed after a line that prints would have handed back a partial answer under a success code. Every refusal now writes its reason to stderr and sets a non-zero exit code explicitly, correct under both evaluation modes and on any Bun version; the CLI's test that had been executing the real reader bytes against a fixture — and had been red on the repository's check gate for exactly this — now also asserts that the reason reaches stderr.\n\n## Behaviour changes\n\n- **Settings > Enterprise SSO, Microsoft Entra ID**: an **App role** rule's **Matches value** is compared against the app role's **Value** as defined in the app registration (for example `Administrator`), read from the ID token's `roles` claim at sign-in — never its display name or id. The Graph `/me/appRoleAssignments` query is gone, so the connection no longer needs `AppRoleAssignment.ReadWrite.All` or `Directory.Read.All`, and a sign-in no longer logs the `403` that query produced; `GroupMember.Read.All` stays the permission group-to-team sync needs. A user Entra assigned no app role has no `roles` claim and, as before, lands on the default role. A **Claim** rule on an Entra connection now reads the ID token's claims (`roles` included); a generic OIDC, OAuth2 or SAML connection, whose adapter already resolved raw claims, is unchanged. When _Auto-assign roles from the IdP_ is on, the mapped role is applied at every sign-in, so a member whose app role maps to a higher role receives it at their next sign-in after the upgrade.\n- **Sandbox egress health**: `compose.yml`, the CLI-generated compose and the `tale-sandbox-egress` image probe the proxy with `curl -sS -o /dev/null --max-time 3 --noproxy '*' http://127.0.0.1:3128/`; the generated service and the image give the probe a 5-second timeout (was 3, so a slow proxy is reported by curl with a reason instead of being cut off by Docker at the same moment), and the intervals and retry counts are as they were. The proxy's log no longer carries a `read_request_line` error per interval. The smoke test the release gate runs asserts the same probe against the read-only `/tmp`.\n- **ui.tale.dev**: with the three collector variables set, the tracker loads and a pageview is sent per resolved home or guide route; without them, nothing changes. The site's README carries the variables and the collector boundary.\n- **The CLI, managed deployments**: a refusal of the configuration read reaches the operator as its reason (`config mount differs`, and the four others) with a non-zero exit, instead of `Spawner returned invalid JSON`; a read that succeeds is unchanged.\n- **The AI gateway** (new, standalone): the panel at `/` behind `AI_GATEWAY_PANEL_PASSWORD`, with **Add account** (Anthropic prints a code to paste; OpenAI lands the browser on a `localhost:1455` address that may not load, and the whole address bar is what you paste), **Reauthenticate**, **Copy CLI command** and **Remove** per row, usage bars per account, English, German and French, light and dark; `GET /api/tokens` behind `AI_GATEWAY_API_KEY`; `GET /api/health` open. An expired account stays listed, with `status: \"expired\"`, so the caller sees what it has and the panel can offer re-authentication; the usage figures can be up to three minutes stale by design, because both vendors rate-limit their usage endpoint per token.\n- **Component library**: `@tale/ui/icons/openai-icon` exports `OpenAIIcon`, the same path the marketing site already draws.\n- **Documentation** (English, German and French): the Enterprise SSO page says what an App role rule matches and that it needs no Graph permission, and its troubleshooting row says a rule matches the Value, not the display name or id; the Own Compose health-check table and the Kubernetes readiness table carry the new egress probe, and the Kubernetes manifest's `sandbox-egress` readiness probe is an `exec` of the same command instead of `tcpSocket`.\n- **The contributor tooling**: `bun run gen` writes the generated tree into the repository root (`--dest .`), where before it silently wrote under `tools/plop/` and reported success naming the path it did not use; the React-service template bundles its Bun server into one file for the runner stage, exports `PORT` from its entrypoint, ignores its build outputs in lint, ships a valid `keys-dynamic.yml`, and orders its imports as the formatter wants; `ai-gateway` is a commitlint scope; the repository's Checks workflow hands its build job the workflow token, so the marketing site's releases fetch no longer fails with a rate-limited `403` on a shared runner. `bun run docker:test:ai-gateway` builds and probes the gateway image the way `docker:test:ui-docs` does the component site, through `compose.ai-gateway.yml` and the new `compose.ai-gateway.test.yml`; the CI environment file carries the four test-only secrets it boots on.\n\n## API contract changes\n\n- **None in this range. The contract stays at 1.20.0**: 86 paths, 135 operations, 63 schemas, 167 `Error.code` values; the shipped `openapi.json` is byte-identical to 0.5.45's, and `X-Tale-Api-Version` answers `1.20.0`.\n- **MCP**: unchanged.\n- **The AI gateway's own API** (`/api/session`, `/api/providers`, `/api/accounts…`, `/api/tokens`, `/api/health`) is a separate service's surface, not part of the platform contract or its OpenAPI document; its routes are listed in the service README.\n\n## Security\n\n- **The Entra connection needs fewer permissions (#3455)**: the Graph app-role query and the delegated `AppRoleAssignment.ReadWrite.All` / `Directory.Read.All` consent it needed are gone. The app roles are read from the ID token the token endpoint handed the backend over TLS in the authorization-code exchange — never from anything the browser supplied; the claims are trusted the way the access token beside them is, and no signature check is repeated (the browser-supplied `state`, by contrast, is verified before it is read). A `roles` entry that is not a string is dropped; a token that is not a three-part JWT with a JSON-object payload yields no roles and no raw claims.\n- **The managed runtime's configuration guards now refuse (#3453)**: the five refusals — foreign config mount, linked file, oversize file, directory, file changed under the read — exit non-zero with their reason. Nothing was bypassed before: the empty answer failed the caller anyway. What is closed is the shape in which a later guard could have returned a partial answer under a success code.\n- **The AI gateway (#3452)**: tokens at rest are AES-256-GCM under `AI_GATEWAY_ENCRYPTION_KEY` with the authentication tag pinned at 16 bytes — the SAST gate caught that the tag length had been left to Node's default, which accepts a forgeable 4-byte tag, and a test holds the pin. The panel session is a cookie signed by `AI_GATEWAY_SESSION_SECRET`; the token endpoint accepts only `AI_GATEWAY_API_KEY`, and every panel route refuses an API-key holder as the token endpoint refuses a panel session. **`GET /api/tokens` hands out raw OAuth access tokens to any holder of the key** — the key is the credential to protect, and the service is for accounts you own, within each vendor's terms. Production refuses to start without the four secrets and names the missing ones; development generates them per process and prints them once. Error reporting is off unless `SENTRY_DSN` is set.\n- **Dependencies**: no third-party package version changes. The lockfile gains the `@tale/ai-gateway` workspace, whose dependencies (`hono` 4.13.5, `zod` 4.3.6, `@tanstack/react-table` 8.21.3, `@playwright/test` 1.58.2, `@types/bun` 1.3.11) resolve to versions the workspace already carried. The dependency-audit and image-configuration lane passed on the change that added the workspace, and the platform image scan (Trivy) passed on the release commit.\n\n## Known issues\n\n- **The Entra app-role fix is not verified against a live Entra tenant in this release.** It is proved against the claim shape Microsoft documents (the `roles` claim holds the role Value and is included in the ID token of an app that signs in users) with stubbed tokens in the adapter, role-mapping and callback tests. A rule whose **Matches value** is an app role's id (a GUID) — which could only ever have matched on a connection whose administrator had granted the Graph consent the guide never asked for — no longer matches; rewrite it to the role's Value.\n- **The AI gateway is deployed on its own and documented in its README only.** `tale deploy` does not run it and no docs page covers it; its image is built and probed by the container test on a pull request that touches it and in the release gate, and its behaviour by the unit suites and the Playwright specs. The OAuth round trip itself is manual by nature (a vendor's consent screen, a real subscription), and so is handing a token to `claude` or `codex`. The image's health probe and the standalone Compose file's are bound to port 3004, so a deployment that overrides `PORT` must override the probe too. By design there is no inference proxy, no account rotation, no multi-user panel and no usage history; a plan badge keeps a light surface in dark mode until the shared `Badge` grows dark variants.\n- **An Own Compose or own Kubernetes deployment keeps the probe it declares.** The image's new `HEALTHCHECK` applies only where no Compose `healthcheck` or Kubernetes probe overrides it; a declared `nc -z` or `tcpSocket` keeps writing the error line per interval until it is changed to the command on the install pages.\n- **ui.tale.dev reports nothing until its deployment sets the three collector variables**; the fleet's own instance needs them in its runtime environment, and a change to them takes effect when the service is recreated.\n- Unchanged from v0.5.45, where each is described in full: a native OIDC client registered before 0.5.45 is refused by the CLI until its application type is backfilled with the one-statement `UPDATE` those notes give; ui.tale.dev ships one English tree; the old `oauthClient.type` and `public` columns stay; `team.memberCount` is a constant 0; the 0.5.42–0.5.44 window has no curated known-issues record, and the **Settings > Governance > Vision model** description (English, German and French) still says a model that already reads images never uses the vision model, which #3436 made inaccurate.\n- Unchanged from v0.5.41, where each is described in full: spend history booked before that release is not rewritten and a month-to-date total spans two pricing rules; off-peak and long-context pricing tiers are not modelled; cache prices are not surfaced; a pin to `deepseek-v4-flash` answers `CHAT_MODEL_UNKNOWN`; the Moonshot, Vercel and OpenRouter harness doors are declared from vendor documentation; the three lane-fix follow-ups (the Read hook's PDF guard, the resumed-retry replay, Z.ai's unused Anthropic door) are open; a shipped provider whose catalog carries no curated embedding entry is refused as an embedding provider; the custom-provider form's authoring limits and a bare listing's assumptions; `tale-vision --thinking disabled` is validated against a controlled upstream only; the chat scroll rounds `CHAT-F39` and `CHAT-F40` are browser-tested for the wheel and the follow latch only; the `bun dev` runtime-image step was not observed live.\n- Unchanged from v0.5.39, where each is described in full: a knowledge entry the REST door wrote before that release keeps `source: \"manual\"`; a scan reuses a robots verdict up to a minute old and a row stored earlier carries no sitemap list; the ask retraction is best-effort and forward-only; the app's own archive stays unfenced; `GET /api/v1/teams` is read-only and a complete set; the chat `content` cap counts UTF-16 code units; the `nullable` on a `oneOf` branch is the OAS 3.0 spelling; 0.5.38 shipped with generated notes and its four pull requests (#3424–#3427) have no known-issues record.\n- Unchanged from v0.5.37, where each is described in full: ledger rows booked before that release keep the subject they were booked under and a project agent can appear twice in **Top assistants** across the upgrade; `app.usage_events` is write-retired, not dropped; nothing in the schema forbids a door string in `usage_ledger.user_id`; the run list labels a keyed start `Started by api-key:…`; the `GOV-F20` round is manual; `llm` nodes are unmetered and a run carries no usage or cost.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; a task can be archived and restored over REST since 0.5.43 but still not deleted; 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`; 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; a run carries no usage or cost.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0112**; the knowledge database is unchanged; `db/migrate.ts` and `auth/auth.ts` are untouched, so Better Auth stays at 1.7.5 and its migrator has nothing to add — the boot runs the same idempotent `team.memberCount` default 0.5.45 introduced and nothing else.\n- **Rolling-deploy safe.** No schema, contract, catalog or message change; the previous and the new platform image read and write the same tables.\n- **No platform environment variable is added or removed**; the root `.env.example` is unchanged. No scheduled job, audit action, error code, catalog, provider definition or platform message key changes. The AI gateway ships its own `services/ai-gateway/.env.example`: four required secrets (`AI_GATEWAY_API_KEY`, `AI_GATEWAY_PANEL_PASSWORD`, `AI_GATEWAY_SESSION_SECRET`, `AI_GATEWAY_ENCRYPTION_KEY` — 32 base64-encoded bytes, irreplaceable: a new key cannot read the old store) and the optional `AI_GATEWAY_DATA_DIR`, `AI_GATEWAY_REFRESH_INTERVAL_SECONDS`, `AI_GATEWAY_USAGE_MIN_INTERVAL_SECONDS`, `AI_GATEWAY_TOKEN_REFRESH_SKEW_SECONDS`, `AI_GATEWAY_CLAUDE_CODE_VERSION`, `AI_GATEWAY_ANTHROPIC_CLIENT_ID`, `AI_GATEWAY_OPENAI_CLIENT_ID`, `SENTRY_DSN`, `SENTRY_ENVIRONMENT` and `PORT`.\n- **`compose.yml` changes in one place**: the `sandbox-egress` health-check command; its interval, timeout and retries are as they were. No `proxy`, `db` or `object-store` change. The standalone `compose.ai-gateway.yml` is new and independent of the platform stack.\n- **Images**: the **platform** image (the Entra adapter, the ID-token decoder, the callback), the **docs** image (three pages in three languages), the **ui-docs** image (the analytics client), the **sandbox-egress** image (its `HEALTHCHECK`) and the new **ai-gateway** image carry source changes. The **web** and **sandbox** images carry no source change of their own but are rebuilt, because the workspace lockfile they install from changed — the web image also copies the component library, whose new icon nothing in the site imports. The `proxy`, `db`, `sandbox-runtime`, `sandbox-buildkitd` and `sandbox-llm-gateway` images carry no source change. **No image in the stop-gated tier changes** — a plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window; `sandbox-egress` is in the tier every deploy recreates, so the new probe is live once the deploy finishes.\n- **The CLI changes in this range** — the compose generator's egress probe and the configuration reader's refusals — and so does the `compose.yml` a managed bundle carries, so the release executables are rebuilt and differ from 0.5.45; they report 0.5.46. **Move a managed deployment's CLI pin and runtime pin together.** Neither mixed pair refuses the other: a 0.5.45 CLI deploying a 0.5.46 workspace keeps rendering the TCP probe into the generated compose, which overrides the image's own `HEALTHCHECK`, so the egress log keeps its error line until the CLI is updated; a managed bundle takes `compose.yml` from the runtime revision, so it carries the new probe with the runtime pin.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.46` and `marketing-ui-v0.5.46` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.46\"`. `@tale/ui` changes in this range (`OpenAIIcon`), so `ui-v0.5.46` differs from `ui-v0.5.45`; `@tale/marketing-ui` does not change, so `marketing-ui-v0.5.46` is content-identical to its predecessor.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.45; there is no 0.5.42 deployment to be on):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`; the egress container is recreated with its new probe as part of the deploy. A deployment crossing 0.5.44 also takes Better Auth 1.7's tables and columns and the `memberCount` default at boot — and, if it declares native OIDC clients created by an older CLI, needs the one-statement backfill in the 0.5.45 notes; one crossing 0.5.41 runs migration 0112 at boot as well; one crossing 0.5.39 runs migration 0111, one crossing 0.5.38 runs that release's `0108_approvals_one_pending_conversation_draft.sql`, one crossing 0.5.37 runs migration 0110, one crossing 0.5.36 runs migration 0109, one crossing 0.5.35 runs migration 0108 (`0108_trusted_header_keys.sql`) and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Before you upgrade, on an Entra connection with App role rules**: check that each rule's **Matches value** is the app role's **Value** from the app registration, not its display name or id; the rule starts matching at each member's next sign-in. Nothing else needs re-pointing: no catalog, model, environment variable or contract field changes in this range.\n\n- **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.\n\n- **The AI gateway** is deployed on its own, not by `tale deploy`:\n\n  ```bash\n  cp services/ai-gateway/.env.example services/ai-gateway/.env   # fill in the four secrets\n  VERSION=0.5.46 docker compose -f compose.ai-gateway.yml pull\n  VERSION=0.5.46 docker compose -f compose.ai-gateway.yml up -d\n  ```\n\n  The pool lives in the `ai-gateway-data` volume; back it up, because losing it loses every authorized account, and keep `AI_GATEWAY_ENCRYPTION_KEY` with it. The panel answers on port 3004.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- fix(sandbox): probe the egress proxy with a request, not a connect by @yannickmonney in https://github.com/tale-project/tale/pull/3451\n- feat(ui-docs): count a pageview for every guide a reader opens by @yannickmonney in https://github.com/tale-project/tale/pull/3450\n- fix(cli): make the config reader's refusals actually refuse by @yannickmonney in https://github.com/tale-project/tale/pull/3453\n- feat(ai-gateway): pool Claude and ChatGPT subscriptions by @yannickmonney in https://github.com/tale-project/tale/pull/3452\n- fix(platform): read Entra app roles from the ID token roles claim by @larryro in https://github.com/tale-project/tale/pull/3455\n- fix(ai-gateway): build the image from the whole workspace manifest set by @larryro in https://github.com/tale-project/tale/pull/3456\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.45...v0.5.46","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.46","publishedAt":"2026-09-21T18:25:11Z"},{"tag":"v0.5.45","version":"0.5.45","name":null,"body":"**0.5.45 carries four merged pull requests and one direct commit — every change since 0.5.44.** Three numbers have gone by since the last curated notes, 0.5.41. `v0.5.42` was tagged on 2026-09-20 but its container test gate failed — the pull request it was cut on had just switched the gate from rebuilding every image on the runner to validating the pulled release images, and the sandbox spawner's local image aliases were not yet re-tagged from those pulls, which the next number added — so no multi-architecture manifest, no `latest` tag, no GitHub release and no executables were published under it, and the number is skipped. 0.5.43 and 0.5.44 were published with generated notes: 0.5.43's list names one pull request and 0.5.44's four, so the other nine changes in that window — pull requests #3436–#3440, which 0.5.43 was the first to ship, and four direct commits — are named in no release notes, and the window's move of the machine contract from 1.19.0 to 1.20.0 is recorded nowhere. This page says what that window changed where it matters to an operator and treats its known issues as unrecorded.\n\nThree themes in 0.5.45. The identity provider's library moves to the Better Auth 1.7 line, which closes the audience advisory the 0.5.33 notes flagged (**GHSA-p2fr-6hmx-4528**), takes the schema and API renames that came with it, and keeps the platform's plain-SQL team provisioning working across a column the new line declares without a default. A project file an agent wrote — a desk's invoice reading, a generated report — now offers **Delete** on its project row, which had been hidden together with the connector-synced rows. And the component library's own site, ui.tale.dev, stops sending a German or French reader to a page that does not exist, and its front page is rebuilt in the marketing design language it documents. The contract stays at **1.20.0**, no numbered migration ships, Better Auth's own migrator adds three tables and columns on seven at boot, no environment variable changes, and no image in the stop-gated tier changes, so a plain `tale deploy` is the whole upgrade — with one caveat for a deployment that provisioned a native OIDC client through the CLI, under _Known issues_.\n\n## Highlights\n\n### The identity provider moves to Better Auth 1.7.5 and closes the audience advisory (#3447)\n\nThe `better-auth` family — the core, `@better-auth/oauth-provider` behind _Continue with Tale_, `@better-auth/api-key`, `@better-auth/passkey` and their adapters — moves from 1.6.30 to 1.7.5. Before 1.7.0 a client could choose its access token's audience at the token endpoint without that choice being bound to the authorization grant (GHSA-p2fr-6hmx-4528, CVSS 6.4, medium; the change and the 0.5.33 notes also cite it as CVE-2026-67332). This deployment was never exploitable — the provider is configured with no external audience and the authorization-code grant only, the advisory's own workaround, in place since the provider shipped — but the fixed line is the remedy, and the 1.6.x line is not patched. The breaking changes of the 1.7 line are absorbed: the OIDC client's application type is `application_type` on the wire and `applicationType` in the store (was `type`); the `public` column is gone (a public client is now one whose token-endpoint auth method is `none`, which the CLI's policy refuses either way); the two-factor **enable** answer is a discriminated union whose secret-less arm both enrolment screens now refuse rather than rendering an empty QR code; and client creation runs inside the adapter's transaction. Better Auth 1.7 also probes the live schema when the instance is built and logs any mismatch; that probe is switched off, because the boot already reconciles the schema deliberately — it runs Better Auth's own migrator inside the app-wide advisory lock — and a rolling deploy would otherwise log a mismatch from the previous image for every column the new one is mid-way through adding.\n\nOne production break the real-Postgres gate caught and CI cannot see: 1.7 adds `team.memberCount` as NOT NULL with an application-level default only, and this codebase owns team membership in plain SQL — SCIM group provisioning and single-sign-on team mapping never call Better Auth's team API — so every one of those inserts would have failed on roll. The boot now gives the column a SQL default of 0 right after Better Auth's migrations, every boot, idempotently; the value is deliberately not maintained, because nothing reads it — the Teams surfaces count members live.\n\n### A project file an agent wrote can be deleted from the project (#3445)\n\nThe project **Knowledge** tab hid a row's **Delete** for every source other than a plain upload, which swept in the files an automation writes into a project. Such a row has no external sync behind it, so nothing restores it once deleted; the gate only meant to spare connector-synced files, whose next sync would bring them back. An operator a desk asked to remove a stray reading found no Delete and only **Remove from project**, which publishes the file organization-wide instead. The rule \"authored here versus owned by an external sync\" already existed twice — the controlled-record gate on the server and its client mirror — and the project tab carried a divergent third copy; all three now read one predicate: an upload, an agent-written file and a row with no provider recorded are authored, and every other value is a connector whose sync loop owns the row.\n\n### ui.tale.dev answers a German or French reader instead of redirecting them to a 404 (#3448)\n\nOpening the component site in a German browser answered `302 Location: /de`, and `/de` was **Page not found**; a `tale_locale=de` cookie — set by every reader who ever picked German or French on tale.dev — did the same. The site boots the shared React server from `@tale/ui`, whose locale negotiation is written for tale.dev and docs.tale.dev, which ship three URL trees; ui.tale.dev ships one English tree, with no locale segment in its routes and no `/de` or `/fr` prerendered. The server gains a `localeRouting` mode: `'path'`, the default, is today's negotiated three-tree shape and leaves web and docs untouched; `'none'`, which ui-docs passes, skips negotiation whole — no redirect, no `tale_locale` cookie the site does not own, no locale `Vary` on an answer that varies by neither header — and sends a stale `/de…` or `/fr…` back onto the tree with a 301, so a bookmark this server once minted still lands on its page. A real-server integration test boots both modes.\n\n### The component site's front page is composed like tale.dev (#3449)\n\nThe front page of ui.tale.dev, the marketing design language's own shop window, did not speak it: two left edges (every section heading centred where tale.dev aligns to the start), a hard seam under the navigation where the hero wash was painted on the header alone, a product window floating between empty strips, and an empty sixth cell in the card panel. The page is now composed the way tale.dev composes — one top wash on the shell, a left-aligned display hero with a quiet meta line (guide count, both packages, the licence), the stage attached straight under its heading, one left edge throughout, a card panel that spans its cells at every width with each card stating how many guides its section holds (read from the same navigation tree the rail renders, so the numbers cannot drift), a two-column install band and a closing band on the marketing gradient.\n\n## Behaviour changes\n\n- **Projects > Knowledge**: a row's **Delete** follows provenance. A file uploaded to the project and a file an agent wrote into it offer Delete (disabled with a reason under a legal hold or on a frozen controlled record, never absent); a file synced from a connector — OneDrive, Google Drive, SharePoint, Confluence, WebDAV — offers none, because the next sync would restore it; remove it at its source. **Remove from project** and **Mark as controlled record** are unchanged. The tab stays deliberately narrower than the Knowledge library's rule: a directly selected connector row the library lets you delete is still refused in the project, because the project listing does not carry the fields that rule reads.\n- **Two-factor enrolment** (the account settings section and the enrolment wall) is unchanged for the time-based codes this deployment registers; an enable answer that carries no TOTP secret shows the enable-failed error instead of advancing to a QR code with nothing behind it. That arm cannot occur today — the guard is a guard, not a reachable flow.\n- **The identity provider's client registration** — the app's identity door (`POST /api/app/identity/clients`, admin only) and the CLI's native client lane — sends and stores `application_type: web`; the read-back that compares a found client against the deployment's policy reads the same field, and a client record that is not an object reads as a configuration conflict rather than skipping the check.\n- **The CLI**: a retained provisioning intent an older CLI wrote — durable operator state on disk that still carries the client's application type under the provider's old key — is accepted and normalised on resume instead of hard-refusing as \"does not match its contract\"; every write uses the new key. The native client export verification no longer requires the retired `public` field.\n- **The provider capabilities that arrive with the 1.7 line** — DPoP-bound tokens, resource indicators, client assertions, back-channel logout, refresh-token rotation — are present in the schema and not configured: the provider still issues the authorization-code grant only, with an empty audience list and five-minute codes and tokens, and dynamic registration stays closed.\n- **ui.tale.dev**: a request with a German or French `Accept-Language` or a `tale_locale` cookie answers the English page with **200** (was a 302 into a 404); `/de` answers **301 → `/`** and `/fr/docs/components/button` **301 → `/docs/components/button`** (were 404); the site sets no `tale_locale` cookie and no `Vary: Accept-Language, Cookie`; an unknown path is still a 404. tale.dev and docs.tale.dev keep the negotiated three-tree behaviour — the new server option defaults to it and neither site passes the other mode.\n- **ui.tale.dev front page**: the composition above — one wash, one left edge, the stage under its heading, a full card grid with per-section guide counts, a two-column install band; two new ICU plural strings (`home.heroMeta`, `home.guideCount`) in English, German and French, no key removed.\n- **Documentation** (English, German and French): the project files page says which rows carry Delete and why a synced row does not; the ui-docs README states the single-tree serving contract and what adding a translated tree would take.\n- **The contributor contract** (`AGENTS.md`): a branch, where a repository asks for one, is named for the work — `<type>/<kebab-slug>` with a commitlint type, or `dist/` for published build output — never for the tool that did it.\n\n## API contract changes\n\n- **None in this range. The contract stays at 1.20.0**: 86 paths, 135 operations, 63 schemas, 167 `Error.code` values; the shipped `openapi.json` is byte-identical to 0.5.44's, and `X-Tale-Api-Version` answers `1.20.0`.\n- **For the record, because no curated notes said so:** 0.5.43 was the first published release to move the contract from **1.19.0 to 1.20.0** (#3438). The one addition is `PATCH /api/v1/projects/{id}/tasks/{taskId}` with an `archived` boolean — the REST door can archive and restore a task, which 1.19.0's `Task` description had recorded as a verb the door lacked; the `Task` schema's description changes accordingly. Paths, schemas and error codes are otherwise the same set as 1.19.0.\n- **MCP**: unchanged.\n\n## Security\n\n- **The Better Auth family moves to 1.7.5 (#3447)** and closes GHSA-p2fr-6hmx-4528 (`@better-auth/oauth-provider` from 1.4.8 before 1.7.0-beta.4; CVSS 6.4, medium): an access token for an unauthorized audience through a resource indicator not bound to the grant. The workaround the 0.5.33 notes described — `validAudiences` empty, the authorization-code grant only — stays in place and is no longer what stands between a deployment and the advisory. The dependency-audit and image-configuration lane (`bun audit`, Trivy) passed on the change; no other advisory is open.\n- **The enrolment screens refuse a secret-less two-factor answer (#3447)** — narrowed, not asserted, so a future OTP-configured server cannot render an empty QR code.\n- **The native client policy is unchanged in substance**: PKCE required, consent not skipped, `client_secret_post`, the authorization-code grant, the `code` response type, a `web` application type, the exact scope set and the organization stamp — now read from the renamed field. A client metadata record that is not an object is a conflict.\n- **Dependencies**: `better-auth`, `@better-auth/core`, `@better-auth/api-key`, `@better-auth/oauth-provider`, `@better-auth/passkey`, the adapters and telemetry 1.6.30 → 1.7.5; `@better-fetch/fetch` 1.3.1 → 1.3.2; the Better Auth packages pin their own nested `zod` 4.6.5 and `jose` 6.2.12, and the workspace's own pins are unchanged. No other dependency changes.\n\n## Known issues\n\n- **A native OIDC client registered before this release is refused by this release's CLI until its application type is backfilled.** Better Auth 1.7 renamed the stored field from `type` to `applicationType`; its migrator adds the new column empty and nothing copies the old value, so a client a 0.5.44-or-earlier `tale deploy` created (a `clients` entry in the deployment specification) reads back with no application type, and this release's CLI refuses it in both places it looks — the reconcile (`Existing native client security policy does not match.`) and the credential-export verification (`Native credential export verification failed; no credentials were changed.`). Proved on a database the 0.5.44 image had booted. No managed deployment in the fleet carries such a client, and a deployment without native clients is unaffected. The remedy, once the new platform image has booted (the column exists only then) and before the deploy's native-client phase is retried, is one statement on the application database, after which both checks pass: `UPDATE \"oauthClient\" SET \"applicationType\" = \"type\" WHERE \"applicationType\" IS NULL AND \"type\" IS NOT NULL;` A boot backfill that does this for every deployment is the follow-up.\n- **ui.tale.dev ships one English tree.** A German or French preference a reader set on tale.dev is not honoured there, by design of the single tree; adding a translated tree means prerendering it and switching the site's server back to path routing, as its README says.\n- **The old `oauthClient.type` and `public` columns stay** on the table — Better Auth's migrator never drops a column — and nothing in this release reads or writes them.\n- **`team.memberCount` is a constant 0** on every team, kept by the SQL default and never maintained; anything that starts calling Better Auth's own team endpoints has to maintain it first. The Teams surfaces do not read it.\n- **The 0.5.42–0.5.44 window has no curated known-issues record.** Its fourteen changes: the sandbox vision lane armed for every managed gateway turn (#3436); every entity list led by the same icon and name (#3437); the REST task archive door (#3438); an indexing run ended when its document is released mid-run (#3439); the release pipeline validating and publishing from the tag (#3440, #3441); every nav section opening on its own first page, every content frame on the same inset, every classified chat error labelled on chat health (direct commits); every table with the same footer and the trash frame fixed (#3442); the opt-in compact Claude harness (#3443); pinned Python document libraries baked into the sandbox runtime image (#3444); the runtime's document tools verified on both release architectures (#3446). Two gaps from that window are known: the **Settings > Governance > Vision model** description (English, German and French) still says a model that already reads images never uses the vision model, which #3436 made inaccurate — the lane is armed for every managed gateway turn, and a model that reads images serves as its own lane model; and 0.5.43's generated list omits the five pull requests it was the first to ship.\n- Unchanged from v0.5.41, where each is described in full: spend history booked before that release is not rewritten and a month-to-date total spans two pricing rules; off-peak and long-context pricing tiers are not modelled; cache prices are not surfaced; a pin to `deepseek-v4-flash` answers `CHAT_MODEL_UNKNOWN`; the Moonshot, Vercel and OpenRouter harness doors are declared from vendor documentation; the three lane-fix follow-ups (the Read hook's PDF guard, the resumed-retry replay, Z.ai's unused Anthropic door) are open; a shipped provider whose catalog carries no curated embedding entry is refused as an embedding provider; the custom-provider form's authoring limits and a bare listing's assumptions; `tale-vision --thinking disabled` is validated against a controlled upstream only; the chat scroll rounds `CHAT-F39` and `CHAT-F40` are browser-tested for the wheel and the follow latch only; the `bun dev` runtime-image step was not observed live.\n- Unchanged from v0.5.39, where each is described in full: a knowledge entry the REST door wrote before that release keeps `source: \"manual\"`; a scan reuses a robots verdict up to a minute old and a row stored earlier carries no sitemap list; the ask retraction is best-effort and forward-only; the app's own archive stays unfenced; `GET /api/v1/teams` is read-only and a complete set; the chat `content` cap counts UTF-16 code units; the `nullable` on a `oneOf` branch is the OAS 3.0 spelling; 0.5.38 shipped with generated notes and its four pull requests (#3424–#3427) have no known-issues record.\n- Unchanged from v0.5.37, where each is described in full: ledger rows booked before that release keep the subject they were booked under and a project agent can appear twice in **Top assistants** across the upgrade; `app.usage_events` is write-retired, not dropped; nothing in the schema forbids a door string in `usage_ledger.user_id`; the run list labels a keyed start `Started by api-key:…`; the `GOV-F20` round is manual; `llm` nodes are unmetered and a run carries no usage or cost.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential. The dependency advisory that release left open is closed by this one.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; a task can be archived and restored over REST since 0.5.43 but still not deleted; 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`; 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; a run carries no usage or cost.\n\n## Migration notes\n\n- **No numbered migration.** The application database stays at **0112**; the knowledge database is unchanged. `db/migrate.ts` and `auth/auth.ts` change, but neither adds a `.sql` file.\n- **Better Auth's own migrator runs at boot**, inside the app-wide advisory lock, after the numbered files, additive only — it creates missing tables and adds missing columns and never drops or alters one. Against a 0.5.44 database it plans **three tables and columns on seven** (verified with the release commit's planner on a database the 0.5.44 image had booted): the tables `oauthResource`, `oauthClientResource` and `oauthClientAssertion`; the columns `oauthClient.applicationType`, `.clientDiscoveryId`, `.clientCredentialsScopes`, `.backchannelLogoutUri`, `.backchannelLogoutSessionRequired`, `.jwks`, `.jwksUri`, `.dpopBoundAccessTokens`; `oauthAccessToken.authorizationCodeId`, `.resources`, `.requestedUserInfoClaims`, `.revoked`, `.confirmation`; `oauthRefreshToken.authorizationCodeId`, `.resources`, `.requestedUserInfoClaims`, `.rotatedAt`, `.rotationReplayResponse`, `.rotationReplayExpiresAt`, `.confirmation`; `oauthConsent.resources`, `.requestedUserInfoClaims`; `jwks.alg`, `.crv`; `team.memberCount`; `teamMember.membershipKey`. Every one is nullable except `team.memberCount` (NOT NULL), which the boot step right after — `ALTER TABLE IF EXISTS \"team\" ALTER COLUMN \"memberCount\" SET DEFAULT 0`, every boot, idempotent — gives its default, so a plain-SQL team insert that names no count lands; a raw `team` insert and a raw `teamMember` insert were both exercised after the roll. It is not a numbered migration for the same reason the provisioned-account catch-up is not: the numbered files run before Better Auth's tables exist.\n- **Rolling-deploy safe.** The previous image neither reads nor writes any of the new columns and tables; the new image's schema probe is off, so neither side logs a mismatch while the other is mid-roll; the advisory lock serialises concurrently booting containers.\n- **No environment variable is added or removed**; `.env.example` is unchanged. No scheduled job, audit action, error code, catalog, provider definition or platform message key changes; the ui-docs site gains two message keys in English, German and French. No `proxy`, `db`, `object-store` or `compose.yml` change.\n- **Images**: the **platform** image (the identity provider, the boot, the project tab, the shared provenance predicate), the **docs** image (one page in three languages), the **ui-docs** image (the front page, the server's single-tree mode) and the **web** image carry source changes — the last three because they bundle `@tale/ui`'s shared React server, which gains the `localeRouting` option; web and docs keep the default mode, so their behaviour is unchanged. The **sandbox** image carries no source change of its own but is rebuilt, because the workspace lockfile it installs from changed. The `proxy`, `db`, `sandbox-runtime`, `sandbox-egress`, `sandbox-buildkitd` and `sandbox-llm-gateway` images carry no source change. **No image in the stop-gated tier changes** — a plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- **The CLI changes in this range** — the native client lane's field rename and the retained-intent tolerance — and so do the reference tree it embeds (the shared provenance predicate) and the lockfile, so the release executables are rebuilt and differ from 0.5.44; they report 0.5.45. **Move a managed deployment's CLI pin and platform pin together; for a deployment with native clients this release requires it**: a 0.5.44 CLI against a 0.5.45 backend requires the `public` field and reads the application type from a field the backend no longer exposes, and a 0.5.45 CLI against a 0.5.44 backend expects an `application_type` the backend does not send — either mixed pair refuses the native client work. A deployment without native clients is unaffected by the pairing.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.45` and `marketing-ui-v0.5.45` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.45\"`. `@tale/ui` changes in this range (#3448: `stripLocalePrefix` and the `localeRouting` option on `startReactServer`), so `ui-v0.5.45` differs from `ui-v0.5.44`; `@tale/marketing-ui` does not change, so `marketing-ui-v0.5.45` is content-identical to its predecessor.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.44; there is no 0.5.42 deployment to be on):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Better Auth's migration and the `memberCount` default are applied at boot. Nothing in this release needs `--stop`. A deployment crossing 0.5.41 runs migration 0112 at boot as well; one crossing 0.5.39 runs migration 0111, one crossing 0.5.38 runs that release's `0108_approvals_one_pending_conversation_draft.sql`, one crossing 0.5.37 runs migration 0110, one crossing 0.5.36 runs migration 0109, one crossing 0.5.35 runs migration 0108 (`0108_trusted_header_keys.sql`) and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Before you upgrade, check for native OIDC clients.** A deployment whose specification declares `clients` and whose clients were created by a CLI older than this release needs the one-statement backfill under _Known issues_ after the new image boots; plan for the deploy's native-client phase to refuse once. Nothing else needs re-pointing: no catalog, model, environment variable or contract field changes in this range.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- docs: name branches for the work, not the agent by @yannickmonney in https://github.com/tale-project/tale/commit/ed257cbee658f2d05b8950012ef3ce70e44e12f2\n- fix(deps): update the better-auth family to 1.7.5 [security] by @yannickmonney in https://github.com/tale-project/tale/pull/3447\n- fix(platform): offer Delete on a project file an agent wrote by @larryro in https://github.com/tale-project/tale/pull/3445\n- fix(ui): serve a single-tree site without locale redirects by @yannickmonney in https://github.com/tale-project/tale/pull/3448\n- fix(ui-docs): build the front page in the marketing language by @yannickmonney in https://github.com/tale-project/tale/pull/3449\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.44...v0.5.45","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.45","publishedAt":"2026-09-21T09:01:59Z"},{"tag":"v0.5.44","version":"0.5.44","name":null,"body":"## What's Changed\n* fix(platform): give every table the same footer and fix the trash frame by @yannickmonney in https://github.com/tale-project/tale/pull/3442\n* perf(sandbox): bake pinned Python document libraries by @yannickmonney in https://github.com/tale-project/tale/pull/3444\n* feat(sandbox): add opt-in compact Claude harness by @yannickmonney in https://github.com/tale-project/tale/pull/3443\n* fix: verify document tools on both release architectures by @yannickmonney in https://github.com/tale-project/tale/pull/3446\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.43...v0.5.44","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.44","publishedAt":"2026-09-21T00:45:02Z"},{"tag":"v0.5.43","version":"0.5.43","name":null,"body":"## What's Changed\n* fix: prepare sandbox aliases from pulled release images by @yannickmonney in https://github.com/tale-project/tale/pull/3441\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.42...v0.5.43","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.43","publishedAt":"2026-09-20T20:24:16Z"},{"tag":"v0.5.41","version":"0.5.41","name":null,"body":"**0.5.41 carries 7 merged pull requests — every change since 0.5.39.** A `v0.5.40` tag was pushed on 2026-09-20 at the commit of the sixth of them, but its pipeline stopped at the container test gate — the image-validation step reached the job's twenty-minute limit while rebuilding the platform image on the runner — so no multi-architecture manifest, no `latest` tag, no GitHub release and no executables were ever published under that number; `latest` stayed on 0.5.39, and the tag remains, so the number is skipped. This release is the first that carries those six pull requests, with a seventh on top. Its themes: an organization's own inference endpoint — a vLLM or Ollama box, an internal gateway — is connected from the **Add credential** dialog instead of a file an operator writes; the model a person pinned in the chat composer remembers **which provider** served it; spend is booked at the vendor's **prompt-cache hit price** instead of the plain input rate, which had overstated a cache-heavy day of DeepSeek traffic about eleven-fold and \"reached\" a spend cap that was not, and a turn the gateway refuses for spend ends there instead of being retried three times on one-cent keys; a Claude Code automation turn on DeepSeek, Moonshot, the Vercel AI Gateway or OpenRouter rides the vendor's **native Anthropic door**, which closes the two 400s that killed an agent run on the down-converted wire; the chat transcript's send-snap wins against the thread-open hold and a trackpad's momentum, and **Scroll to bottom** follows a streaming reply; a reply that failed before its first token stops \"thinking\"; the organization's **embedding model** is picked from the provider's catalog, and a shipped provider whose catalog lists none is refused up front instead of failing at index time; and DeepSeek's catalog names the vendor's current Flash model. No contract change — `1.19.0` stays — one migration (0112) adds a nullable column, no environment variable changes, and no image in the stop-gated tier changes, so the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Define a custom AI provider from the settings UI (#3431)\n\nAn organization-owned provider — a self-hosted model server such as vLLM or Ollama, or an internal gateway that speaks the OpenAI or Anthropic API — existed only as a file under `TALE_CONFIG_DIR/<org>/providers/` that an operator wrote by hand or through the managed-configuration lane; from the app it looked impossible. **Settings > AI providers > Add credential** now pins a **Custom provider** entry under the catalog (it survives a search that matches nothing), and its setup step takes the provider's facts beside the key: a **Provider name** (it names the provider and the credential; the identifier is derived from it and numbered past an existing one), the **API format** (Chat Completions for vLLM, Ollama, LiteLLM and most gateways; Messages for Anthropic-compatible endpoints), the **Base URL**, and how the endpoint's **Models** are known — **Discover from the endpoint**, which reads its `/models` with this key, or **Enter model IDs** for a server that cannot list them. One submit writes the definition file through the existing definition door and then the credential; a credential the server refuses rolls the definition back. The row and the picker carry a **Custom** badge; the row menu's **Check models** lists the endpoint afresh with the organization's key; **Edit credential** edits the provider's facts against the definition's loaded hash; and deleting the provider's last credential retires the provider itself — the dialog says so beforehand, the exact previous file is archived under `.history/<name>/` like every save, and the audit log records `provider_definition.deleted`. Behind it, `DELETE /api/app/providers/definitions/{name}` (admin or developer, an optional `expectedHash` compare-and-set) is refused with 409 `PROVIDER_IN_USE` while any credential still names the slug, so keys are retired deliberately and never orphaned. Two listing defects surfaced with the first real custom provider and are fixed: a live `/models` is fetched with the organization's default key for the provider — most hosted OpenAI-compatible endpoints refuse an anonymous listing, which is what \"catalog fetch returned HTTP 401\" was, and a remembered anonymous refusal no longer holds back the first keyed attempt — and a bare OpenAI-shape listing (`id`, `object`, `created`, `owned_by`, the shape of api.openai.com, DashScope, DeepSeek, vLLM and Ollama) no longer normalizes to nothing: such an entry is admitted under the same assumed 128,000-token window an allowlist entry carries and reads as tool-capable, while a catalog that publishes its windows (OpenRouter) stays strict. A private or loopback address still needs the deployment's own opt-in, which an operator sets; the dialog names it. The providers page gains **Define a custom provider** in English, German and French, and the self-hosted providers page and the local-provider tutorial point at it.\n\n### The sticky model pick remembers its provider (#3431)\n\nThe composer's sticky pick stored the model id alone, and a new chat was seeded by id: a model listed by a shipped provider _and_ by a custom provider on another endpoint of the same vendor landed on the shipped copy — whose key the person never meant — and failed with that provider's refusal. **Migration 0112** adds `chat_model_provider_slug` to the preferences row; the preference saves and serves the (provider, id) pair, the seed prefers the exact pair and falls back to whichever provider serves the id for a pick saved before providers were part of it, the thread-title lane names the thread on the pick's own connector, and the picker's section headers show each provider's display name, so an organization-defined provider reads by the name its admin gave it rather than by its slug.\n\n### Spend is booked at the vendor's cache-hit price, and a spend refusal ends the turn (#3433)\n\nAn automation agent run ended with \"the setup assistant could not run\". Its record: the node's gateway key had been minted at the $1.51 the organization's monthly cap had left, the gateway refused the forty-third call with `402 Model-level budget exceeded`, and the auto-retry then resumed the same transcript three times on one-cent keys, each dead on its second call. The cap had been reached on overstated data: for that day the vendor console showed ¥6.50 for 33 million tokens, the platform had booked $10.20 for the same traffic — 87 % of it prompt-cache hits — because the gateway had been told only an input and an output rate, so it billed every cached token at the input rate (its documented fallback), and always at the peak rate. Three fixes. **Cache hits are priced**: a catalog entry's `pricing` gains optional `cacheReadCentsPerMillion` and `cacheWriteCentsPerMillion`; every shipped catalog carries the vendor's cache prices as published on 2026-09-20 (49 of the 59 shipped entries; the file headers cite the listings), a live listing's `pricing.input_cache_read` and `input_cache_write` (OpenRouter and the Vercel gateway spell them the same) are read into the entry, a hit price above the input price is dropped as a listing glitch, the pricing patch pushed to the sandbox gateway carries `cache_read_input_token_cost` and `cache_creation_input_token_cost`, a stored patch that lacks a cache rate the catalog now has is rewritten rather than trusted, and the chat lane's one cost formula bills the reported cached share at the hit price (the ledger entry carries `cachedInputTokens`). Base prices moved where the vendor page had: `claude-sonnet-5` is priced at last ($2 / $10 per million), `gpt-5.6-sol` $5 / $30 → $4 / $20, `kimi-k2.6` and `kimi-k2.7-code` → $0.95 / $4, `glm-5.1` and `glm-5.2` → $1.40 / $4.40, and `deepseek-v4-pro` → $1.32 / $3.96 (see the DeepSeek highlight). **A mid-turn 402 is a spend refusal, not a provider hiccup**: both agent hosts settle a harness result carrying API status 402 as `budget_exceeded`, with a reason that names the exhausted allowance and keeps the gateway's line, and neither lane's retry gate re-kicks it — a resumed retry replayed the whole transcript into a key sized from the same balance. **Settlement reads the key's live spend**: the gateway meters in memory and dumps its counters to the store every ten seconds, and the plain read served the store, so a settle seconds after a turn's last call missed that call (a 156-cent turn booked 150; a one-call retry booked 0); the read now asks for the live counters the gateway's own budget gate meters and falls back to the stored row only when the live index lacks the key. The REST thread's model view keeps its documented input/output pair; the cache prices are billing inputs, not part of the contract.\n\n### Claude Code automation turns ride the connector's native Anthropic door (#3432)\n\nAn automation `agent` node running Claude Code on DeepSeek rode the connector's OpenAI gateway record although the shipped definition declares the vendor's native Anthropic endpoint and the serving resolver said so: the workflow agent host never read the flag on the kick's routing, the scheduled start, the key mint or the answered-ask resume, where the task lane has threaded it since 0.5.19. The down-conversion still breaks on the shipped gateway: both agent nodes of a 2026-09-20 run lost their first attempt within ten seconds to `400 The reasoning_content in the thinking mode must be passed back to the API` on a fresh session's second call, and the setup node then read two PDFs, which Claude Code attached as Anthropic `document` blocks, the gateway turned into a `file` part, and DeepSeek refused with `400 … file must have a file_id or file_data` on every resumed retry. The lane now reaches all four places, so the exec model, the scheduled start's arguments, the minted key's allowed-model reference and the provisioned record all name the connector's `…__anthropic` record. The shipped connectors were checked against vendor documentation for a native door: **Moonshot** (`api.moonshot.ai/anthropic`), the **Vercel AI Gateway** (`ai-gateway.vercel.sh`) and **OpenRouter** (`openrouter.ai/api`) now declare one beside DeepSeek's — from the vendor docs, not live-probed from this platform — and a gateway-standard connector on the Claude Code lane (OpenRouter) takes an organization-scoped record of its own, because the gateway's built-in OpenRouter implementation speaks only the OpenAI wire to the vendor: a Claude model then passes through natively in both directions instead of being down-converted here and converted back there. Z.ai keeps no door on purpose (its Anthropic endpoint silently drops image blocks for every model, which a harness endpoint cannot yet declare), and Qwen's door is a workspace-specific host that a shared definition cannot name — an organization declares it on its own connector.\n\n### The chat send-snap outranks the thread-open hold and a trackpad's momentum (#3433)\n\n\"Sending sometimes does not scroll\" had two reproduced causes. Opening a thread arms a two-second position hold, and a content tick under a live hold returned before it consumed the send intent, so a send within that window never scrolled once the reply settled inside it. And any wheel or touch movement over the transcript cancelled the snap, direction ignored, so a trackpad's momentum tail after scrolling to the bottom — or a one-pixel downward wheel with the pointer resting over the messages — killed the glide mid-flight. A send or edit intent now wins over any live hold on the next content tick; only an upward wheel turn or a finger travelling down the screen counts as taking over; and **Scroll to bottom** pressed while a reply streams engages a follow latch that keeps the view at the growing bottom until the person scrolls up, sends, or opens another thread — it survives the client-side reveal that keeps draining text after the server settled, where a one-shot jump had left the button back on screen a second later. The button no longer flashes during the send glide, and a glide that lands late on slow frames gets a fresh 250 ms settle. Underneath, the transcript is one list with a spacer after it instead of three lists around the last user message: a send used to move the previous turn's rows between lists, which remounted them, and a freshly inserted lazily-rasterized row spends its first frames at a 200-pixel placeholder, so the content above the new message jumped on every send and, after a long reply, clamped the scroll position out from under the glide. Every row now keeps its rendered height as it becomes history, and the spacer is written synchronously from a mutation observer, so the scroll height no longer bounces on every streamed chunk — the wobbling scrollbar thumb is gone. A real-Chromium browser test covers the hold-plus-send, the wheel directions, row identity and the follow latch.\n\n### A reply that failed before its first token stops thinking (#3431)\n\nA turn that failed before any text — a refused key, a stop before the first token — kept \"Thinking · Ns\" ticking under its error: the failed settle drained the row like any other, and with no text nothing ever painted a first glyph, so the pre-answer shell stayed. The shell now drops on a terminal row (failed, stopped, refused), and the thread view never presents a terminal row as streaming, not even under a generation row that outlived its settle; a failure mid-stream settles what the server wrote, never a tail it refused.\n\n### The embedding model is picked from the provider's catalog (#3435)\n\n**Settings > Data residency > Embedding model** took the model as a free-text tag that failed only at index time. The **Model** row now reads the organization's provider catalogs — the same listing the AI-providers and governance pages use — and lets the catalog decide: a provider whose catalog lists embedding models gets a closed select over them, and a pick fills **Vector width** from the catalog; an organization-defined provider whose listing tags embedding models gets the same select plus **Other model…**, which opens a **Model tag** field, because a bare `/models` listing tags nothing as an embedding model; a shipped provider whose catalog lists none is refused — \"The {provider} catalog lists no embedding model. Choose a provider that serves one.\" — with no field and the shared Save off; a shipped catalog that could not be loaded is refused naming the remedy; and only a provider with no listing to consult (an organization-defined one without an entry, Azure's `catalog: none` deployments) takes a typed tag, with a hint that says why. Three shipped catalogs list an embedding model today: OpenAI (`text-embedding-3-small`), OpenRouter (`qwen/qwen3-embedding-8b`) and Z.ai (`embedding-3`), each at width 1536. The `@tale/ui` `Select` gains `errorMessage`, rendered under the control the way `Input` renders its own — an error routed through `description` landed in the label column of a settings row — and the form's three selects use it. The data-residency page says so in English, German and French.\n\n### DeepSeek's catalog names the vendor's current Flash model (#3429)\n\nDeepSeek released **DeepSeek-V4.1-Flash** on 2026-09-10 under the API id `deepseek-flash` — there is no `deepseek-v4.1-flash` on the vendor API — and retired `deepseek-v4-flash`, which is \"temporarily routed\" to the new model; the shipped catalog still offered the retired id, and the Pro price had been stale since the vendor's 2026-08-16 pricing change. `deepseek-flash` replaces `deepseek-v4-flash`: native image input, a 1,048,576-token window, 384,000 output tokens, the same effort knob, and the official **peak** rates ($0.30 / $1.20 per million, cache hit $0.006) — DeepSeek bills half of that off-peak and the one input/output pair the schema carries cannot express a time of day, so the conservative figure ships; `deepseek-v4-pro` moves to $1.32 / $3.96. On OpenRouter, `deepseek/deepseek-v4.1-flash` joins the curated defaults at the aggregator's price and `deepseek/deepseek-v4-flash` stays, because third-party hosts still serve it there. **Auto**'s draft band prefers `deepseek-flash`, then `deepseek-v4.1-flash` (the aggregator's spelling), in place of the retired id. Shipped catalogs change only with a release (**Refresh catalogs** skips `source: static`), so the card shows the new default after the deploy.\n\n### A sandbox vision batch can switch the model's thinking off (#3434)\n\nThe sandbox runtime's `tale-vision` tool — the batch that reads images for a text-only agent — can spend a page's per-image deadline on the model's reasoning before it returns a transcription. `tale-vision --thinking disabled` sends the standard disabled-thinking request for that batch; the default (`provider`, or omission) leaves the provider's behaviour and the historical cache keys unchanged, and an explicit override caches under its own key. Nothing in the platform passes the flag yet; it is there for a deployment that calls the tool directly. Image bytes, model selection, output limits, deadlines and the ordinary Read fallback are untouched. The runtime's README documents it.\n\n### `bun dev` builds the sandbox runtime image when it is missing (#3430)\n\nFor contributors: the host development loop brought up every Compose backing service but never the sandbox **runtime** image, which is neither a Compose service nor a registry image, so on a fresh checkout — or after a local image cleanup — the first agent session died with `Unable to find image 'tale-sandbox-runtime:latest' locally`. After the backing services and the gateway wait, `bun dev` probes for the image and, when it is missing, runs the same one-time build `docker:dev` uses (several minutes, labelled as such); a failed build degrades to a warning that carries the exact retry command, and the fleet keeps booting with only sandbox sessions unavailable. The contributor-setup page says so.\n\n## Behaviour changes\n\n- **Settings > AI providers > Add credential** pins a **Custom provider** entry under the catalog; its setup step takes **Provider name**, **API format**, **Base URL**, **Models** (**Discover from the endpoint** or **Enter model IDs**) and the key. Organization-defined providers carry a **Custom** badge in the row and in the picker; the row menu gains **Check models**; **Edit credential** edits the provider's facts; deleting the last credential retires the provider after a warning. The shipped providers and the file lane are unchanged.\n- **A live model listing is fetched with the organization's default key** for the provider (models-endpoint catalogs only); a remembered anonymous refusal no longer blocks the first keyed attempt. A bare OpenAI-shape `/models` entry is admitted under a 128,000-token window and reads as tool-capable; OpenRouter's listing stays strict.\n- **The composer's sticky pick saves and seeds the (provider, model) pair**; a pick saved before this release resolves by id, as before. Picking **Auto** clears both. The picker's section headers are the providers' display names. The thread-title lane names a thread on the pick's own connector.\n- **A reply that ended without text** shows its notice without the dots or the ticking timer; a stopped-before-the-first-token row renders **Generation stopped**.\n- **Chat scrolling**: a send or edit snaps the new message to the top even inside the thread-open window; a downward wheel or a sideways swipe over the transcript no longer cancels the glide, an upward one still does; a finger travelling down the screen escapes, one travelling up does not; **Scroll to bottom** during a stream keeps following the reply until an upward scroll, a send or a thread change; the button stays hidden during the send glide; the previous turn's rows keep their height when a send demotes them; the scroll height no longer bounces per streamed chunk.\n- **Spend**: the usage ledger and the per-message cost bill a provider-reported cached share at the catalog's cache-hit price (the input rate when the catalog has none); the gateway's per-model pricing carries the cache pair; agent-turn settlement books the key's live counters. **Historical rows are not rewritten.**\n- **A managed agent turn that the gateway refuses with 402** — the key's budget, sized from the organization's remaining cap, is spent — settles as `budget_exceeded` with a reason naming the exhausted allowance; neither the automation nor the task auto-retry resumes it (was: `harness_error`, retried three times on one-cent keys).\n- **A Claude Code automation `agent` turn** on DeepSeek, Moonshot, the Vercel AI Gateway or OpenRouter rides the connector's native Anthropic endpoint through an organization-scoped `…__anthropic` gateway record on the kick, the scheduled start, the key mint and the answered-ask resume; the vision model's routing is unchanged; every other harness keeps the record it used. Z.ai and Qwen keep the OpenAI base.\n- **Shipped catalogs**: `deepseek-flash` (vision) replaces `deepseek-v4-flash`; `deepseek/deepseek-v4.1-flash` is added on OpenRouter; 49 entries carry cache prices; the base prices of `claude-sonnet-5`, `gpt-5.6-sol`, `kimi-k2.6`, `kimi-k2.7-code`, `glm-5.1`, `glm-5.2` and `deepseek-v4-pro` change as listed above. **Auto**'s draft band prefers `deepseek-flash`.\n- **Settings > Data residency > Embedding model**: **Model** is a select over the provider's catalog where the catalog lists embedding models (a pick fills **Vector width**), a select plus **Other model…** for an organization-defined provider whose listing tags them, a refusal for a shipped provider whose catalog lists none or could not be loaded, and a typed **Model** tag only where no listing can tell. A stored tag a shipped catalog does not list shows as its own entry, so nothing configured is hidden. Switching the provider empties the model.\n- **`@tale/ui` `Select`** takes `errorMessage`, rendered under the control with `role=\"alert\"` and announced with the trigger; it implies the invalid state.\n- **`tale-vision --thinking disabled`** in the sandbox runtime sends `thinking: {type: \"disabled\"}` for that batch and caches apart from the provider default; `--thinking provider` and omission are unchanged.\n- **`bun dev`** builds `tale-sandbox-runtime:latest` from source when it is missing (contributor loop only).\n- **Documentation** (English, German and French): **Define a custom provider** on the providers page with the self-hosted providers page and the local-provider tutorial pointing at it (and the listing's assumed window \"when the listing publishes neither\"); the chat basics say the pick keeps its provider; the data-residency page describes the catalog-driven **Model** row; the contributor-setup page describes the runtime-image build; the `@tale/ui` docs say `Select` takes `errorMessage`.\n\n## API contract changes\n\n- **None. The contract stays at 1.19.0**: 86 paths, 134 operations, 63 schemas, 167 `Error.code` values; `X-Tale-Api-Version` answers `1.19.0`, and `generate:openapi` on the release commit reproduces the shipped document byte for byte. The REST thread's model view keeps its documented `pricing` pair; the catalog's cache prices are deliberately not on the wire.\n- **App doors, not the machine contract**: `DELETE /api/app/providers/definitions/{name}` (admin or developer; optional `expectedHash`; 409 `PROVIDER_IN_USE`, 404 `PROVIDER_NOT_FOUND`); `DELETE /api/app/provider-credentials/{id}?retireUnusedCustomProvider=1`; `GET /api/app/providers/catalogs` rows carry `origin: \"shipped\" | \"organization\"`; the chat-model preference write takes `providerSlug` beside `modelId` and the preferences read answers `chatModelProviderSlug` beside `chatModelId`; the composer's model options carry `providerLabel`.\n- **MCP**: unchanged.\n\n## Security\n\n- **Custom provider definitions are written and deleted only by an admin or developer (#3431).** The delete door refuses while any credential names the provider (409 `PROVIDER_IN_USE`), takes an optional compare-and-set on the definition's reviewed hash, archives the exact previous file under `.history/<name>/`, and writes a `security`-category audit row (`provider_definition.deleted`). A base URL on a private or loopback host is refused unless the deployment opted in (`TALE_ALLOW_PRIVATE_PROVIDER_HOSTS`), and a public host needs `https`; cloud-metadata addresses stay blocked. A name that belongs to a shipped provider is refused as reserved.\n- **The organization's provider key now travels to the provider's own `/models` listing (#3431)** — the same endpoint the key already serves chat calls to — as a bearer on the listing request. It is never logged and never part of the catalog cache key; a listing is the provider's catalog whoever fetched it. A provider with no usable default credential lists anonymously, as before.\n- **A spend refusal is final (#3433).** A turn the gateway refuses with 402 is settled as `budget_exceeded` and not re-kicked, so an exhausted cap no longer funds three more transcript replays on one-cent keys; and a turn's last calls are booked from the gateway's live counters instead of a stale row.\n- **The minted key follows the record the session calls (#3432).** On the Claude Code lane the key's allowed-model reference binds to the connector's `…__anthropic` record — the same routing the exec model and the provision use — and the pricing override is provisioned on that record, so a session on the native door is metered and capped like one on the OpenAI record.\n- **The sticky pick's provider slug is validated** (lowercase letters, digits and single hyphens, at most 120 characters) before it is stored (#3431).\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **0.5.40 was never published.** The `v0.5.40` tag exists at the commit before this release's last pull request; its per-architecture images (`0.5.40-amd64`, `0.5.40-arm64`) reached the registry, but no `0.5.40` manifest, no release and no executables did, so a deployment cannot pin that number. Move to 0.5.41.\n- **Spend history is not rewritten.** Ledger rows and per-message costs booked before this release keep the overstated figures; cost figures from this release on are lower for cache-heavy traffic, so a month-to-date total spans two pricing rules. A budget that was \"reached\" on the old figures frees up only as new, correctly priced usage replaces the window.\n- **Off-peak pricing is not modelled.** The shipped gateway cannot express a time-of-day rate, so DeepSeek's catalog carries the peak rate all day and books double the vendor's off-peak charge; nor are long-context tiers (xAI at 200,000 tokens and above, OpenAI beyond 272,000) — one pair per model.\n- **Cache prices are not surfaced** in the model popover, over REST or in the OpenAPI document; they are billing inputs only.\n- **A pin to `deepseek-v4-flash`** — a sticky pick, an agent's `supportedModels`, a governance default, an automation `llm` node, a REST caller — answers `CHAT_MODEL_UNKNOWN` after the upgrade and needs re-picking, although DeepSeek still routes the old name for now; a catalog alias the resolver honours is not built.\n- **The Moonshot, Vercel and OpenRouter harness doors are declared from vendor documentation** and were not live-probed from this platform; a Claude Code automation on one of them exercises the door for the first time.\n- **Three follow-ups from the lane fix are open**: the Read hook's PDF guard arms only for text-only models (`deepseek-flash` has native vision, so a native PDF read reaches the wire and the vendor's Anthropic door drops the document silently — the model is told it was unsupported); a resumed retry that fails with the same invalid-request 4xx replays the rejected transcript instead of re-kicking a fresh conversation; and Z.ai's Anthropic door stays unused until a harness endpoint can declare that it drops images.\n- **A shipped provider whose catalog carries no curated embedding entry** (DeepSeek, Anthropic, xAI, Moonshot, Qwen, Gemini, Nous) is refused as an embedding provider, including the ones whose vendor does serve embeddings — the remedy is curating the entry, width included, in a release, not a typed tag. OpenAI offers `text-embedding-3-small` only. Azure keeps the free field although it is shipped: its deployments carry the admin's own names and there is no listing to consult.\n- **The custom-provider form authors api-key and env credentials, models-endpoint or no catalog, and the two API formats**; a subscription auth entry or an `embedding` declaration on an existing definition is preserved on edit, never authored — that stays the file lane. A definition that survives a failed retirement (the credential is gone either way) stays visible in the picker until its next credential is deleted.\n- **A bare listing's assumptions are assumptions**: 128,000 tokens of context and tool support. A server that refuses tools fails loudly on the wire, never silently; a server that serves less context than assumed is corrected by a lower context limit in governance.\n- **`tale-vision --thinking disabled`** is validated against a controlled upstream and the runtime's own tests; whether a given vendor honours the disabled mode, and what it does to transcription quality and latency, remain deployment checks. Nothing in the platform passes the flag.\n- **The chat scroll rounds `CHAT-F39` and `CHAT-F40` are browser-tested for the wheel and the follow latch**; trackpad momentum and touch gestures on a real device stay manual. The deferred-send path (attachments still processing) sets no scroll intent, by design.\n- **The `bun dev` runtime-image step** wraps the same recipe `docker:dev` uses; its own rendering during a missing-image boot was not observed live, because a second orchestrator cannot share the running fleet's ports.\n- Unchanged from v0.5.39, where each is described in full: a knowledge entry the REST door wrote before that release keeps `source: \"manual\"`; a scan reuses a robots verdict up to a minute old and a row stored earlier carries no sitemap list; the ask retraction is best-effort and forward-only; the app's own archive stays unfenced; `GET /api/v1/teams` is read-only and a complete set; the chat `content` cap counts UTF-16 code units; the `nullable` on a `oneOf` branch is the OAS 3.0 spelling; 0.5.38 shipped with generated notes and its four pull requests (#3424–#3427) have no known-issues record.\n- Unchanged from v0.5.37, where each is described in full: ledger rows booked before that release keep the subject they were booked under and a project agent can appear twice in **Top assistants** across the upgrade; `app.usage_events` is write-retired, not dropped; nothing in the schema forbids a door string in `usage_ledger.user_id`; the run list labels a keyed start `Started by api-key:…`; the `GOV-F20` round is manual; `llm` nodes are unmetered and a run carries no usage or cost.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; 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`; 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; a run carries no usage or cost.\n\n## Migration notes\n\n- **One migration, 0112** (`0112_user_preferences_chat_model_provider.sql`): `ALTER TABLE app.user_preferences ADD COLUMN IF NOT EXISTS chat_model_provider_slug text`, plus a column comment. Nullable, no default, no backfill, no index, no constraint — a metadata-only change that takes the table lock for an instant. The file is safe to re-run. It is rolling-deploy safe in both directions: a pick saved before the column exists keeps resolving by id alone, exactly as before, and the previous image ignores the column. The application database moves from **0111 to 0112**; the knowledge database is unchanged, and Better Auth adds no column.\n- **Migrations are tracked by file name.** 0.5.38 shipped a second file with the `0108` prefix (`0108_approvals_one_pending_conversation_draft.sql`, one partial unique index); the boot applies every `.sql` file in name order once and records each by name, so a deployment crossing 0.5.38 runs that file at boot beside this release's 0112.\n- **No environment variable is added or removed**; `.env.example` is unchanged. No scheduled job is added or retired. **One new audit action**, `provider_definition.deleted` (category `security`); two app-door error codes, `PROVIDER_IN_USE` (409) and `PROVIDER_NOT_FOUND` (404 on the new delete door); no machine-contract code changes.\n- **The shipped catalogs change with this release**: nine model catalogs (cache prices, the DeepSeek and OpenRouter entries, the base prices listed above) and six provider definitions (the three new harness doors; comments on DeepSeek, Z.ai and Qwen). They are read-only image inputs, replaced on upgrade; **Refresh catalogs** skips them. An organization's own provider files are untouched, though an admin can now author, edit and delete them from the app; the app's deletions archive under `TALE_CONFIG_DIR/<org>/providers/.history/<name>/` like every save.\n- **The sandbox gateway's per-model pricing is re-pushed** on the next session that names a model whose stored patch lacks a cache rate the catalog now has; the shipped gateway image (`sandbox-llm-gateway`) itself is unchanged and already accepts the cache fields. A Claude Code automation on DeepSeek, Moonshot, the Vercel gateway or OpenRouter provisions the connector's `…__anthropic` record and its pricing on its first turn after the upgrade.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and the managed proxy _policy_ the CLI renders is unchanged. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the settings dialog, the composer and transcript, the billing, both agent hosts, the routing, the catalogs), the **docs** image (six edited pages, each in English, German and French; no new page), the **ui-docs** image (the `Select` sentence and the changed `@tale/ui` component) and the **sandbox-runtime** image (`tale-vision`) carry source changes. The `web`, `db`, `proxy`, `sandbox`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change. A deploy pulls the new runtime image and retags it as the spawner's `tale-sandbox-runtime:latest`; a session container that is already running keeps the image it was started from until the spawner recreates it.\n- **The CLI has no change of its own in this range**, but the reference tree it embeds — the platform's core and shared modules, where the cost formula, the catalog normalizer, the model choice and the shared provider schema live — does, so the release executables are rebuilt and differ from 0.5.39; they report 0.5.41. The shared provider schema's objects are strict, so a CLI older than this release refuses an organization catalog entry that names the new `cacheReadCentsPerMillion` or `cacheWriteCentsPerMillion` keys; nothing the app writes today does. A managed deployment should move its pinned CLI reference together with its platform reference, as always.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.41` and `marketing-ui-v0.5.41` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.41\"`. `@tale/ui` changes in this range (`Select.errorMessage`), so `ui-v0.5.41` differs from `ui-v0.5.40` and `ui-v0.5.39`, which are content-identical to each other; `@tale/marketing-ui` does not change, so `marketing-ui-v0.5.41` is content-identical to its predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.39; there is no 0.5.40 deployment to be on):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Migration 0112 is applied at boot. Nothing in this release needs `--stop`. A deployment crossing 0.5.39 runs migration 0111 at boot as well; one crossing 0.5.38 runs that release's `0108_approvals_one_pending_conversation_draft.sql`, one crossing 0.5.37 runs migration 0110, one crossing 0.5.36 runs migration 0109, one crossing 0.5.35 runs migration 0108 (`0108_trusted_header_keys.sql`) and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Before you upgrade, check what depends on the catalogs and the spend figures.** Anything pinned to `deepseek-v4-flash` — a chat pick, an agent's `supportedModels`, a governance default, an automation `llm` node, a REST caller — must be re-pointed at `deepseek-flash` (or `deepseek/deepseek-v4.1-flash` on OpenRouter); it answers `CHAT_MODEL_UNKNOWN` after the deploy. A budget rule that was tuned to the old, overstated figures books less from now on; a report that reads `usage_ledger` cost figures sees two pricing rules across the upgrade. A Claude Code automation on DeepSeek, Moonshot, the Vercel gateway or OpenRouter rides a different gateway record after the deploy. A custom provider whose `/models` refused an anonymous listing lists correctly once the organization holds a default key for it.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- fix(platform): list DeepSeek V4.1 Flash as deepseek-flash by @larryro in https://github.com/tale-project/tale/pull/3429\n- fix(platform): build the sandbox runtime image in bun dev when missing by @larryro in https://github.com/tale-project/tale/pull/3430\n- feat(platform): define custom AI providers from the settings UI by @larryro in https://github.com/tale-project/tale/pull/3431\n- fix(platform): ride the Anthropic harness lane in workflow agent turns by @larryro in https://github.com/tale-project/tale/pull/3432\n- fix(platform): bill cache hits at vendor rates and stop retrying 402 by @larryro in https://github.com/tale-project/tale/pull/3433\n- feat(sandbox): add per-request vision thinking control by @yannickmonney in https://github.com/tale-project/tale/pull/3434\n- feat(platform): pick the embedding model from the provider's catalog by @larryro in https://github.com/tale-project/tale/pull/3435\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.39...v0.5.41","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.41","publishedAt":"2026-09-20T15:15:58Z"},{"tag":"v0.5.39","version":"0.5.39","name":null,"body":"**0.5.39 carries 1 merged pull request.** It closes every finding of the 2026-09-19 external evaluation of the machine surface (the eleventh round, run against 0.5.36 and contract 1.17.0), the first round that found nothing above the third severity: seven S3 and twenty-seven S4, each re-confirmed in code before it was touched — one, a pagination marker the evaluator read without resolving a reference, needed nothing. The largest fixes are the ones a client feels: an organization's teams can be listed over REST at last, so an integration can set a project's audience without a person copying an id out of the app; archiving or deleting a chat can no longer race a message that was accepted a few milliseconds earlier; the OpenAPI document no longer carries keywords beside a `$ref`, which every generator silently dropped and typed three documented `null`s as non-null; the audience and agent doors follow the no-op rule every other write already follows; the MCP endpoint answers the citation fields, the exact-integer refusal and the run history that REST already answered; a knowledge entry written over REST says so; a question a cancelled or expired run stopped taking is retracted on the task's timeline; and the crawler refuses a bare IP address and reads a site's `robots.txt` once, not twice, when a site is registered. The contract moves from **1.18.0 to 1.19.0** with one new read, one new schema and no new error code. One migration (0111) widens a check constraint, no environment variable changes, and no image in the stop-gated tier changes, so the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Teams are readable over REST (#3428)\n\nA project's `teamIds`, a Hub document's `teamIds` and a skill's `teams` take team ids, and nothing on the machine surface answered one: `GET /api/v1/me` carries no teams, no list existed, and the reference never mentioned `teamIds`, so a caller could set an audience only with an id a person had copied out of the app. **`GET /api/v1/teams`** answers every team of the organization — `id`, `name` and `member`, whether the key holder belongs to it — by name, as a complete set (`x-tale-pagination: none`), for any key holder: a team's name is what every audience badge shows, so the list is the app's own directory read, not the holder's memberships. `member` is the pre-flight for `TEAM_ACCESS_DENIED`: an organization admin may assign any team, another role only the teams it is a member of. The surface stays read-only by design — teams are created, renamed and staffed in the app (**Settings > Teams**) or by an identity provider, and no operation on this surface writes one. The reference gains a **Teams** row and a paragraph under _Find or create the project_ that walks the audience end to end.\n\n### Archiving or deleting a chat can no longer race a send (#3428)\n\nThe REST archive and delete doors checked for a running turn and then wrote; a message accepted in the ten to thirty milliseconds between the check and the write landed on a thread that was gone — the turn's job opened on a trashed thread and vanished, and a client polling the send saw 404 forever — or on an archived one, where the worker settled it as cancelled. The fence is now the write's own predicate on the thread row: the archive and the trash `UPDATE` carry `status = 'active'`, no queued send (`generation_queued_since_ms IS NULL`), no generating turn (`generation_status IS DISTINCT FROM 'generating'`) and no open generation, on the columns the send's claim and the worker's open write in their own transactions, so the row lock serialises the two and a send that committed first is seen. A door whose write was held back re-reads the row in a fresh statement and answers what it finds: 409 `CHAT_TURN_IN_PROGRESS` when a turn claimed it, and the idempotent answer when another writer already archived or trashed the thread. The send's claim gained the mirror-image predicate — it lands only on the caller's active, un-archived thread — and, when it loses, answers 404 `THREAD_NOT_FOUND` or 409 `CHAT_THREAD_ARCHIVED` from the same fresh read instead of opening a turn on a thread that is gone. The app's own archive keeps its unfenced behaviour (a turn that lands in an archived thread is settled by the worker); the app's trash answers the same `ok: false` it always did, now from the predicate rather than a separate read.\n\n### The OpenAPI document is generator-clean again (#3428)\n\nThe three read doors 1.16.0 added — `GET …/runs/{runId}/ask` in both scopes and `GET …/tasks/{taskId}/review` — declared their idle `null` as `{\"$ref\": …, \"nullable\": true}`, and `PendingAsk.questions` carried a `description` beside its `$ref`. A Reference Object cannot be extended (OAS 3.0.3 §4.7.23): every reader ignores the sibling, so every generated client typed a documented `null` as non-null. Those were the only four `$ref`-with-sibling sites in the document; the builder's `nullable()` now wraps a reference in `allOf` and carries `nullable` beside it, and a typeless `oneOf` (`ContactInput` and `ContactPatch.externalId`) carries `nullable` on each branch, where a `nullable` on the bare `oneOf` compiles nowhere. Three guards in the spec test hold the document to it: no Reference Object has a sibling keyword, every `nullable` sits beside a `type` or an `allOf`-wrapped reference, and the ask and review doors declare their idle answer as a nullable reference.\n\n### The audience and agent doors follow the no-op rule (#3428)\n\nRe-asserting the audience a project already carries bumped `updatedAt` on every pass — a sync that re-asserts on every run moved the stamp every time and invalidated every other client's `expectedUpdatedAt` — where a name no-op did not; a repeated team id was a 400 on projects while documents and skills collapsed it silently; an archived project, frozen to every other write, still took a `teamIds` change; and a byte-identical agent `PUT` bumped `updatedAt`, so two declarative writers re-asserting one configuration 409'd each other with `PROJECT_AGENT_STALE`. Each now follows the rule the document doors set: the audience the project already carries is a no-op (no write, no audit row, `updatedAt` kept — order counts, because `teamIds[0]` is the mirrored owning team); a repeated id collapses to one, first-seen order kept, and the stored list is what the response echoes, while a blank entry is still `PROJECT_SHARING_INVALID`; `PATCH {teamIds}` on an archived project is 403 `PROJECT_ARCHIVED` unless the same body restores it — the gate now sits above the sharing call, so nothing is written first; and an agent `PUT` whose body names the configuration already stored writes nothing. The 400 text on both project doors now names `INVALID_BODY` for a `teamIds` past its `maxItems`, the code the wire always answered.\n\n### The MCP endpoint answers what REST answers (#3428)\n\nFour gaps between the two doors are closed. `get_knowledge` passages carry `documentId`, `corpus`, `chunkIndex` and `projectId` — the citation the REST search answers, so an MCP client follows a hit to its document without a second search over REST. A JSON-RPC body carrying a whole number beyond ±(2^53 − 1) is refused as an invalid request (`-32600`) naming the literal's path, the way the REST door names it under `INVALID_BODY`, instead of being rounded and echoed — a client keying its correlation table on 64-bit ids never matched the reply; the two doors share one parser now, `lib/utils/json-exact.ts`. `list_runs {name}` answers a deleted automation's kept runs, and so does REST `GET …/automations/{name}/runs` (the project twin included), where both used to say the automation never existed — a run's history outlives its automation by design, and only a name neither an automation nor a run ever bore is `AUTOMATION_NOT_FOUND`. `get_catalog` narrowed to a core node kind (`transform`, `llm`, `agent`, `subautomation`) answers the hint `search_catalog` already gave — get_docs describes it — instead of an empty list, and `start_run` on a version that is not the deployed one names `run_automation {mode: \"mock\"}` rather than a \"mode\" the tool does not have. The reference now says the `Idempotency-Key` HTTP header is refused on the endpoint (400 `INVALID_HEADER`, the wire's long-standing answer), not \"not read\": a batch carries up to 20 calls, so the key rides in the tool arguments.\n\n### A knowledge entry says where it came from (#3428)\n\n`app.knowledge_entries.source` names the lane a fact arrived through — `chat` for the assistant's capture, `manual` for the form — and the REST door stamped `manual` too, so an operator reading the table's **Source** column could not tell an integration's import from a hand-typed fact. **Migration 0111** widens the check constraint to admit `api`; a create or a supersede over `POST`/`PATCH /api/v1/knowledge-entries` writes it, `KnowledgeEntry.source` is the enum `chat | manual | api`, and the table and the entry dialog show an **API** badge in English, German and French. Rows the door wrote before this release keep `manual`, honestly the value they carried. Beside it, a `PATCH` of a superseded row used to name its direct successor, which may itself be superseded, sending a client down the chain one 409 at a time; the 409 now names the topic's active row in `data.activeId` (and the successor in `data.supersededBy`), so a stale id costs one round trip. The `fusedScore` description says what the divisor actually is — the best score possible given the legs that contributed candidates to this response — and products are documented as what they are: deleted outright, no trash and no restore, the name and `externalId` free for a new product at once.\n\n### A question that stopped taking an answer is retracted (#3428)\n\nAn `ask_human` node posts a \"Question for you\" card on the task timeline; when the run was cancelled or the question expired, the card stayed the newest comment, inviting a person to answer a question the door then refused with `HUMAN_ASK_NOT_PENDING`. The closure now posts a retraction as the same trusted workflow actor, in the same automated voice, on the same task — \"the question above no longer takes an answer\", with the reason: the run was cancelled, the run ended, or the question expired before it was answered. It is best-effort like the card itself; a task retired in the meantime has no timeline to retract on. On the answering door, a blank `answer` — whitespace or invisible format characters only — is now the documented 400 `EMPTY_ANSWER`: the code has been in the registry since 1.16.0, but the schema's own minimum-length check spoke first, so no client ever saw it.\n\n### The crawler registers a site politely, and only by hostname (#3428)\n\nA bare public IP address registered as a website (201) and then scanned straight into a TLS error: the crawler dials by host name and verifies the certificate against it, which no IP literal can present. It is now 400 `WEBSITE_DOMAIN_INVALID`, judged after the host policy so a loopback, private or metadata address keeps its clearer `WEBSITE_DOMAIN_NOT_CRAWLABLE`. Registration itself was impolite: the homepage probe and the first scan started together and read `robots.txt` twice within milliseconds, and the probe's homepage read went out unpaced. The probe now runs before the first scan is queued, persists its verdict on the corpus row — the sitemaps it advertised included, and a 4xx, which is an answer (the site publishes no rules) — and the scan reuses a verdict younger than a minute instead of dialing `/robots.txt` again (RFC 9309 lets a crawler cache it for a day); the homepage read waits the site's `Crawl-delay` after the robots read, and every discovery request — each sitemap and each link-walk read — is paced from the previous request the way a content fetch is, where the first sitemap and the first homepage read used to go out unpaced.\n\n## Behaviour changes\n\n- **`PATCH …/threads/{id}` with `archived: true` and `DELETE …/threads/{id}` refuse from the row lock**: a turn that claimed the thread between the door's read and its write is 409 `CHAT_TURN_IN_PROGRESS`; a thread another writer trashed meanwhile is the same 404 or idempotent answer the read would have given. A send that loses the same race answers 404 `THREAD_NOT_FOUND` or 409 `CHAT_THREAD_ARCHIVED` instead of 202.\n- **`GET /api/v1/teams`** is new: `{teams: [{id, name, member}]}`, a complete set, any key holder, `ETag` and 304 like every read.\n- **`POST …/asks/{askId}`** answers 400 `EMPTY_ANSWER` for a blank answer (was `INVALID_BODY`).\n- **`GET …/automations/{name}/runs`** and the project twin answer a deleted automation's kept runs (was 404); MCP `list_runs {name}` the same.\n- **`POST /api/v1/projects` and `PATCH /api/v1/projects/{id}`**: a repeated team id collapses to one (was 400 `PROJECT_SHARING_INVALID`); the audience the project already carries is a no-op that keeps `updatedAt` and writes no audit row; `PATCH {teamIds}` on an archived project is 403 `PROJECT_ARCHIVED` unless the same body restores it (was 200 with a real change).\n- **`PUT …/projects/{id}/agents/{agentId}`** with the stored configuration writes nothing and keeps `updatedAt`.\n- **`POST /api/v1/websites`** refuses a bare IP address as `domain` with 400 `WEBSITE_DOMAIN_INVALID` (was 201 and a `tls_error` scan).\n- **`POST` and `PATCH /api/v1/knowledge-entries`** write `source: \"api\"`; the superseded 409 carries `data.activeId` and `data.supersededBy`, and its message names the active row. The **Knowledge entries** table and dialog show **API** beside **Manual** and **Chat**.\n- **`PATCH /api/v1/knowledge-entries/{id}`** on a topic with no active row left says so (\"create the topic again\") instead of pointing at a superseded successor.\n- **MCP**: an inexact integer anywhere in the body is `-32600` naming its path; `get_knowledge` passages carry `documentId`, `corpus`, `chunkIndex` and `projectId`; `get_catalog {kind: <core kind>}` answers `{node_types: [], hint}`; `start_run` on an undeployed version names `run_automation {mode: \"mock\"}`.\n- **A cancelled, ended or expired ask** posts a retraction comment on its task's timeline as the workflow actor.\n- **Website registration** probes `robots.txt` and the homepage before the first scan is queued, persists the robots verdict with its sitemaps, and paces the homepage read by the site's `Crawl-delay`; a scan reuses a verdict younger than 60 seconds; sitemap and link-walk reads are paced from the previous request.\n- **The web tier's JSON 404** for `/api/*` and `/.well-known/*` paths the API does not serve carries `X-Request-Id` (a caller's own when it is letters, digits, `_` or `-` up to 255 characters, else a fresh UUID) and `Cache-Control: no-store`; it carries no `X-Tale-Api-Version`, because that tier answers no version of the contract.\n- **`HEAD` on `/llms.txt`, `/llms-full.txt`, `/robots.txt` and the app shell** carries the `GET`'s `Content-Length` (was `0`).\n- **A non-ASCII `X-Organization-Slug`** that names no organization is echoed back as the UTF-8 the caller sent (`tälé`, not `tã¤lã©`), still capped and still without a lookup.\n- **`DELETE /api/v1/browser-sessions/import`** is 405 with `Allow: POST, OPTIONS` (it used to reach the pool gate as a delete of a session called `import`, and `OPTIONS` advertised `DELETE`).\n- **The chat send's `content` cap message** names its unit — 100,000 UTF-16 code units — where \"characters\" misstated it for an emoji.\n- **Documentation** (English, German and French): the Teams row and the audience walkthrough, the 403 bullet's `teamIds`, run history by name after an automation delete, a project delete taking its run history (unlike an automation delete), a mirrored task starting in `backlog`, `?version=latest`, the guarded skill delete's 404, `source: \"api\"` and `data.activeId`, products without a trash, the onboarding tables' `404 ORG_SLUG_INVALID` row, the deploy gate (a version with a failing test cannot be deployed; one with none can), the MCP header refusal, passage fields, `list_runs` exception and `get_catalog` hint, the knowledge-entries source badges, and the crawler's hostname rule.\n\n## API contract changes\n\n- **1.18.0 → 1.19.0.** 85 → 86 paths, 133 → 134 operations, 62 → 63 schemas; `Error.code` stays at 167 values — no code is added, removed or moved. `X-Tale-Api-Version` answers `1.19.0`. For the record, 0.5.38 moved the contract from 1.17.0 to 1.18.0 (task discussion reads and comment writes carry optional `bodyByLocale` snapshots), which its generated notes did not say.\n- **`GET /api/v1/teams`** (new) → `{teams: [{id, name, member}]}`, family `none` (a complete set, no cursor), tag **Organization**, any key holder; the `Team` schema is new.\n- **`GET …/runs/{runId}/ask`** (both scopes) and **`GET …/tasks/{taskId}/review`**: the nullable member is `{allOf: [{$ref}], nullable: true}`; `PendingAsk.questions` is `{allOf: [{$ref: QuestionSet}], description}`; `ContactInput` and `ContactPatch.externalId` carry `nullable` on each `oneOf` branch. A generated client that typed these `null`s as non-null gets the right type on regeneration; nothing on the wire changes.\n- **`POST …/asks/{askId}`**: a blank `answer` is 400 `EMPTY_ANSWER` (was `INVALID_BODY`).\n- **`KnowledgeEntry.source`** is the enum `chat | manual | api`; the door writes `api`. **`PATCH /api/v1/knowledge-entries/{id}`** on a superseded row: `data.activeId` (when the topic still has an active row) and `data.supersededBy`.\n- **`POST` / `PATCH /api/v1/projects`**: a repeated team id collapses (was 400 `PROJECT_SHARING_INVALID`); the 400 text names `INVALID_BODY` for a `teamIds` past its `maxItems`; a no-op `teamIds` keeps `updatedAt`; `PATCH {teamIds}` on an archived project is 403 `PROJECT_ARCHIVED` (was 200). The `teamIds` descriptions point at `GET /api/v1/teams`.\n- **`PUT …/agents/{agentId}`**: an identical body writes nothing (`updatedAt` kept).\n- **`GET …/automations/{name}/runs`** (both scopes): answers a deleted automation's kept runs; its 404 is \"a name no saved automation and no run of the organization bears\". `DELETE /api/v1/automations/{name}` says so.\n- **`POST /api/v1/websites`**: a bare IP `domain` is 400 `WEBSITE_DOMAIN_INVALID` (was 201); `WebsiteInput.domain` says so.\n- **`DELETE /api/v1/products/{id}`** is documented as permanent — no trash, no restore, the name and `externalId` free at once; `GET` of a deleted product is 404 `PRODUCT_NOT_FOUND` (the wire is unchanged; the text said \"trash\").\n- **`KnowledgeHit.fusedScore`**: the description names the divisor — the best score possible given the legs that contributed candidates to this response (`diagnostics.legs`).\n- **MCP** (not in the OpenAPI document): an inexact integer anywhere in the body is `-32600`; `get_knowledge` passages gain `documentId`, `corpus`, `chunkIndex`, `projectId`; `get_catalog` core kinds and `start_run` on an undeployed version carry hints; `list_runs {name}` answers a deleted automation's runs.\n- `generate:openapi` on the release commit reproduces the shipped document; the contract fingerprint moved with the version.\n\n## Security\n\n- **The chat archive and delete fences are the row's own predicate (#3428).** The old check-then-act left a ten-to-thirty-millisecond window in which a send accepted by one request landed on a thread another request was deleting; the accepted turn then ran on a trashed thread — a billed turn nobody could read — and the poll answered 404 forever. The refusal is evaluated under the row lock now, on both sides of the race, so no turn opens on a thread that is gone or archived.\n- **An inexact integer no longer rounds silently on the MCP door (#3428).** A JSON-RPC `id` above 2^53 − 1 was echoed as its neighbour; a body value was stored altered. Both doors refuse the literal by path now. This is a tightening: an MCP client that sent 64-bit ids as numbers is refused where it was rounded before — send them as strings.\n- **The web tier's JSON 404 echoes a request id only in the API's alphabet (#3428).** A caller-supplied `X-Request-Id` is echoed when it is up to 255 letters, digits, `_` or `-`, and replaced by a fresh UUID otherwise — the same rule the API applies — so the header never reflects arbitrary bytes.\n- **The organization-slug 404 re-decodes before it echoes (#3428).** The message quotes the header as the UTF-8 the caller sent; it is still capped at the slug length limit, answered without a lookup, and only for a value that cannot be a slug at all.\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **History is not rewritten.** A knowledge entry the REST door wrote before this release keeps `source: \"manual\"`; nothing backfills `api`, because the row cannot tell which lane wrote it.\n- **The robots verdict a scan reuses is up to a minute old** by design; a site that changes its `robots.txt` within a minute of being registered is scanned under the earlier verdict once, and the next scan reads it again. A row stored before this release carries no sitemap list, so its next scan reads `robots.txt` once more and stores it.\n- **The ask retraction is best-effort and forward-only.** A card whose task was retired in the meantime has no timeline to retract on, and nothing retracts a \"Question for you\" card posted before this release.\n- **The app's own archive stays unfenced** by design: a turn that lands in a thread the app archived is settled by the worker. Only the REST door refuses.\n- **`GET /api/v1/teams` is read-only by design.** No operation on the machine surface creates, renames, staffs or deletes a team; that is the app or the identity provider.\n- **The `Team` list is a complete set** (family `none`), like the other unbounded lists recorded as contract debt; teams are few.\n- **The chat `content` cap counts UTF-16 code units**, so an emoji counts two; the message now says so, the limit itself is unchanged.\n- **The `nullable` on a `oneOf` branch** is the OAS 3.0 spelling; a 3.1-only reader still needs the document's declared version to read it.\n- 0.5.38 shipped with generated notes, so its four pull requests (#3424–#3427) have no known-issues record to carry here.\n- Unchanged from v0.5.37, where each is described in full: ledger rows booked before that release keep the subject they were booked under and a project agent can appear twice in **Top assistants** across the upgrade; `app.usage_events` is write-retired, not dropped; nothing in the schema forbids a door string in `usage_ledger.user_id`; the run list labels a keyed start `Started by api-key:…`; the `GOV-F20` round is manual; `llm` nodes are unmetered and a run carries no usage or cost.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; 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`; 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; a run carries no usage or cost.\n\n## Migration notes\n\n- **One migration, 0111** (`0111_knowledge_entries_api_source.sql`): drops the check constraint on `app.knowledge_entries.source` if it exists and re-creates it as `CHECK (source IN ('chat', 'manual', 'api'))`. No column, no index, no backfill, no `updated_at_ms` moves. Adding the constraint validates the existing rows under the table's lock for the moment that takes — the table holds one row per fact version and every row already satisfies the wider check. The file is safe to re-run. It is rolling-deploy safe: the previous image writes only `chat` and `manual`, which the widened constraint still admits. The application database moves from **0110 to 0111**; the knowledge database is unchanged, and Better Auth adds no column.\n- **Migrations are tracked by file name.** 0.5.38 shipped a second file with the `0108` prefix (`0108_approvals_one_pending_conversation_draft.sql`, one partial unique index); the boot applies every `.sql` file in name order once and records each by name, so a deployment crossing 0.5.38 runs that file at boot beside this release's 0111.\n- **No environment variable is added or removed**; `.env.example` is unchanged. No organization configuration file changes; the seed catalog is untouched. No scheduled job is added or retired, and no new audit action or error code appears.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and the managed proxy _policy_ the CLI renders is unchanged. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the doors, the fence, the MCP endpoint, the crawler, the knowledge-entries badge) and the **docs** image (seven edited pages, each in English, German and French; no new page) carry source changes. The `web`, `ui-docs`, `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has no change of its own in this range**, but the reference tree it embeds — the platform's shared modules, where the exact-integer parser, the crawl host policy and the contract version live — does, so the release executables are rebuilt and differ from 0.5.38; they report 0.5.39. Nothing in the range is new for an older CLI to refuse. A managed deployment should move its pinned CLI reference together with its platform reference, as always.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.39` and `marketing-ui-v0.5.39` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.39\"`. Neither package changes in this range, so both tags are content-identical to their 0.5.38 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.38):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Migration 0111 is applied at boot. Nothing in this release needs `--stop`. A deployment crossing 0.5.38 runs that release's `0108_approvals_one_pending_conversation_draft.sql` at boot as well; one crossing 0.5.37 runs migration 0110, one crossing 0.5.36 runs migration 0109, one crossing 0.5.35 runs migration 0108 (`0108_trusted_header_keys.sql`) and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Before you upgrade, check what your integrations assume.** A sync that re-asserts a project's audience on every pass will see `updatedAt` stop moving on a no-op — a client that read the stamp as a heartbeat has to stop. A client that branched on `INVALID_BODY` for a blank ask answer now sees `EMPTY_ANSWER`. A consumer of `GET /api/v1/knowledge-entries` that mapped `source` onto two values has to admit `api`. An MCP client that sent 64-bit ids as numbers is refused now — send them as strings. A caller that relied on `GET …/automations/{name}/runs` answering 404 as \"the automation is gone\" should read `GET /api/v1/automations/{name}` instead, which still answers 404 for a deleted automation.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- fix(platform): close the 2026-09-19 round-K API evaluation findings by @larryro in https://github.com/tale-project/tale/pull/3428\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.38...v0.5.39","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.39","publishedAt":"2026-09-20T03:42:27Z"},{"tag":"v0.5.38","version":"0.5.38","name":null,"body":"## What's Changed\n* fix(platform): scroll the rows, not the page, on every overview list by @yannickmonney in https://github.com/tale-project/tale/pull/3424\n* feat(platform): draft replies on conversations, and add the Jev decisions connector by @Israeltheminer in https://github.com/tale-project/tale/pull/3425\n* fix(platform): stabilize OAuth login handoff by @yannickmonney in https://github.com/tale-project/tale/pull/3426\n* fix(platform): keep task and workflow output language consistent by @yannickmonney in https://github.com/tale-project/tale/pull/3427\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.37...v0.5.38","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.38","publishedAt":"2026-09-20T00:38:01Z"},{"tag":"v0.5.37","version":"0.5.37","name":null,"body":"**0.5.37 carries 1 merged pull request.** Every billable call Tale makes now names the person who asked for the work. The usage ledger's subject is a bare user id — the member who sent the chat message, or the one who started the agent run, whichever door they came through — or one sentinel row, **Automations (triggers)**, for a run a schedule, a webhook or an event started, which nobody is responsible for. The automation lane used to copy the run's _door_ (`user:…`, `api-key:…`, `trigger:…`) into the ledger instead, so **Per-user usage** listed pseudo-users beside real ones, personal and team limits never saw automation spend, and a run started with an API key was billed to no key at all. A keyed run now books to its key as well, so a key's own budget cap sees what the integration behind it costs; a project agent books under its stable id and the page resolves its name when it reads. The rule is written down twice: a new documentation page, **How usage is counted**, for administrators and members in English, German and French, and a contract README for the next lane that spends. One migration (0110) adding two nullable columns, no backfill and no environment change; the API contract is unchanged at 1.17.0, and no image in the stop-gated tier changes, so the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Every billable call is booked under a person (#3423)\n\nThree lanes wrote to the same usage ledger and disagreed about what its `user_id` column meant. Chat and project-agent turns wrote a bare user id. The automation lane copied `automation_runs.started_by` — the **door** that started the run, in the format the REST contract publishes on `startedBy` (`user:<id>`, `api-key:<id>`, `trigger:<triggerId>`) — verbatim into the ledger. The consequences ran through every reader: **Per-user usage** showed rows named `api-key:…` and `user:…`, which resolve to no member and so were labelled with the raw string, and one person's chat and automation spend sat on two separate rows; the budget gate looked a door string up as a member, found none, and measured the turn against the organization's _default_ personal triple in a usage pool of its own, so a member's real personal and team caps never saw a single automation run; erasure deleted the bare-id rows and left the prefixed ones behind; and a run started with an API key recorded nothing against that key, because the run row never knew which key had authenticated it.\n\nThe rule is now one sentence with one owner: every `app.usage_ledger` row names a person by bare user id, or the single sentinel `__automation__` for spend nobody is responsible for, plus the API key when one authenticated the start. `services/platform/backend/domains/governance/README.md` states it as eight numbered rules with the lane table, the reader table and the guards behind each; `.agents/repo.md` carries it as a repository boundary. The door format is untouched — the REST contract, erasure and the trigger fire ledger all read `started_by` — but exactly one module parses it, `lib/shared/run-starter.ts`, and managed agent turns resolve their subject in exactly one place, `resolveSessionOpAttribution`, which both the reservation and the settlement call so a turn is measured and booked against the same subject.\n\n### A run started with an API key books to that key (#3423)\n\n**Settings > Governance** has been able to cap what one API key may spend since budget rules shipped, and the documentation has promised that the runs a key starts count toward it. They did not: a chat message sent with a key was booked to it, but a run start had nowhere to record the key — `app.automation_runs` had no column for it — so a key-driven integration could start automation runs all day against a cap that never moved. **Migration 0110** adds `api_key_id` to `app.automation_runs` and to `app.sandbox_session_ops`; the REST run start, the REST task-workflow starts (including the act-as lane and the agent-mention comment), and the MCP endpoint's run tools hand the authenticating key's id to the run, the reservation stamps it on the op, and the settlement books it beside the person. Both columns are nullable and are set by the keyed doors alone: a start from the run list, the builder, a chat capability or a trigger carries none.\n\nThe member's own limits apply as well. A keyed request is measured against the personal, team and role caps of the member the key acts for — the key holder, or the member named on an act-as request — and against the key's own cap; the strictest of them refuses first, as _How rules combine_ already described for rules that overlap.\n\n### Trigger-started runs share one row, and never look like a member (#3423)\n\nA run a schedule, a webhook or an event started has no person behind it. It books under `__automation__`, and every read side treats that subject as a bucket rather than a user: **Per-user usage** shows it as **Automations (triggers)** (translated into German and French), it is excluded from the **Active users** count, and the budget gate binds only the organization's caps to it — and the key's, on the rare keyed path — because there is no person whose personal, team or role cap could apply. A short note under the table names the rule so an administrator reading the page does not have to infer it, and the member's **Settings > Usage** now says the runs they start count toward their limits whichever way they started them.\n\n### A project agent books under its id, not its display name (#3423)\n\nThe `agent_slug` axis mixed identifiers and labels: a chat assistant booked under its slug and an automation under its name, but a project agent booked under its **display name**, which an administrator can change at any time — so renaming an agent split its history in two and two agents sharing a name merged theirs. A project agent now books under `project_agents.id`, a value that never changes, and the **Top assistants** table resolves ids to names when it reads. The rule generalizes: `agent_slug` is a stable identifier, and a reader that wants a label looks it up.\n\n### How usage is counted, written down (#3423)\n\nA new page, **How usage is counted** (**Governance > How usage is counted**, in English, German and French), answers the question administrators and members actually ask: what counts as usage, who each kind of work counts against, which limits apply to it, and where it shows up in **Usage analytics**. A table walks every lane — a chat reply and the model call that titles a new chat, an agent run on a task, an automation run someone started, an automation run a trigger started, voice output, transcription, a metered connector call — with the person it counts against, the API key it also counts toward when one was used, and the row it appears on. Three situations that confuse people get their own answers: a teammate who mentions your agent in a task comment spends their own allowance, not yours; a nightly scheduled automation lands on the **Automations (triggers)** row and only an organization limit can stop it; and a retry continues the run its starter kicked off. The page is linked from **Usage analytics**, **Policies and limits**, **API keys** and the member's **Preferences**.\n\n## Behaviour changes\n\n- **A member's personal and team budget caps now count the automation runs they start.** Before this release the automation lane's spend was measured against the organization's default personal limits in a pool of its own, so a member with a personal cap could start automation runs past it. An organization that runs automations under personal caps should review those rules before upgrading — see _Upgrading_.\n- **A run a trigger started is no longer measured against a personal limit.** It has no person, so only the organization's caps (and a key's, where one is involved) can refuse it. Set an organization cost or request limit when trigger-started runs need a ceiling.\n- **A run started with an API key counts toward that key's budget cap**, beside the personal, team and role caps of the member it acts for.\n- **Per-user usage** no longer shows `user:…` or `api-key:…` rows for work done from this release on; trigger-started runs sit on one **Automations (triggers)** row, which does not raise **Active users**. Rows booked before the upgrade keep the form they were written in.\n- **Top assistants** shows a project agent under its current name, resolved when the page is read, rather than under whatever name it carried when the spend was booked; renaming an agent no longer splits its history from here on.\n- The member's **Settings > Usage** description now says that the agent runs they start count toward their limits whichever way they started them.\n- **Erasing a member now also removes the ledger rows the automation lane booked under `user:<id>` and `api-key:<id>`** before this release; only the bare-id rows were removed before.\n- **The chat lane stops writing `app.usage_events`**, the per-turn row it kept beside the ledger. Nothing ever read it; erasure and retention still sweep the rows already there.\n- A project-agent or automation run whose starter cannot be parsed falls back to the subject the reservation stamped on the op row, rather than booking the unreadable value.\n\n## API contract changes\n\n- **None.** The OpenAPI document is byte-identical to 0.5.36: **1.17.0**, 85 paths, 133 operations, 62 schemas, 167 `Error.code` values. `X-Tale-Api-Version` still answers `1.17.0`.\n- `startedBy` on a run keeps its door format (`user:<id>`, `api-key:<userId>`, `trigger:<triggerId>`) — the attribution fix happens at the ledger boundary, never by rewriting a door field, so no client that reads `startedBy` has to change.\n- A run still carries no `usage` block; that remains contract debt with its design recorded in the ledger.\n\n## Security\n\n- **Erasure covers the door-form rows it used to miss (#3423).** A data-subject erasure deleted `app.usage_ledger` rows whose `user_id` equalled the subject's id, so the rows the automation lane had booked under `user:<id>` and `api-key:<id>` — the same person's spend, in the door's spelling — survived the erasure. The pass now deletes all three forms, and sweeps the subject's retired `app.usage_events` rows as well.\n- **A key's budget cap now binds what the key starts.** A cap on an API key was enforceable only for chat sends; the runs a key started were invisible to it. This is a tightening: an integration that starts runs with a capped key can now be refused where it was not before.\n- **An engine actor that names nobody is refused at the parse (#3423).** The automation dispatch store split its actor string on the first `:` and treated whatever followed as a user id, so a `trigger:<triggerId>` or an unrecognized door reached the membership lookup as a candidate user. No trigger id is a user id, so nothing is known to have passed — the lookup refused it with `ORG_FORBIDDEN` — but the refusal rested on that coincidence. The store reads the actor through the one starter parser now and answers `UNAUTHENTICATED` before the lookup.\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **History is not rewritten.** Ledger rows booked before this release keep the subject they were booked under: an automation's spend still sits on a `user:…` or `api-key:…` row, and a project agent's on its display name at the time. The decision was deliberate — new data has to be clean, and a rewrite of a period bucket cannot be undone. A usage window that spans the upgrade therefore shows both forms, and a project agent can appear twice in **Top assistants**: once as the old name-keyed bucket and once as the new id-keyed one resolved back to the same name.\n- **`app.usage_events` is write-retired, not dropped.** The previous image still writes it during a rolling upgrade, so the `DROP TABLE` waits for a later release; erasure and retention cover its rows until then.\n- **Nothing in the schema forbids a door string in `usage_ledger.user_id`.** The rule is enforced by the resolver and its tests; a `CHECK` constraint lands `NOT VALID` in a later release, once no image in a roll can still write the old form.\n- **The run list still labels a keyed start `Started by api-key:…`** — the raw door string, unchanged and cosmetic; it names the same door the contract publishes.\n- **The browser round for the usage page (`GOV-F20`) is manual**: the subject per lane, the sentinel, the key, the stamp fallback, the impersonal budget subject and the name resolution are proved by unit tests and by a backend integration probe that settles a trigger-started and a keyed workflow op against real Postgres and reads both ledger rows.\n- **`llm` nodes in an automation are still unmetered** and a run still carries no usage or cost; this release changes who spend is booked under, not which nodes book it.\n- Unchanged from v0.5.36, where each is described in full: automation `files:` mounts and workflow `document.*` steps do not apply the team audience; a single-sign-on sign-in with an empty group list revokes nothing and a SCIM group replace overwrites hand-added members silently; the legacy team mirror columns stay; the three GIN indexes of migration 0109 were built without `CONCURRENTLY`; the team rounds `NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24` and `CONV-F12` are manual; a team skill's `teams` list is validated only when it changes; REST `Document.teamId` stays as the deprecated single-team spelling.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; 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`; 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.\n\n## Migration notes\n\n- **One migration, 0110** (`0110_run_billing_subject.sql`): adds a nullable `api_key_id text` column to `app.automation_runs` and to `app.sandbox_session_ops`. Both are `ADD COLUMN IF NOT EXISTS`, so the file is idempotent; both are columns the previous image neither reads nor writes, so it keeps serving while the migration applies. No backfill, no index, no constraint, and no `updated_at_ms` moves. The application database moves from **0109 to 0110**; the knowledge database is unchanged, and Better Auth adds no column.\n- **Ledger history is not rewritten.** No statement touches `app.usage_ledger`; rows booked before this release keep their subject, and the readers that need to cover both forms (erasure) name them explicitly.\n- **No environment variable is added or removed**; `.env.example` is unchanged. No organization configuration file changes; the seed catalog is untouched. No scheduled job is added or retired, and no new audit action or error code appears.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and the managed proxy _policy_ the CLI renders is unchanged. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the ledger subject, the doors, the budget gate, the usage page) and the **docs** image (the new _How usage is counted_ page plus four edited pages, each in English, German and French) carry source changes. The `web`, `ui-docs`, `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has no change of its own in this range**, but the reference tree it embeds — the platform's shared modules, where the sentinel and the starter parser live — does, so the release executables are rebuilt and differ from 0.5.36; they report 0.5.37. Nothing in the range is new for an older CLI to refuse. A managed deployment should move its pinned CLI reference together with its platform reference, as always.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.37` and `marketing-ui-v0.5.37` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.37\"`. Neither package changes in this range, so both tags are content-identical to their 0.5.36 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.36):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Migration 0110 is applied at boot. Nothing in this release needs `--stop`. A deployment crossing 0.5.36 runs that release's migration 0109 at boot as well; one crossing 0.5.35 runs migration 0108 and Better Auth's session column, and one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **Before you upgrade, review your budget rules if you run automations.** Personal and team caps now count the automation runs a member starts, where before that spend was measured against the organization's default personal limits in a pool of its own. A member whose work is mostly automation runs can reach a personal cap that never used to bind them. Trigger-started runs move the other way: no personal cap binds them any more, so give the organization a cost or request limit if they need a ceiling. **Settings > Governance > Policies and limits** holds both, and _How usage is counted_ explains which rule applies to which work.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- feat(platform): book agent spend under the person who started the run by @larryro in https://github.com/tale-project/tale/pull/3423\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.36...v0.5.37","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.37","publishedAt":"2026-09-19T13:44:17Z"},{"tag":"v0.5.36","version":"0.5.36","name":null,"body":"**0.5.36 carries 1 merged pull request.** A team becomes what it always claimed to be: a label on work that says who may see it, and a queue a conversation can wait in — never a workspace to switch into. A document, folder or project carries the teams that may see it; empty means the whole organization, and Owners and Admins see everything. One rule states this once and every door reads it, including the doors that ignored teams until now — WebDAV, folder creation, the cloud imports, the document retag lane. The **Team** switcher in the account menu is gone; in its place a read-only **Teams** row opens **Settings > Account > Your teams**, and the document, project and inbox lists gain **Teams** and queue filters that live in the page address. Deleting a team happens in one transaction with a preview of what it touches, a team keeps at least one member, and a team an identity provider provisions shows **Synced** and is not edited by hand. Governance rules for a member of several teams combine one way — a limit to the strictest, a permission list as a union with a block winning, a single choice by list order — and the chat budget banner reads the same standing the gate enforces. REST carries the audience as `teamIds` on projects and documents (API contract 1.17.0). One migration (0109); no image in the stop-gated tier changes, so the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### A team is an audience, not a workspace (#3422)\n\nA member of two teams could \"switch team\" in the account menu and nothing changed: the row was a client-side filter dressed as a context switch — it redirected to chat, hid nothing organization-wide and showed the chosen team nowhere — while the backend used \"team\" for six different jobs whose rules disagreed with each other. A document carrying a `team_id` beside an empty tag list read as organization-wide on one door and as team-only on the next.\n\nThe model is now one sentence, stated once in `core/lib/audience.ts` and embedded by every list door: a document, folder or project carries `teamIds`, the teams that may see it; empty means every member of the organization; Owners and Admins see everything regardless. A team folder owns the audience of everything inside it — a document filed there takes the folder's teams and cannot name a team outside them (`TEAM_INHERITED_FROM_FOLDER`). Naming a team is bounded too: an id that is not one of the organization's teams is refused (`TEAM_NOT_IN_ORG`), and a member who is not an Owner or Admin can only restrict work to teams they belong to (`TEAM_ACCESS_DENIED`). Projects fold their owning team and shared teams into one `team_ids` set; a conversation keeps a team as its queue. Knowledge retrieval carries the same rule, so an Owner's or Admin's search reaches every team library, and the `org_<id>` pseudo-team that stood in for \"organization-wide\" is gone.\n\nThe switcher and its provider are deleted. The account menu's **Teams** row names the teams you are in (two names, then `+n`; **No teams** for an account in none) and opens the account page's new **Your teams** section, which says what teams decide and links Owners and Admins to **Settings > Teams**. Documents and projects gain a **Teams** filter — **Organization-wide**, **My teams**, and every team of the organization by name — kept in the page address as `?teams=…` so a filtered list can be bookmarked; the inbox shows each conversation's queue as a chip and gains **Filter by queue** (`?queue=…`) with **All queues**, **My teams**, **Unassigned** for administrators, and each team by name. A project shows its audience by name in the list's **Sharing** column, is given one under **Who can see it** on create and under **Audience** on **General**, where removing a team asks for confirmation because members outside the remaining teams lose access; clearing every team widens the project to the organization and saves at once. Every surface resolves names through one read, `GET /api/app/teams/directory`, which names every team of the organization to any member — nobody looks at blanks or raw ids for teams they are not in any more.\n\n### Every door reads the one rule (#3422)\n\nSeveral doors ignored teams altogether. **WebDAV** resolved paths and listed collections for the credential's organization without asking which teams the credential's owner was in, so a team library was one `PROPFIND` away for any member holding a WebDAV credential. The handlers now resolve the owner's audience (fail-closed: no membership, no team rows), filter listings in the SQL statement, stamp a `PUT` with the landing folder's audience, and refuse a `DELETE`, `COPY` or `MOVE` whose tree holds a team folder or document the caller cannot see — refused whole with **403** rather than done half-way, so a hidden subtree can never be re-homed under a wider audience; an Admin's copy under an organization-wide destination keeps each row's own audience. **Folder creation**, the **OneDrive and Google Drive import** doors and the document **Assign team** lane accepted any team id, including another organization's; each now validates through the same assignment rule an upload obeys, and a **team skill**'s `teams` list is validated the same way when it changes. Two team-membership lookups — the mention directory's and the team doors' — read memberships without joining the organization's teams, so a membership in another tenant's team could count; both join `team` on the organization now. A cloud-sync refresh used to re-stamp a synced document with the pipeline's single team, lifting a restriction an administrator had set by hand; it now leaves the stored audience alone unless the landing folder owns one, and an import into a team folder takes the folder's whole team list.\n\n### Deleting a team is one transaction, with a preview (#3422)\n\nBetter Auth's team endpoint deleted the team row and its memberships and nothing else; the projects, folders, documents, queues and sync configurations the team scoped have no foreign key to it, so they stayed pointed at a ghost nobody could satisfy until a daily sweep ran — and a door that failed half-way left the same ghost. `DELETE /api/app/teams/:id` now retires every scope, the identity-provider provenance, the memberships and the row in one serializable transaction, audited as `team.deleted` with the counts of what it retired; `GET /api/app/teams/:id/impact` previews what a deletion touches — members, projects, folders, documents, queued conversations — and how many of those items have no other team and become visible to everyone. The confirmation dialog shows those numbers. SCIM's group deletion and the single-sign-on group reaper retire the team's scopes inside the transaction that deletes the team. The daily `teams.repair_scopes` sweep is removed and unscheduled at boot, and **migration 0109** cleans the ghosts one last time. The administrator doors never take a team's last member (**409 `TEAM_LAST_MEMBER`**; delete the team instead), and a team an identity provider provisions shows **Synced** with a read-only edit dialog, because a local change would be undone by the next synchronization — deleting it locally still works.\n\n### How rules combine for a member of several teams (#3422)\n\nEach governance policy combined a member's team rules in its own way; the budget rule took the most permissive team cap, so joining a lenient team raised a member's personal cap. `rule_precedence.ts` now states the rule once and every policy reads it: the most specific scope wins — user, then team, then role, then default — and among a member's team rules a **limit** combines to the strictest value, a **permission list** to the union of allowed models with a block anywhere winning for that model, and a **single choice** such as the default model to the first matching rule in the table's order. The chat budget banner derives its warnings from the same standing buckets the admission gate walks — the personal cap against the reader's own usage, each team's shared cap against that team's aggregate at the team rule's own threshold, the organization's against the organization's — so it can never announce a standing the gate would not enforce, and a team's shared cap reads **Team _name_: … left**. The usage ledger no longer records a team on a row (a team's usage is read through its membership), and the per-user usage table drops its **Team** column. The Policies and limits page documents the combination in a new _How rules combine_ section, in English, German and French.\n\n### Projects and documents carry their audience over REST (#3422)\n\n`Project.teamIds` joins the project payload, always present (empty = organization-wide), and `teamIds` is accepted on `POST /api/v1/projects` (teams of the organization; for a key holder who is not an admin, their own) and on `PATCH /api/v1/projects/{id}`, where it replaces the audience whole and is an organization-admin verb like `archived`. `Document.teamIds` joins the document payload beside the single `teamId`, now marked deprecated as the pre-1.17.0 spelling; `teamIds` is accepted on the document create and patch bodies, `teamId` still is, and inside a team folder the folder's audience applies. Three codes join the machine contract: `TEAM_NOT_IN_ORG` (400, `data.teamIds` names the strangers), `TEAM_INHERITED_FROM_FOLDER` (400) and `PROJECT_SHARING_INVALID` (400: more than the allowed number of teams, a duplicate or blank id, `data.unknownTeamIds`). The OpenAPI document names each on the operations that answer it, and the manual error-code register carries their rows.\n\n## Behaviour changes\n\n- A member of several teams gets the **strictest** personal budget and context cap among their team rules; it was the most permissive. Model access combines as the union with a block winning; the default model follows the table's order.\n- Owners and Admins see every hub document and folder, in the library, over WebDAV and in knowledge retrieval; there was no administrator bypass on the hub before. Nothing changes for other members: a document or folder is visible to the members of any of its teams, or to everyone when it carries none.\n- A document or folder stamped only through the single `team_id` column, with an empty tag list, is team-scoped after migration 0109; it read as organization-wide on one door and as team-only on the next.\n- A member who is not an Owner or Admin can restrict a document, folder, project or team skill only to teams they belong to; an id that is not one of the organization's teams is refused everywhere it can be named.\n- A document inside a team folder cannot name a team outside the folder's audience; moving one in applies the folder's teams, as an upload already did.\n- A WebDAV `DELETE`, `COPY` or `MOVE` whose tree holds a team folder or document the caller cannot see is refused whole with 403; a WebDAV listing shows only what the credential's owner may see.\n- The **Team** switcher in the account menu is gone, and with it the per-browser remembered selection; the **Teams** row is read-only and opens **Settings > Account > Your teams**. Lists narrow by team through **Teams** filters kept in the page address; the inbox filters by queue, and a member sees no **Unassigned** option and no unassigned conversations.\n- Every member can read every team's name through `GET /api/app/teams/directory`; the team list under **Settings > Teams** and a team's members keep their rule.\n- Deleting a team is atomic and previews its impact; the daily ghost-team sweep is gone. A team keeps at least one member: the member-removal doors answer 409 `TEAM_LAST_MEMBER`.\n- A team an identity provider provisions shows **Synced** and opens a read-only edit dialog.\n- Clearing every team on a project saves without a confirmation; removing a team while others remain confirms first.\n- A cloud-sync refresh no longer lifts a team restriction set by hand; an import or refresh into a team folder takes the folder's whole team list.\n- The chat budget banner reads every cap that binds the reader and adds a **Team _name_** line for a team's shared cap; the per-user usage table no longer shows a **Team** column, and the usage ledger stops recording a team.\n- Over REST, `Project.teamIds` is always present and `teamIds` is accepted on project and document writes; `PATCH …/projects/{id}` with `teamIds` needs an organization admin.\n\n## API contract changes\n\n- The OpenAPI document moves from **1.16.0 to 1.17.0**: still **85 paths**, **133 operations** and **62 schemas**; no path or operation is added or removed. Four request bodies change: `POST /api/v1/projects` and `PATCH /api/v1/projects/{id}` accept `teamIds` (at most 21 ids of at most 128 characters each); `POST /api/v1/documents` and `PATCH /api/v1/documents/{id}` accept `teamIds` (at most 64) beside `teamId`. Every operation that answers a `Project` or a `Document` answers `teamIds`.\n- `Project` gains `teamIds` (required, array of strings). `Document` gains `teamIds` (array of strings), and its `teamId` is marked deprecated — the first team of `teamIds`, or null. `DocumentInput` and `DocumentPatch` gain `teamIds`; on the patch it replaces the audience whole and `[]` makes the document organization-wide.\n- The `Error.code` enum grows from **164 to 167** values: `PROJECT_SHARING_INVALID`, `TEAM_INHERITED_FROM_FOLDER`, `TEAM_NOT_IN_ORG`. The first two were app-only codes before and are on the machine contract now; `TEAM_NOT_IN_ORG` is new.\n- `X-Tale-Api-Version` answers `1.17.0`. Every change is additive — new optional request fields, one new response field on two schemas, three new codes — so a client generated from 1.16.0 keeps working; a client that rejects unknown response fields has to regenerate.\n- One new code stays app-only, on `/api/app/teams`: `TEAM_LAST_MEMBER` (409, removing a team's only member). `FOLDER_TEAM_FORBIDDEN` keeps its place on the folder doors.\n\n## Security\n\n- **Several doors enforced the team audience inconsistently or not at all (#3422).** The WebDAV handlers listed and resolved every hub row of the organization for any member holding a WebDAV credential, and a `COPY` or `MOVE` could re-home a hidden team subtree under a wider audience; folder creation, the two cloud-import doors and the document retag lane accepted a team id without asking whether it was the organization's or the caller's. Every one of them now reads `core/lib/audience.ts`: the viewer's audience is resolved fail-closed, list doors filter in the statement, and every write validates its team ids. Two membership lookups that could count a team granted by another tenant now join the organization's teams.\n- **Owners and Admins gain read access to every team library**, in the hub, over WebDAV and in retrieval, matching what they already had for projects. This is a deliberate widening: a team is documented as never being a way to hide work from administrators.\n- **Every team's name is readable by every member** through the directory endpoint — names only, never members — so filters and audience chips can show names. Membership and the **Settings > Teams** list keep their admin-or-own rule.\n- **Ghost team ids are gone.** The scope columns have no foreign key to Better Auth's `team` table; migration 0109 removes every id that is not one of the row's organization's teams from documents, folders, projects, conversation queues and sync configurations, and from here on team deletion is one transaction and every write validates its ids, so the daily repair sweep is retired rather than kept as a safety net.\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **Automation `files:` mounts and workflow `document.*` steps do not apply the team audience yet**; they read documents as they did before this release. A single-sign-on sign-in that arrives with an empty group list still revokes nothing, and a SCIM group replace still overwrites members an administrator added by hand without saying so; both are left for a later release.\n- **The legacy mirror columns stay** — `team_id` on documents and folders, `team_id` and `shared_with_team_ids` on projects, `team_id` on the usage ledger. The first four are still written as derived mirrors so the previous image keeps working mid-roll, and the project readers fall back to the legacy pair while `team_ids` is still empty on a row the previous image wrote; the columns are dropped in a later release once nothing reads them.\n- **The three GIN indexes of migration 0109 are built inside the migration transaction**, without `CONCURRENTLY`; a large tenant holds a share lock on `app.documents`, `app.folders` and `app.projects` for the seconds the build takes.\n- **The browser rounds for the Teams row, the account section, the three filters, the project audience pickers, the synced-team lock and the atomic delete (`NAV-F6`, `SET-F18`, `SET-F19`, `SET-F42`, `KNOW-F20`, `PROJ-F23`, `PROJ-F24`, `CONV-F12`) are manual**; the audience rule, the delete preview and the three deletion lanes, the last-member rule, the assignment and inheritance refusals, the WebDAV refusals and the migration's idempotence are proved against real Postgres in the backend integration check.\n- **A team skill's `teams` list is validated only when it changes**, so an edit that leaves the list alone does not fail on a team the organization has since deleted; the deleted id simply matches nobody.\n- **The REST `Document.teamId` stays as the deprecated single-team spelling**; nothing removes it in this release.\n- Unchanged from v0.5.35, where each is described in full: a frame carries the signed-in session only from a same-site host page and the shell's embedding policy is the union across organizations; revoking a trusted-header key or turning the card off ends no session; the `AUTH-F21`–`AUTH-F24`, `AUTH-B10` and `SET-F41` rounds are manual; approvals have no REST twin; moving a folder has no door and documents already at the root stay there; the auto-retry resumes only a turn that announced its conversation handle; the Google Drive row counts a deployment app from either lane.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; 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`; 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.\n\n## Migration notes\n\n- **One migration, 0109** (`0109_team_audience.sql`): adds `team_ids` to `app.projects` and backfills it from the owning and shared teams; makes the tag array authoritative on `app.documents` and `app.folders` (a row stamped only through `team_id` gets `team_tags = [team_id]`); re-derives the mirror columns from the arrays; removes, once, every team id that is not one of the row's organization's teams from documents, folders, projects, conversation queues and the OneDrive and Google Drive sync configurations (guarded so a fresh database, where the application migrations run before Better Auth creates its tables, skips the sweep); and creates three GIN indexes for the per-team lanes. Every statement is `IF NOT EXISTS` or an `UPDATE` whose `WHERE` matches nothing on a second run, no `updated_at_ms` moves, and the previous image keeps serving while it applies. The application database moves from **0108 to 0109**; the knowledge database is unchanged. Better Auth adds no column this time.\n- **One scheduled job is retired**: `teams.repair_scopes`, the daily ghost-team sweep. The schedule is removed at boot, so a deployment upgrading in place stops running it without an operator step.\n- **No environment variable is added or removed**; `.env.example` is unchanged. No organization configuration file changes; the seed catalog is untouched.\n- **Three error codes join the machine contract** and one app-only code appears; see _API contract changes_. New audit action: `team.deleted`, carrying the counts of what the deletion retired.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and the managed proxy _policy_ the CLI renders is unchanged. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the audience rule, the doors, the atomic delete, the governance precedence, the frontend) and the **docs** image (six pages in each of English, German and French: Teams, Documents, Manage your account and preferences, Project concepts, Policies and limits, Models) carry source changes. The `web`, `ui-docs`, `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has no change of its own in this range**, but the reference tree it embeds — the platform's shared modules and its core, and the shared project schema — does, so the release executables are rebuilt and differ from 0.5.35; they report 0.5.36. Nothing in the range is new for an older CLI to refuse. A managed deployment should move its pinned CLI reference together with its platform reference, as always.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.36` and `marketing-ui-v0.5.36` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.36\"`. Neither package changes in this range, so both tags are content-identical to their 0.5.35 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.35):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Migration 0109 is applied at boot. Nothing in this release needs `--stop`. A deployment crossing 0.5.35 runs that release's migration 0108 and Better Auth's session column at boot as well; one crossing 0.5.33 runs migration 0107. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **After the upgrade, review who sees what.** A document or folder that carried a single team stamp without a tag list is team-scoped now; a member of several teams with budget or context rules is held to the strictest; Owners and Admins can open every team library. Under **Settings > Teams**, a delete previews its impact before anything is removed. Members find their teams under **Settings > Account > Your teams** and narrow lists with the **Teams** filters; nobody has to \"switch team\" any more.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- feat(platform): teams as audiences: one visibility rule, atomic delete, honest UI by @larryro in https://github.com/tale-project/tale/pull/3422\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.35...v0.5.36","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.36","publishedAt":"2026-09-19T06:31:40Z"},{"tag":"v0.5.35","version":"0.5.35","name":null,"body":"**0.5.35 carries 8 merged pull requests.** The trusted-headers door becomes an organization's own feature: an administrator switches it on, caps the role a proxy may assert and mints keys under **Settings > Enterprise SSO**, the door resolves the organization from the key, and a proxied visitor is signed in on the app's first request without ever seeing a sign-in page; the same page gains an **Embedding** card naming the web origins that may show Tale inside a frame. REST gains the doors an external system needs to act _for_ a person — answer a run's pending question, decide a task's review — naming the verified member the gesture is recorded for (API contract 1.16.0). Team and member lists refresh in every open session after a team write or a directory push, a Knowledge import lands in the folder you have open and a file can be moved afterwards, an auto-retried agent step continues its conversation instead of starting over, two settings surfaces stop reporting configuration as missing when it is set, and every data table's two unnamed header cells get their names. One migration (0108) and one column Better Auth adds at boot; no image in the stop-gated tier changes, so the upgrade is `tale update` followed by a plain `tale deploy`. Two environment variables of the retired deployment-wide trusted-headers mode are removed.\n\n## Highlights\n\n### Trusted headers belong to the organization (#3418)\n\n`GET /api/trusted-headers/authenticate` — the door an authenticating reverse proxy hands its users through — used to be a deployment-wide mode: one environment switch, one environment secret, every proxy sign-in landing in the earliest organization that had an owner or administrator, the sign-in page redirecting every visitor, and a rotation meaning a redeploy. None of that serves a deployment that hosts several organizations, where a hosting application must sign its users into _its_ organization and no other.\n\nThe credential is now organization data, in the shape the SCIM token already has. Under **Settings > Enterprise SSO**, the **Trusted headers** card holds the switch, the **Highest role a proxy may assert** and the keys: a key is a random bearer value answered exactly once, only its SHA-256 hash is stored, an organization holds at most 10 live keys, and revocation is a stamp that keeps the row as the audit trail behind every session the key minted. **Migration 0108** creates the two tables.\n\nThe door reads the key from the key header (`Remote-Internal-Secret` by default; an `Authorization` header is ignored, so a REST API key can never be mistaken for it), resolves the organization by the hash — never from a header, a body or a path — refuses a paused organization or a revoked key, charges unknown keys to the source address, and caps `Remote-Role` at the organization's ceiling; **Owner** is never assertable through a proxy. The single-sign-on organization-binding contract holds: a member of that organization signs in, an address the deployment has never seen becomes a new member with the asserted role, and an existing account from another organization is refused before any write. A member's seat follows the asserted role on every sign-in, with the same `update_member_role` audit a manual change writes; an Owner seat never moves, and `GET /api/app/members/me` reports the role the organization gate enforces so the app never shows a stale seat. `Remote-Teams` accepts plain names — `Finance,Operations`, the form most authenticating proxies emit — as well as `id:name` pairs; teams are matched and created by name, where a bare list used to be read as \"no teams\" and revoked synchronized memberships. Every sign-in is audited as `trusted_headers_sign_in` naming the key; every write to the card is audited too. The admin surface is `/api/app/trusted-headers`.\n\nThe deployment-wide switch (`TRUSTED_HEADERS_ENABLED`, `TRUSTED_HEADERS_INTERNAL_SECRET`) and the sign-in page's global redirect are removed without a compatibility shim.\n\n### A proxied visitor is signed in without a sign-in page (#3418)\n\nA GET the app makes for itself — the session probe, a read under `/api/app/*` — that carries the organization's key and the identity header but no session cookie is answered signed in: the backend mints the session on the spot, the cookie rides on the response, and the request goes on as if the browser had sent it. Never on a POST, never for a cross-site fetch, never while a session cookie is present, never while the app's hold cookie stands. The dashboard opens directly.\n\nThe door itself answers a success with a **302** to the in-app return path and the cookie — no page of its own. A refusal of a hand-off the app started goes back to the sign-in page with its reason, rendered there in the app's own words in English, German and French with a recovery hint; a proxy-routed or terminal refusal is answered as a plain page with its status code. The sign-in page still hands off by itself when it lands on a proxied request (a stale cookie, say), shows a refusal instead of retrying, and keeps a one-minute marker so a hand-off that came back without a session shows the form with **Try again** rather than a redirect loop. A public probe, `GET /api/app/sso/discovery/trusted-headers`, tells the page whether the request carries the proxy's headers at all — presence only, uncacheable, no second key oracle.\n\nBecause the proxy owns the session, the account menu offers no **Log out** for a proxied session. After an inactivity sign-out the app sets a short-lived hold cookie (`tale_handoff_hold`, fifteen minutes): the backend does not mint while it stands, and the sign-in page waits behind **Continue with automatic sign-in** so the inactivity notice is seen before the session comes back.\n\n### An organization names who may embed Tale (#3418)\n\nFraming was refused outright — `frame-ancestors 'none'` and `X-Frame-Options: DENY` on every response — so an application that wanted Tale inside its own page had no knob. The new **`embedding` governance policy** (`{ enabled, frameAncestors[] }`) names the web origins allowed as frame ancestors: `https://host[:port]`, lowercase, or plain `http://` on loopback only, at most 16, matched by a character-restricted pattern because the value lands in a response header. It rides the existing policy file lane — `<org>/governance/embedding.yml`, the generic `/api/app/governance/policies/embedding` routes, `tale config apply` — and the seed catalog ships the closed default.\n\nThe web tier scans every organization's policy (cached, like the storage-origin scan) and the shell's Content Security Policy carries `frame-ancestors 'self'` plus the union; `X-Frame-Options`, which cannot express an allowlist, is left off while anything is admitted and returns to `DENY` when nothing is. The canvas preview admits the same list; the trusted-headers door stamps its framing headers per organization once the key has named one; every other backend response keeps the fixed `DENY`. The **Embedding** card on the Enterprise SSO page holds the switch and the origin list in English, German and French with a Swiss German override.\n\n### Act for a member over REST: answer a run's question, decide a task's review (#3419)\n\nA run parked on `waitingFor: \"ask\"` and a task parked in `in_review` both wait on a person. When that person works in another application that mirrors the desk, the machine caller can now relay their gesture and name them as the **`actor`**, so Tale's timeline and audit trail record the person and not the key.\n\n- `GET …/runs/{runId}/ask` answers the live question as `PendingAsk` — the sentence, an optional structured `questions` set (`QuestionSet`: up to four questions of two to four labelled options each, answerable in the person's own words too), the node that asked and the `expiresAt` deadline — or `ask: null`. `POST …/runs/{runId}/asks/{askId}` records the answer, resumes the run in the same transaction and mirrors the answer onto the task timeline as the answerer's own comment. Both exist in the organization scope (`/api/v1/runs/…`) and the project scope (`/api/v1/projects/{id}/runs/…`). Without `actor` the key answers as itself (`answeredBy: \"api-key:<userId>\"`). A closed question answers **409 `HUMAN_ASK_NOT_PENDING`**, an expired one **409 `HUMAN_ASK_EXPIRED`**, one this run did not ask **404 `HUMAN_ASK_NOT_FOUND`**, a blank answer **400 `EMPTY_ANSWER`**.\n- `GET /api/v1/projects/{id}/tasks/{taskId}/review` answers the task's status and its pending `TaskReview`, or `review: null`; `POST` on the same path decides it, and here `actor` is required because a review is always a person's decision. `approve` is the board's move to Done — the member's own project access and the organization's `review_policy` apply exactly as there (**403 `REVIEW_INDEPENDENT_REVIEWER_REQUIRED`** or **`REVIEW_COMPETENCE_REQUIRED`**), and a task with open subtasks answers **409 `TASK_HAS_OPEN_SUBTASKS`**. `request_changes` needs `comment` and `workflowSlug`: it withdraws the review, puts the comment on the timeline and starts the workflow again on the task, which reads the comment as feedback; the answer carries the `runId` to poll. A task not in review answers **409 `TASK_NOT_IN_REVIEW`**. Every decision is audited as `task.review_relayed`, every relayed answer as `automation.ask_answered`, naming the member and the key.\n- `actor.email` is resolved against the organization with the notification export's rule — exactly one active, verified membership: **404 `ACTOR_NOT_FOUND`**, **409 `ACTOR_AMBIGUOUS`**, **403 `ACTOR_UNVERIFIED`**, **403 `ACTOR_DISABLED`**. Every answer returns the resolved `actorUserId`; pinned as `actor.userId`, an address that has since moved to another account answers **409 `ACTOR_REBOUND`** instead of acting as its new holder. A member who may not see the project, or may not write the task, answers **403 `ACTOR_FORBIDDEN`**.\n- Naming an actor is a right of its own: an Owner or Admin key has it by role, any other key holder needs the new **`tale:rest.act-as`** capability, granted and revoked in the competence register like the export capability. `GET /api/v1/me` answers it as `capabilities.actAs`; an `actor` sent without it answers **403 `ROLE_FORBIDDEN`** before any member is looked up. Both writes charge the execute bucket on top of the general REST bucket.\n\nThe API reference documents the two doors in a new _Act for a member_ section in English, German and French.\n\n### Team and member lists stay live in every session (#3414, #3415)\n\nTeam create, rename and delete ride Better Auth's own organization endpoints, so no app write adapter ever saw them: a second tab, and every teammate with the Teams page or the team picker open, kept the stale list until a reload, and the bulk delete refreshed nothing at all. The plugin's lifecycle hooks now emit one `team` invalidation hint per write, after the plugin's own commit and non-fatal by construction, so every connected session refreshes through `/events`; the bulk delete refetches once per batch. The SCIM provisioning lane had the same gap on the other door into the same tables — a directory sync that added a member, renamed a group, moved somebody between teams or de-provisioned an account left every open Members and Teams page showing the pre-sync list. Every SCIM write now emits its hint inside the transaction that performs it (`member` for the user lane, `team` for the group lane, one `team` hint per team a de-provisioning shrank), gated on an actual change so a directory's scheduled full re-push, which moves nothing, hints nothing. Both ends of each hint share one named constant (`TEAM_HINT_ENTITY`, `MEMBER_HINT_ENTITY`) — the drift the shared vocabulary exists to prevent.\n\n### Imports land in the open folder, and a file can be moved (#3420)\n\nImport placement mirrored the provider's own path and nothing else, so files picked at the top of the OneDrive or Google Drive picker went to the Knowledge root however deep in the tree you were standing, and nothing could correct that afterwards. Both import routes take `destinationFolderId` through one shared gate — hub scope only, ordinary folder access, and the destination's team wins over the picker's selection; a path-less pick lands in the destination, a provider subfolder is mirrored underneath it, and the depth cap counts from the destination. The row menu gains **Move to folder…** for a file, listing every hub folder the caller can see by full path plus the root. The document update behind it gains two rules the create path has enforced since the hub shipped: a destination outside the caller's teams is refused with `FOLDER_NOT_ACCESSIBLE`, and a team folder stamps its team on what lands inside it. Files only — moving a folder has no door here — and documents already at the root stay there.\n\n### An auto-retried agent step continues its conversation (#3421)\n\nAn automation `agent` node's in-node auto-retry re-kicked a fresh harness conversation: same prompt, same sandbox workspace, but the model started reasoning from zero and re-read every file the dead turn had already read. An errored settle now keeps the harness's conversation handle, the stepper hands it to the re-kick, and the retry resumes that conversation with a short continuation prompt naming the cut as an infrastructure failure, not the agent's doing. A turn that died before announcing a handle, whose sandbox session is gone or that never launched still starts fresh over the preserved workspace; the retry budget and the fifteen-minute progress refresh are unchanged. The execution-logs page says so in English, German and French.\n\n### Two statuses stop blaming configuration that is set (#3417)\n\nThe OAuth apps card called Google Drive **Not configured** on a deployment whose Drive import worked: Drive has two lanes consenting against one vendor app, and the card read only the connector lane's variables, not the import lane's. The row now consults both and is configured when either answers. The backend's plaintext-secrets warning said `SOPS_AGE_KEY` was not set on a deployment where it was; the branch never consulted the key, and the text hardcoded the message. It now branches on the key's presence and, with a key set, names the real next step: re-save the secrets so the write path encrypts the file. Neither change alters resolution — only the two reports were wrong.\n\n### Two data-table header cells get their names (#3416)\n\nA `<th>` with no discernible text fails WCAG 2.1 AA (axe `empty-table-header`). `DataTable` named its row-action column already; the expander column's header was permanently empty, and the select column's header was empty for the whole loading state, because the skeleton masks the select-all checkbox that carries the name. Both labels now live in the header renderer, the select label emitted only while skeletonizing so the live checkbox keeps owning the name. The expander's cell button, which announced a hardcoded English \"Expand row\" / \"Collapse row\" to every locale, reads the same `aria` keys in English, German and French. `@tale/ui` changes in this range for it.\n\n## Behaviour changes\n\n- `GET /api/trusted-headers/authenticate` resolves the organization from the presented key and refuses a request without one, with an unknown or revoked key, or against an organization whose card is off; it answers a success with a 302 to the in-app return path instead of a page of its own. The deployment-wide mode is gone: `TRUSTED_HEADERS_ENABLED` and `TRUSTED_HEADERS_INTERNAL_SECRET` are no longer read, and the sign-in page no longer redirects every visitor.\n- A cookieless GET the app makes for itself that carries an organization's key and the identity header is answered signed in, the session minted on the spot; a proxied session offers no **Log out**, and an inactivity sign-out pauses the automatic sign-in for fifteen minutes.\n- On every proxied sign-in the member's seat is moved to the asserted role, capped at the organization's ceiling and audited as `update_member_role`; an Owner seat never moves. `Remote-Teams` accepts plain team names; a bare list no longer revokes synchronized memberships.\n- Tale's pages answer `frame-ancestors 'self'` plus the union of every organization's enabled `embedding` origins and drop `X-Frame-Options` while anything is admitted; with nothing admitted, `frame-ancestors 'none'` and `DENY` as before.\n- `POST …/runs/{runId}/asks/{askId}` and `POST …/tasks/{taskId}/review` take an `actor`; the second requires it. A key holder who is neither Owner nor Admin needs `tale:rest.act-as` to name one. Both writes charge the execute bucket.\n- Team create, rename and delete, and every SCIM write that changes something, emit invalidation hints; open Teams and Members pages in every session refresh without a reload.\n- A OneDrive or Google Drive import lands in the folder open in the app; a file gains **Move to folder…**; a document update that names a destination folder outside the caller's teams is refused with `FOLDER_NOT_ACCESSIBLE`, and a team folder's team is applied to a document moved into it.\n- An auto-retried agent step resumes the failed turn's conversation when that turn had announced its handle.\n- The OAuth apps card reports Google Drive configured when either the connector or the import lane holds a deployment app; the plaintext-secrets warning names the right next step when `SOPS_AGE_KEY` is set.\n- Every data table names its expander column header and, while loading, its select column header; the expander button is announced in the user's language.\n\n## API contract changes\n\n- The OpenAPI document moves from **1.15.0 to 1.16.0**: **85 paths** (from 80), **133 operations** (from 127), **62 schemas** (from 58). New paths: `/api/v1/runs/{runId}/ask`, `/api/v1/runs/{runId}/asks/{askId}`, `/api/v1/projects/{id}/runs/{runId}/ask`, `/api/v1/projects/{id}/runs/{runId}/asks/{askId}`, `/api/v1/projects/{id}/tasks/{taskId}/review` (GET and POST). New schemas: `Actor`, `PendingAsk`, `QuestionSet`, `TaskReview`. No existing path changes.\n- `Me.capabilities` gains `actAs` (required, boolean).\n- The `Error.code` enum grows from **150 to 164** values: `ACTOR_AMBIGUOUS`, `ACTOR_DISABLED`, `ACTOR_FORBIDDEN`, `ACTOR_NOT_FOUND`, `ACTOR_REBOUND`, `ACTOR_UNVERIFIED`, `EMPTY_ANSWER`, `HUMAN_ASK_EXPIRED`, `HUMAN_ASK_NOT_FOUND`, `HUMAN_ASK_NOT_PENDING`, `REVIEW_COMPETENCE_REQUIRED`, `REVIEW_INDEPENDENT_REVIEWER_REQUIRED`, `TASK_HAS_OPEN_SUBTASKS`, `TASK_NOT_IN_REVIEW`. Seven of them — `EMPTY_ANSWER`, the three `HUMAN_ASK_*`, the two `REVIEW_*` and `TASK_HAS_OPEN_SUBTASKS` — were app-only codes before and are on the machine contract now; the `ACTOR_*` codes and `TASK_NOT_IN_REVIEW` are new.\n- `X-Tale-Api-Version` answers `1.16.0`. No existing operation changes shape; `Me` gains a field and the `Error.code` enum grows, so a client generated from 1.15.0 keeps working.\n- Two new codes stay app-only, on `/api/app/trusted-headers`: `TRUSTED_HEADER_KEY_LIMIT` (409, an eleventh live key) and `TRUSTED_HEADER_KEY_NOT_FOUND` (404). The door's refusals are pages, not envelopes.\n\n## Security\n\n- **The trusted-headers credential moves from one deployment-wide shared secret to per-organization keys (#3418).** A key is shown once and stored only as a SHA-256 hash; the organization it signs into is decided by the key, never by anything the request names; the asserted role is capped per organization and can never be Owner; a paused card refuses every key without revoking one; unknown keys are charged to the source address and refused with 429 past the budget; the `Authorization` header is ignored on the door so a REST key is never a hand-off credential. The proxy contract stands and is documented: strip client-supplied `Remote-*` headers, add the key only on the hand-off request, keep the backend reachable only through the proxy. **Anyone holding a key can sign in as any member the proxy names within that organization** — it is a password, held by the proxy, rotated from the card.\n- **The transparent sign-in mints a session from request headers without a page.** It fires only on a GET the app makes for itself, only with the key and the identity header present, only without a session cookie, never for a cross-site fetch and never under the hold cookie; a deployment whose organizations hold no keys never mints. The hand-off's return path is validated to same-origin paths, and the sign-in page's redirect goes to the origin the browser is on, never a configured site URL.\n- **Framing is opened only by an organization's explicit allowlist (#3418).** Each origin is validated by a strict pattern before it is interpolated into the `Content-Security-Policy` header; a file that fails the schema is dropped with a warning, never emitted. The shell is one document for the deployment, so an origin any organization admits may load the shell — the session inside the frame still decides what it can reach, and the door judges per organization. The browser sends the session cookie into a frame only from a same-site page (`SameSite=Lax` is unchanged); a cross-site frame shows the sign-in page.\n- **A key with `actAs` records gestures for other people (#3419).** An Owner or Admin key has the right by role; any other key needs the `tale:rest.act-as` competence an administrator grants, scoped to the organization, optionally expiring, revoked with the membership. The actor must be an active, verified member, their own project and task access decides what the gesture may do, `actor.userId` pins the person against a re-bound address, and every relayed answer and decision is audited naming both the member and the key.\n- **The document move door fails closed (#3420).** The document update accepted a destination folder without asking whether the caller can see it and without applying the destination's team; the app's move action makes it a door, so it now refuses an inaccessible destination and stamps the team, the two rules uploads already follow.\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **A frame carries the signed-in session only from a same-site host page** — a subdomain of the host, or Tale served under the host's own domain. A cross-site frame shows the sign-in page, which, with the loop guard, offers **Try again** instead of bouncing. The embedding allowlist is per organization but the shell's policy is the union across organizations. The dev server's preview keeps a fixed `SAMEORIGIN`.\n- **Revoking a trusted-header key does not end the sessions it started**, and turning the card off refuses every key without ending sessions either.\n- **The browser rounds for the trusted-headers card, the hand-off, the embedding card, the page-less sign-in and the door refusals (`AUTH-F21`–`AUTH-F24`, `AUTH-B10`) are manual**, as is the two-session team round trip (`SET-F41`). The door, the mint, the framing headers, the seat move and the refusals are proved against real Postgres in the backend integration check; the hints are covered per write by their unit suites.\n- **Approvals still have no REST twin.** A run parked on `waitingFor: \"approval\"` is decided in the app only; the ask half of that debt is paid in this release, the approval half is recorded with its design in the ledger.\n- **Moving a document into a team folder changes who can see it**, because the folder's team replaces the document's — the same rule an upload follows. Moving a folder has no door; documents already at the root are not moved by this release.\n- **The auto-retry resumes only a turn that announced its conversation handle.** A turn that died before announcing one, whose sandbox session is gone, or that never launched starts fresh over the preserved workspace, as before.\n- **The Google Drive row counts a deployment app from either lane.** An organization-level app still does not make the row configured, by design.\n- Unchanged from v0.5.34, where each is described in full: a managed deployment gets the organization-creator behaviour only once its specification declares `organizations.creators` and a new bundle is applied; the `AUTH-B9` and `AUTH-F20` rounds are manual; the creator list is matched against sign-in addresses.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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, answers asks and wires triggers.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; approvals have no REST twin; 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`; 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.\n\n## Migration notes\n\n- **One migration, 0108** (`0108_trusted_header_keys.sql`): two new tables, `app.trusted_header_settings` and `app.trusted_header_keys`, with a unique index on the key hash and a listing index per organization. Nothing existing changes, so the previous image keeps serving while the new one migrates. The application database moves from **0107 to 0108**; the knowledge database is unchanged.\n- **One column Better Auth adds at boot**: the session record gains `trustedOrganizationId` beside the existing `trustedRole`, through Better Auth's own schema step that runs after the numbered migrations (logged as `applying better-auth migrations`). Nullable, ignored by the previous image.\n- **Two environment variables are removed**: `TRUSTED_HEADERS_ENABLED` and `TRUSTED_HEADERS_INTERNAL_SECRET`. A deployment that set them ran the deployment-wide trusted-headers mode; on 0.5.35 they are ignored, and its proxy sign-ins are refused until an administrator turns the card on in the organization the proxy should sign into and gives the proxy a key from it. Delete the two variables from the environment. `TRUSTED_SECRET_HEADER` and the other `TRUSTED_*_HEADER` names keep their meaning; the environment reference is updated in English, German and French. No variable is added; `.env.example` is unchanged.\n- **One new organization configuration file**, `governance/embedding.yml`, seeded closed (`enabled: false`, no origins) for a new organization. An existing organization without the file behaves exactly as before: nothing may frame it. A CLI older than this release does not know the `embedding` policy type and refuses the file in `tale config apply`.\n- **Fourteen error codes join the machine contract** and two app-only codes appear; see _API contract changes_. New audit actions: `trusted_headers_enabled`, `trusted_headers_disabled`, `trusted_headers_policy_updated`, `trusted_header_key_created`, `trusted_header_key_revoked`, `trusted_headers_sign_in`, `automation.ask_answered`, `task.review_relayed`.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and the managed proxy _policy_ the CLI renders is unchanged. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the door, the mint, the two cards, the REST doors, the hints, the import destination, the retry resume, the two statuses) and the **docs** image (five pages in each of English, German and French: the API reference, Enterprise SSO, execution logs, authentication configuration and the environment reference) carry source changes. The **web**, **docs** and **ui-docs** images also carry the `@tale/ui` change through the design system they build on. The `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has no change of its own in this range**, but the reference tree it embeds — the platform's shared modules, its core, and the seed catalog with the new embedding policy — does, so the release executables are rebuilt and differ from 0.5.34; they report 0.5.35. A managed deployment should move its pinned CLI reference together with its platform reference, as always.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.35` and `marketing-ui-v0.5.35` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.35\"`. `@tale/ui` changes in this range (the data-table header names and their three message files); `@tale/marketing-ui` does not, so its tag is content-identical to its 0.5.34 predecessor.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.34):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Migration 0108 and Better Auth's column are applied at boot. Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy. A deployment crossing 0.5.33 runs that release's migration 0107 at boot as well.\n\n- **Ran the deployment-wide trusted-headers mode?** Remove `TRUSTED_HEADERS_ENABLED` and `TRUSTED_HEADERS_INTERNAL_SECRET` from the environment. In the organization the proxy should sign users into, an Admin opens **Settings > Enterprise SSO**, turns on **Accept sign-ins from a trusted proxy**, chooses the role ceiling, creates a key and gives it to the proxy in place of the old secret, in the same `Remote-Internal-Secret` header. A proxied member is signed in on the app's first request; routing the proxy's `/log-in` to the hand-off address remains supported. To show Tale inside the application's own page, list that page's origin under **Embedding** on the same page.\n\n- **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. A hosted deployment that wants to admit an application's proxy adds that proxy's public origin to `additionalOrigins` and creates the organization the proxy will sign users into before the administrator enables the card.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- fix(platform): keep every session's team list live after a team write by @yannickmonney in https://github.com/tale-project/tale/pull/3414\n- fix(ui): name the data table's two unlabelled header cells by @yannickmonney in https://github.com/tale-project/tale/pull/3416\n- fix(platform): emit invalidation hints from the SCIM provisioning lane by @yannickmonney in https://github.com/tale-project/tale/pull/3415\n- feat(platform): act for a verified member on ask answers and reviews by @larryro in https://github.com/tale-project/tale/pull/3419\n- fix(platform): resume the failed agent conversation on auto-retry by @larryro in https://github.com/tale-project/tale/pull/3421\n- fix(platform): stop two statuses blaming config that is already set by @Israeltheminer in https://github.com/tale-project/tale/pull/3417\n- fix(platform): land imports in the open folder and allow moving a file by @Israeltheminer in https://github.com/tale-project/tale/pull/3420\n- feat(platform): own trusted-header keys per organization by @larryro in https://github.com/tale-project/tale/pull/3418\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.34...v0.5.35","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.35","publishedAt":"2026-09-18T18:15:58Z"},{"tag":"v0.5.34","version":"0.5.34","name":null,"body":"**0.5.34 carries 2 merged pull requests.** One gives the operator of a deployment a way to name who may create an organization, in place of the two extremes that existed: any signed-in user on a deployment you run yourself, and nobody at all — not even the owner — on a managed deployment, whose proxy refused organization creation for everyone. The other lets a person whose administrator set their password actually leave the forced-change wall on their first sign-in, instead of being bounced back onto it with the password already changed. No migration, no API contract change, no image in the stop-gated tier: the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Who may create an organization is now the operator's to name (#3413)\n\nBetter Auth's organization plugin lets every signed-in user create an organization, and a managed deployment answered that with the opposite extreme: the CLI's proxy policy refused `POST /api/auth/organization/create` for everyone and answered the capability probe `{\"canCreate\":false}` without looking at who asked, so neither the owner nor the operator could open a second workspace, and nothing the deployment declared could change it.\n\n**`TALE_ORGANIZATION_CREATORS`** names the sign-in addresses that may create an organization — commas, semicolons or spaces, matched case-insensitively, the same grammar `TALE_DEPLOYMENT_CONFIG_ADMINS` uses, through one shared parser. The **backend** judges every caller against it, in Better Auth's before-hook for the create route: the edge cannot know who is asking, and `backend-api` is reachable from the sandbox network the edge never sees — the same argument 0.5.33 made for accounts.\n\n- **Unset** keeps the previous behaviour: any signed-in user may create. A workspace deployment or an own Compose file that never heard of the variable is unchanged.\n- **Set**, a listed address passes without a database read; anyone else is refused with **403 `ORGANIZATION_CREATION_FORBIDDEN`** once the deployment holds an organization, and the refusal is logged.\n- **The first organization is always allowed** — the setup flow's, or the managed bootstrap's — so setup is unaffected by any list.\n- A **set but empty** value closes creation to everyone after the first organization.\n\n`GET /api/app/organizations/capabilities` answers the same predicate per caller, so the app shows **Create organization** exactly when the door would open; the create page's forbidden copy already existed in English, German and French, and the app needed no change.\n\n**A managed deployment declares the list instead of inheriting the blanket refusal.** The deployment specification takes `organizations.creators`: 1–64 distinct addresses, literal or environment references that preparation resolves; two spellings of one address are refused as a duplicate. Declared, the CLI writes `TALE_ORGANIZATION_CREATORS` into the runtime environment as a managed variable (an `environment` entry cannot set it), leaves the two organization rules out of the proxy policy — the sign-up refusal stays — records the declaration in the runtime bundle so the bundle reader checks the policy against it, and includes the list in the receipt's input hash only when declared, so every earlier receipt keeps its hash. Undeclared, nothing changes: the edge refuses everyone as before, and the variable is removed if an earlier declaration wrote it.\n\nThe rule is documented on the **Install the tale CLI** page (_Name who may create organizations_, under Managed deployments), on the first-administrator page and in the environment reference, in English, German and French; the manual layer gains `AUTH-B9`.\n\n### The forced-password wall releases on the write's own answer (#3412)\n\nA member whose administrator set their password meets the forced-change wall on first sign-in. Setting the new password left them on it: the page navigates to the dashboard the moment the change resolves, the dashboard gate reads the shared `password-expiry` cache, and that cache still said `expired: true` — so it redirected straight back onto the wall with the form emptied and the password already changed. Only a manual reload got the person through. Reproduced end to end against a real stack.\n\n0.5.31 had closed the cache staleness by invalidating the status after the write, which works when the follow-up read lands — but it made the landing depend on a second round-trip, and with that read failing the flow bounced back onto the wall all the same.\n\nThe write now answers the question itself: `updateUserPassword` recomputes the credential's expiry status and `POST /api/app/users/update-password` returns it as `passwordExpiry`, and the app publishes what the server just decided into the shared cache instead of asking again — no extra round-trip on the critical path, nothing left to fail between the write and the landing. Against a backend that answers no status — a mid-roll deployment still serving the previous image — the app falls back to re-reading, so the roll itself is safe. Proved in real Chromium with the real query cache, account bootstrap, expiry gate and router (the published status with no re-read; still landing when **every** re-read fails; the mid-roll fallback), in the adapter test, against real Postgres in the backend integration check, and in the browser end to end including with the status read forced to 503 throughout. Manual box `AUTH-F20` covers the administrator-set-password path.\n\n## Behaviour changes\n\n- `POST /api/auth/organization/create` is judged in the backend against `TALE_ORGANIZATION_CREATORS` when the variable is set: a listed caller passes, any other signed-in caller is refused with `403 ORGANIZATION_CREATION_FORBIDDEN` once the deployment holds an organization, and the first organization always passes. Unset, the route behaves as before.\n- `GET /api/app/organizations/capabilities` answers `canCreate` per caller by the same rule instead of `true` for everyone.\n- A managed deployment that declares `organizations.creators` no longer refuses organization creation at its proxy and no longer answers the capability probe there; one that declares nothing is unchanged.\n- The CLI refuses a deployment specification whose creator list is empty, longer than 64, repeats an address in another spelling, or names something that is not an address; an `environment` entry that tries to set `TALE_ORGANIZATION_CREATORS` is refused as a managed key.\n- `POST /api/app/users/update-password` answers `{ ok, passwordExpiry }` — the credential's recomputed expiry status — where it answered `{ ok }`; the app leaves the forced-change wall on that value and re-reads only when a backend answers none.\n\n## API contract changes\n\n- **None.** The OpenAPI document stays at **1.15.0** with **80 paths, 127 operations and 58 schemas**, and the `Error.code` enum keeps its 150 values. `ORGANIZATION_CREATION_FORBIDDEN` (403) is a code of the Better Auth door, like `SIGN_UP_CLOSED`; `/api/v1` mounts no organization-creation route, and the REST registry records the code as app-only. The `passwordExpiry` field on the password write is an addition to an `/api/app` door — the app's own session API, not the machine contract.\n\n## Security\n\n- **Organization creation is now a named-list decision made in the backend (#3413).** Before, a managed deployment's only protection was the proxy, which the sandbox network bypasses, and a self-managed deployment had no control at all. The default for a deployment that sets nothing is unchanged in both cases — open where it was open, refused at the edge where it was refused — so the release itself widens nothing; an operator who sets the variable narrows creation to the people named.\n- **What the list does not cover.** A managed deployment that declares no creators keeps today's edge-only refusal, so a signed-in session on the sandbox network can still reach the backend's open create door there, as before this release. Declaring a list closes that door in the backend too. A session is required in every case; the route never answers an unauthenticated caller with anything but 401.\n- **No dependency changes** in this range, and no advisory is fixed. The `@better-auth/oauth-provider` advisory noted in 0.5.33 (CVE-2026-67332 / GHSA-p2fr-6hmx-4528, medium) remains open with its workaround in place; the 1.7.0 upgrade is still a separate dependency pull request.\n\n## Known issues\n\n- **A managed deployment gets the new behaviour only once its specification declares `organizations.creators` and a new bundle is applied.** Until then its proxy refuses organization creation for everyone, exactly as on 0.5.33 — the release alone changes nothing on such a deployment.\n- **The browser round for `AUTH-B9` is manual**: the picker entry disappearing for an unlisted member, the forbidden page, and the entry returning for a listed one. The backend gate, its wiring, the capability route and the CLI declaration are automated, and the signed-in refusal is proved against real Postgres in the backend integration check. `AUTH-F20` (an administrator-set password's first sign-in leaving the wall) is likewise a manual box beside its browser and integration specs.\n- **The list is matched against sign-in addresses.** A person who signs in through enterprise SSO is matched by the address the directory reports; a renamed address must be renamed on the list.\n- Unchanged from v0.5.33, where each is described in full: the sign-up gate's first-boot race; the boot catch-up that marks provisioned accounts verified asks nobody; the break-glass administrator's password-rotation, single-sign-on-link and memory-adapter limits; the cross-scope webhook guard governs deliveries from that release on; a site's robots policy upgrades at its next scan; a scan waiting on render capacity takes longer by design; the governance pickers list only providers with an active credential; one dependency advisory is open.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; 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; 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`; 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.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0107** and the knowledge database is unchanged; the boot-time account catch-up from 0.5.33 keeps running and matches nothing once every provisioned account is verified.\n- **One new environment variable**: `TALE_ORGANIZATION_CREATORS`, documented in `.env.example` and in the environment reference in English, German and French. Unset means unchanged behaviour, so no deployment has to act on this release.\n- **One optional addition to the managed deployment specification**, `organizations.creators`. A specification that declares nothing behaves as before; a managed deployment whose runtime bundle was prepared by an older CLI is unaffected until a bundle prepared with this release's CLI declares the list.\n- **One new error code**, `ORGANIZATION_CREATION_FORBIDDEN` (403), on the Better Auth door only.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change. The managed proxy _policy_ the CLI renders changes only for a deployment that declares creators. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the gate, the capability route, the shared allowlist parser, the forced-change wall's answer) and the **docs** image (three pages in each of English, German and French: the CLI install page, the first administrator and the environment reference) carry source changes. **web** and **ui-docs** rebuild with no change of their own. The `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has source changes in this range** — the `organizations.creators` declaration, its runtime variable, the policy rendering and the bundle reader — so a managed deployment must move its pinned CLI reference as well as its platform reference to declare the list. The release executables report 0.5.34.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.34` and `marketing-ui-v0.5.34` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.34\"`. Neither package has a source change in this range, so both tags are content-identical to their 0.5.33 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.33):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy. A deployment crossing 0.5.33 runs that release's migration 0107 at boot.\n\n- **Want to limit who may open a new organization?** Set `TALE_ORGANIZATION_CREATORS` to their sign-in addresses in the deployment environment and redeploy; the first organization stays allowed, everyone else loses the **Create organization** entry and is refused at the API. Leave it unset to keep the open behaviour.\n\n- **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. To open organization creation to named people, add `organizations.creators` to the specification before preparing that bundle; without it the proxy keeps refusing creation for everyone. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n## What's Changed\n\n- feat(platform): let the operator name who may create an organization by @larryro in https://github.com/tale-project/tale/pull/3413\n- fix(platform): release the forced-password wall on the write's answer by @yannickmonney in https://github.com/tale-project/tale/pull/3412\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.33...v0.5.34","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.34","publishedAt":"2026-09-18T10:53:10Z"},{"tag":"v0.5.33","version":"0.5.33","name":null,"body":"**0.5.33 carries 5 merged pull requests.** Three of them are about who may hold an account on a deployment and what that account is worth: the account-creation route now closes the moment a deployment holds an account, an account an administrator provisions counts as a verified one everywhere at once, and a managed deployment can rename its deploy operator in place and declare a break-glass administrator whose password never reaches the deployment. The fourth closes the 2026-09-18 external API evaluation — the backend no longer stamps Sentry trace headers onto outbound requests, the crawler honours the robots group written for it by name, a webhook re-posted across scopes is refused instead of run twice, and a third concurrent site scan waits for a render slot instead of failing — and moves the contract to **1.15.0**. The fifth reconnects two governance model pickers that had been empty since the AI-backend rewrite. **One migration** (application database 0107, nullable column and index, rolling-safe), **one new environment variable** for throwaway test stacks only, and nothing in the stop-gated tier: the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Account creation closes once a deployment has an account (#3407)\n\n`POST /api/auth/sign-up/email` answered anyone who could reach the backend. The proxy in front of a deployment refuses that route, but `backend-api` is also attached to each box's sandbox network so the in-sandbox connectors bridge can call its host door — and the proxy never sees a request that arrives that way. Measured on a test deployment, from a container on that network: **200, with a live session token for a brand-new account.** Code running inside an agent session — a prompt-injected agent, or an automation body — could create accounts on a deployment whose edge refuses exactly that.\n\nThe auth before-hook now refuses the route with **403 `SIGN_UP_CLOSED`** once the deployment holds an account, and keeps the two paths that exist: the **first** account over HTTP (the setup flow in the browser, or the managed CLI's bootstrap), and every later one **server-side** — an administrator's **Settings > Members** door and the deploy's own provisioning reach Better Auth as calls that carry no request and bring their own authorization. The refusal is logged (`[sign-up] refused: this deployment has an account`), because nothing else records the attempt. The login page already offered setup only while the deployment was empty; the backend now agrees with it, through one shared `hasAnyUsers` query so the page can never offer a screen the backend refuses.\n\nA throwaway test stack that mints its own accounts over HTTP reopens the route with **`TALE_ALLOW_OPEN_SIGN_UP=true`**. The local dev orchestrator, the dev compose overlay and the backend integration check set it for themselves; a real deployment leaves it unset. The managed CLI learned the refusal too: a deploy whose bootstrap sign-up is refused takes back the journal marker it wrote a moment earlier — nothing was created — and says what to do instead of reporting an authentication failure: sign in as the break-glass administrator and set the operator password back to the one the deploy carries.\n\n### A provisioned account is a verified account (#3408)\n\nTale sends no mail and has no self-service sign-up, so an address could never become verified by anything a person or an administrator could do. The setup wizard's owner and every colleague added under **Settings > Members** started unverified and stayed that way; only enterprise SSO, SCIM, trusted headers or the CLI's `operator-attested` declaration ever set the flag. Everything that requires a vouched-for address therefore refused those people: identity issuance answered **403 `IDENTITY_NOT_ELIGIBLE`** — after Tale's own login page had already sent them back to the application, so the relying party showed a generic failure — and conversation synchronization and the notification mirror skipped them.\n\nWhoever provisions an account names its address, and on this platform that assertion is the only verification there can be — the same one the CLI records as `operator-attested`. **Every account Better Auth creates here now arrives verified**: the setup wizard's owner, a member added under Settings > Members, the deploy's own accounts. A directory-provisioned account (SSO, SCIM, trusted headers) is written straight to the table and keeps its provider's verdict. Connected applications read this as the `email_verified` claim on the identity Tale issues, so a colleague an administrator just added can sign in to them immediately.\n\n**Existing deployments heal themselves.** After the numbered migrations and Better Auth's own, boot catches up the accounts this instance provisioned — a `credential` row is the proof that it issued the password — and logs how many it verified. It is not a numbered migration, because the application's `.sql` files run before Better Auth's tables exist and do not own them; it runs on **every** boot, so a rolling deploy cannot leave behind an account the previous image created while the new one was already up. A directory account has no credential row and is left alone. Both halves are proved against real Postgres in the backend integration check.\n\n**Five minutes to sign in.** The identity provider signs the login and consent continuation with the same lifetime it gives an authorization code, so `codeExpiresIn: 60` was also the window a person had to find a password and answer a second factor before the request expired and the provider's error page took over. It is now **300 seconds** — still a single-use, PKCE-bound code, within the conventional ceiling. The CLI pins the backend's provider policy before it provisions a managed client, so its expectation moves with the platform: **a deploy needs a CLI at least as new as the platform it deploys**, which the managed bundle already requires.\n\n### A machine address for the deploy operator, and a break-glass administrator (#3406)\n\nA managed deployment (`tale deploy --bundle`) signs in as its `identity` operator on every run, with the password alone. That had two consequences. An organization could not enforce `two_factor_policy` without breaking its own deploys: a password sign-in of a TOTP-enabled operator is answered with a challenge, and an operator with neither factor is held at the enrolment wall once its grace period ends — and the CLI stopped at both with one generic message. And the operator's **user id** anchors the retained state — the bootstrap and email-attestation journals, managed native client intents, operator-owned skills (which have no ownership transfer) and API keys — so a _new_ account could not take over an existing deployment's deploys.\n\nThe way out keeps the account and gives it a machine address, so that people sign in with accounts of their own and choose either factor.\n\n- **`identity.migrateEmailFrom`** — the retained operator's previous sign-in address. Requires `bootstrap: \"fresh\"` and must differ from `identity.email`. The CLI reads the retained account's current address backend-locally (it must be the declared or the previous one, and no other account may hold the declared one), signs in with it and proves the retained user id, journals `migratingEmailFrom`, renames the account through Better Auth's internal adapter in a write scoped so that only this exact update passes — guarded by `email = previous`, so a concurrent change is refused rather than overwritten — ends **every** session of the account, signs in again with the new address and proves the same user id. A declared operator attestation re-verifies the new address, admitting the previous address's completed journal exactly once. A replay after an interruption finds the address already moved and finishes without a second rename; leaving `migrateEmailFrom` declared afterwards is harmless. Renaming an operator that holds a single sign-on link is refused: the link belongs to the person who signed in with it.\n- **`identity.breakGlass: { email, passwordHash }`** — one administrator for when the operator is unavailable. `email` is a literal or a required environment reference, distinct from the operator's current and previous address; `passwordHash` is a required environment reference to a Better Auth hash (`<32 hex>:<128 hex>`). The host resolves both into private provisioning stdin only; the bundle carries the variable names, and **the password never reaches the deployment**. After the managed organization is selected, every deployment converges on the declaration: an absent account is created with a verified address, and `break-glass.json` binds its id **as soon as the user row exists**, before its single `credential` account is linked — the platform's adapter runs without transactions, so this binding is what makes a partial creation resumable. A credential write (first link or replacement) is journaled as pending beforehand and cleared only after the account's sessions are proven ended, so a run that dies between the write and the sweep still ends the old sessions on replay. An unbound account at the address is adopted only when it is this deployment's own interrupted creation; any other account is refused, and so are the operator and a different account holding a bound address. Membership converges through the organization-scoped member doors — add as `admin`, or set `admin`; an `owner` is never touched. **The deployment never signs in as this account**, and the ready proof must name it.\n- **`tale auth hash-password`** prints that hash from stdin, or from a hidden prompt with confirmation in an interactive terminal. It refuses a password that fails the platform's default password policy and prints nothing but the hash. It is a local helper that manages no instance, so it is excluded from the CLI's version alignment — a re-exec would hand its stdin password to another binary.\n- **Two named refusals** replace the generic MFA message: _The deploy operator has TOTP enabled; a managed deploy signs in with its password alone, so the operator must hold a passkey and no TOTP_, and _The deploy operator has no second factor and its two-factor enrolment grace period has ended; register a passkey for it_. Operator-address and break-glass refusals name the conflict — another holder, a missing account, a retained binding — and never echo credentials.\n\nThe new suites drive **real Better Auth 1.6.30** — its internal adapter, adapter scoping and transactions — over its memory adapter, and an independent adversarial read before merge found four defects that are fixed in the same change: the role door needs `orgId`, an interrupted rotation could leave old sessions, a foreign account could be adopted as administrator, and account creation is not transactional on the platform adapter. All of it is documented under _Rename the deploy operator's address_, _Declare a break-glass administrator_ and _Enforced two-factor sign-in_ on the **CLI install** page, in English, German and French, and in the CLI readme.\n\n### The 2026-09-18 API evaluation findings are closed (#3410)\n\nThe tenth external black-box evaluation of the platform API ran against 0.5.32 — about 2,700 requests, zero 5xx, no data loss, no cross-tenant leak, no auth bypass, verdict _mature_ — and rated the website crawler its weakest surface. Every finding was re-confirmed against the code before it was fixed. The four serious ones:\n\n- **Sentry trace headers leaked from the backend — and not only from the crawler.** The empty `tracePropagationTargets` list from the previous round is correct and present, but the deployment's shared environment file carries `SENTRY_TRACES_SAMPLE_RATE` into the backend containers (the CLI writes it, `'0'` by default). Any value switches span recording on; the http and fetch integrations then register OpenTelemetry's request instrumentation beside their own, and its propagator derives the target URL from the active span — an unsampled span records no URL, so the target list is never consulted and `sentry-trace` and `baggage` (release, public key, environment) went out on **every** outbound request. The two integrations are now registered with `spans: false`, which keeps the request lanes on the Sentry-native hooks that honour the target list. Proved on the wire with the sample-rate variable set, with a positive control.\n- **The `User-agent: TaleBot` robots group was ignored.** Only the `*` group was ever read, so a site that followed the documentation and addressed TaleBot by name to refuse or throttle it was crawled under the rules it wrote for everyone else. robots.txt is now parsed for the product token per RFC 9309 — the named group binds, else the `*` group — with `Allow`, longest-match precedence, `$` end-anchors (a `Disallow: /*.pdf$` used to fail open) and `Crawl-delay` (capped at 60 seconds, paced on both fetch legs and on sitemap reads), honoured by discovery, every continuation link, the rendered-page admission, the retirement pass and the registration-time homepage probe — which also identifies as TaleBot now instead of a bare `node` user agent. A `Disallow: /` site no longer has its `/sitemap.xml` guessed or its homepage walked for links, and a scan that stored no page because robots refused everything lands on the documented `error` state with that reason instead of reading `active` with zero pages.\n- **A cross-scope webhook redelivery ran twice, silently.** The delivery key folded the requested project into its hash, so the same delivery id at the organization door and at a project door produced two keys, two ledger rows and two runs — an operator who moved an automation between organization and project scope had the sender's redelivery run again, and the documented `409 AUTOMATION_DELIVERY_SCOPE_MISMATCH` never fired. Migration **0107** adds a scope-free `identity_hash`; the accept path reads the live rows that share it and answers the 409 when one belongs to the organization door and the other to a project door. Per-project fan-out — the same id starting one run in each installed project — stays legitimate.\n- **A third concurrent scan failed the whole scan.** An organization's render sessions are a shared budget (two by default), and a third scan that found it spent ended in `error` with everything it had fetched discarded, and no retry until the next scan interval. The refusal is now a wait: the link polls for a slot every 15 seconds while its window allows, and when none comes it leaves the batch unmarked for the next link, which follows after 60 seconds instead of the usual five. Only infrastructure failures other than capacity still fail the scan.\n\nAnd the rest of the round, each a small honest thing: `Run.failureCode` listed `budget_exceeded` twice (the chat and agent families both name it), which made `openapi.json` fail OAS 3.0.3's `uniqueItems` and aborted `openapi-python-client` by default — deduplicated, with a guard; the MCP `cancel_run` tool answered _already finished_ for an id that did not exist, which a cancel-until-refusal loop read as success — now `RUN_NOT_FOUND`, like `get_run` and REST; a task create's 201 carries `Location`; a redirect off the registered site is reported as its own `host_not_allowed` kind instead of wearing the `private_ip` label (with a Websites-dialog label in English, German and French); a registration with a non-default port is refused instead of silently crawling the wrong origin on 443; website search's `total` is a real match count rather than the page size; `Product.currency` accepts any case, as the door always did; `/openapi.json` carries an `ETag` and answers 304; and the chat send documents that its 100,000-character cap counts UTF-16 code units.\n\n### The governance model pickers are filled from the live catalog (#3411)\n\nOn **Settings → Governance → Default models**, the _Add default model rule_ dialog's Provider dropdown showed **No providers found** even with provider credentials configured, and the **Model access** editor's pickers were empty for the same reason. Both read their options through two hooks that the 2026-08 AI-backend rewrite had replaced with stubs returning nothing while the catalog was offline; the catalog came back and already powers the Providers page, but these editors were never reconnected.\n\nBoth hooks now read the live provider catalog. The provider list is scoped to the providers the organization holds an **active credential** for, so a default rule can never point at a provider that could not serve it and the set matches what the Providers page shows; the model-info popover gets the catalog's context window, cost, reasoning, tools and vision.\n\n## Behaviour changes\n\n- `POST /api/auth/sign-up/email` answers **403 `SIGN_UP_CLOSED`** once the deployment holds any account, unless `TALE_ALLOW_OPEN_SIGN_UP=true`. The setup flow and the managed CLI's bootstrap still create the first account; Settings > Members and the deploy's provisioning are server-side and unaffected. A refused attempt is logged.\n- Every account Better Auth creates on this platform arrives with `emailVerified: true`; directory-provisioned accounts keep their provider's verdict. At boot, accounts this deployment provisioned earlier (those with a `credential` row) are marked verified, every boot, with a log line naming the count. The `email_verified` claim on an issued identity, conversation synchronization and the notification mirror follow.\n- The identity provider's authorization code — and with it the signed login-and-consent continuation — lives **300 seconds** instead of 60.\n- A managed deployment stopped by an enforced two-factor policy names whether it met a TOTP challenge or the enrolment wall.\n- The backend sends no `sentry-trace` or `baggage` header on any outbound request, whatever `SENTRY_TRACES_SAMPLE_RATE` says.\n- The crawler obeys the robots.txt group that names `TaleBot` over `*`, honours `Allow` with longest-match precedence, `$` end-anchors and `Crawl-delay` up to 60 seconds, never fetches a guessed `/sitemap.xml` or walks the homepage when robots covers them, and fails a scan that robots left with no page to store. The rules persist on the site row as an object rather than a bare list; rows written by earlier scans are still read.\n- A webhook delivery re-posted across the organization/project boundary within its identity window answers `409 AUTOMATION_DELIVERY_SCOPE_MISMATCH`; the same id across two installed projects still starts one run in each.\n- A site scan that finds the organization's render sessions spent waits for one (15-second polls within its window) and otherwise defers the batch to its next link 60 seconds later; it no longer ends in `error`.\n- MCP `cancel_run` on an unknown run id answers `RUN_NOT_FOUND`. `POST …/projects/{id}/tasks` answers 201 with a `Location` header on a create (an upsert that matched an existing task is a 200 without one). `/openapi.json` carries an `ETag` and answers 304 to a matching `If-None-Match`.\n- A crawl redirect that leaves the registered site is recorded as `host_not_allowed`, with a message that says so; registering a site with a non-default port answers `400 WEBSITE_DOMAIN_INVALID`.\n- `POST /api/v1/websites/{id}/search` answers a `total` that counts every matching chunk on the site — it can exceed `results.length` — instead of the page size.\n- The governance _Default models_ and _Model access_ editors list the providers the organization has an active credential for, with their models and capabilities.\n\n## API contract changes\n\n- **1.14.0 → 1.15.0.** The surface is unchanged at **80 paths, 127 operations, 58 schemas** and **150** `Error.code` values; the version moves because a generated client sees the difference. Regenerating the document from this commit reproduces the committed file and its contract fingerprint.\n- `Run.failureCode`, `RunProjection.failureCode` and `RunSummary.failureCode` list `budget_exceeded` **once**. The document now validates as OAS 3.0.3, and `openapi-python-client` generates from it with its default settings. A spec test refuses a repeated enum value from here on.\n- `WebsitePage.lastErrorKind` gains **`host_not_allowed`** — a redirect off the registered site, which the crawler does not follow; it used to be reported as `private_ip`, whose description now says what it means.\n- `GET /api/v1/skills/{slug}/files/{path}` declares the conditional GET the door already answered: `If-None-Match` and `If-Modified-Since` request headers, `ETag`, `Last-Modified` and `Cache-Control` on the 200, and a **304** response.\n- `Product.currency` on create and update is `^[A-Za-z]{3}$` — any case in, stored uppercase, as the door always behaved; the old `^[A-Z]{3}$` made a generated client refuse `\"eur\"` the door accepts.\n- `WebsiteSearchResults.total` is documented as the count of all matches across the site, not the size of the answer; this door has no pagination.\n- `Automation.createdBy` and `AutomationVersion.createdBy` document `system:provisioning` for the built-in connector automations (split on the first `:`; a value without one is a user id).\n- The chat send's `content` documents that its 100,000 cap counts UTF-16 code units, so an astral character costs two.\n- Not in the schema but on the wire: `Location` on a task create's 201, `ETag`/304 on `/openapi.json`, `RUN_NOT_FOUND` from MCP `cancel_run`, and `AUTOMATION_DELIVERY_SCOPE_MISMATCH` — already in the enum — actually answered.\n- `SIGN_UP_CLOSED` (403) is a code of the Better Auth door, not of `/api/v1`, whose surface mounts no sign-up route; the REST registry records it as app-only.\n\n## Security\n\n- **Unauthenticated account creation from inside the deployment's own network is closed (#3407).** The sign-up route answered whoever could reach the backend, and the sandbox network could. The measured result was a new account with a live session token; that account belonged to no organization, so it read no tenant data, but it was a foothold on the deployment, and an address created that way is one an operator can no longer declare for a machine account. The route now refuses once an account exists, and the refusal is logged. One limit is stated in the code rather than hidden: on an _empty_ deployment the probe and the insert are separate statements, so simultaneous first sign-ups are all admitted.\n- **The backend no longer leaks its Sentry release, public key and environment to third parties (#3410).** The previous round's fix held on the render leg only; with `SENTRY_TRACES_SAMPLE_RATE` in the environment — which every CLI-written deployment has — the plain fetch leg, and every other outbound request the backend makes, carried `sentry-trace` and `baggage`. Registering the http and fetch integrations with `spans: false` closes it for the whole backend, and the wire test now runs with the variable set.\n- **What `email_verified` means on this platform (#3408).** A relying party reading the claim from a Tale-issued identity should know it now says _an administrator or the operator named this address_, not _the person clicked a link_ — Tale has no link to click. Accounts from a directory (SSO, SCIM, trusted headers) carry their directory's verdict, unchanged.\n- **The break-glass administrator's password never reaches the deployment (#3406).** Only its Better Auth hash travels, resolved on the host into private provisioning stdin; the deployment never signs in as that account; any credential change ends the account's sessions, also when a run finishes an interrupted one; a foreign account at the declared address is refused, never adopted.\n- **The authorization-code lifetime is 300 seconds (#3408)**, up from 60, for a single-use, PKCE-bound code that also gates the login and consent continuation — within the conventional ceiling.\n- **A crawl redirect that leaves the registered site is refused under its own name (#3410)**, and a non-default port is refused at registration; neither is a new refusal, but a public off-site redirect is no longer reported as a private-network attack, and a site can no longer be registered at `host:8443` and crawled on 443.\n- **A cross-scope webhook replay is refused (#3410)**: the sender's redelivery no longer runs an event a second time after an automation moved between organization and project scope.\n- **No dependency advisory is fixed in this release**, and no dependency changes in this range. One advisory is **open** against a runtime dependency: `@better-auth/oauth-provider` 1.6.30, the identity provider behind _Continue with Tale_, is affected by **CVE-2026-67332 / GHSA-p2fr-6hmx-4528** (CVSS 6.4, medium) — a client could request an access token for a different allow-listed audience than its authorization covered. The platform already applies the advisory's own workaround and has since the provider shipped: `validAudiences` is empty, so no resource outside the deployment's own can be minted, and only the `authorization_code` grant is enabled, so the refresh-grant path does not exist. The upgrade to 1.7.0 is a breaking change with a schema migration and is tracked as a separate dependency pull request, not carried here.\n\n## Known issues\n\n- **The sign-up gate has a first-boot race.** On a deployment with no account at all, simultaneous first sign-ups are all admitted; the gate closes once one exists. The setup flow is the only intended caller of that window.\n- **A managed deploy that cannot authenticate its operator can no longer mint a replacement administrator** — that was the hole. The recovery is the break-glass administrator: sign in as it and set the operator password back to the one the deploy carries, as the CLI's refusal says.\n- **The boot catch-up asks nobody.** Every account with a `credential` row that this deployment holds becomes verified at the first boot of 0.5.33 (and any it missed at every later boot). There was no state on this platform in which an unverified local account was intended, but an operator who relied on the flag being false for such accounts should read the _Security_ note above.\n- **Break-glass limits, each documented on the CLI install page:** the deployment records no password-change time for the break-glass account, so an organization password rotation policy may ask it for a new password, which the next deployment sets back; renaming an operator that holds a single sign-on link is refused until the link is removed; the backend-local suites drive real Better Auth over its memory adapter, not the platform's Postgres adapter.\n- **The cross-scope webhook guard governs deliveries from here on.** Rows written before the upgrade carry no `identity_hash` and never match, so a delivery first taken on 0.5.32 and re-posted across scopes on 0.5.33 still runs; the guard applies within the delivery's identity window — 24 hours for a header-named id, two minutes for a body-hashed one.\n- **A site's robots policy upgrades at its next scan.** Until then the row holds the bare `Disallow` list an earlier scan stored, read as no `Allow` rules and no delay. `Crawl-delay` is honoured up to 60 seconds; a site that asks for more gets 60.\n- **A scan waiting on render capacity takes longer, by design**, and can still end in `error` if its continuation budget runs out with pages waiting — the reason then says so (_N page(s) were still waiting when the scan's continuation budget ran out_). The organization's render-session budget itself is unchanged.\n- **The Sentry fix is proved by a reproduction and the wire test, not yet on a fleet host** — no deployment runs this release yet. The check is the same as the evaluation's: a logging origin behind the crawler should see no `sentry-trace` or `baggage` header on either fetch leg.\n- **The governance pickers list only providers with an active credential.** A provider whose credential is missing, expired or disabled does not appear in _Add default model rule_ or _Model access_, even though the catalog ships it. That is the intended reading; the alternative (every shipped provider) is a one-line filter change if it turns out wrong.\n- **One dependency advisory is open** (`@better-auth/oauth-provider`, above); the workaround is in place, the upgrade is not.\n- A page is still fetched three to four times per scan — the plain probe, the render pass and, for the homepage, the create-time metadata probe. The robots `$`-anchor and `Allow` half of that ledger item is paid down by this release.\n- Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and `minTokensPerSecond` is a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster, `config-data` needs RWX or a single node, and Tale ships no Helm chart.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy _policy_ only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; a cancelled run answers `trace: null` and `effects: null` where a failed run answers both; 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` (its `total` is now honest); 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.\n\n## Migration notes\n\n- **One migration, forward-only and rolling-safe.** **0107 `automation_webhook_delivery_identity`** on the application database, applied by the backend at boot inside the advisory lock while the previous image keeps serving: `ALTER TABLE app.automation_webhook_deliveries ADD COLUMN IF NOT EXISTS identity_hash text` and an index on `(trigger_id, identity_hash, expires_at_ms)`. The column is nullable, the previous image neither writes nor reads it (its rows simply do not take part in the new guard), and no backfill is possible or needed. The application database moves from **0106 to 0107**; the knowledge database is unchanged. There is no down migration by design.\n- **One boot-time catch-up that is not a numbered migration.** After the migrations, the backend marks verified every account that holds a `credential` row and is not yet verified, and logs `verified N provisioned account(s)` when it changed any. It runs on every boot and matches nothing once they are all verified.\n- **One new environment variable**: `TALE_ALLOW_OPEN_SIGN_UP`. Unset on a real deployment. Set to exactly `true` only on a throwaway test stack that mints its own accounts over HTTP; the local dev orchestrator, the dev compose overlay and the backend integration check set it for themselves. Documented in `.env.example` and in the environment reference in English, German and French.\n- **Two optional additions to the managed deployment specification**, `identity.migrateEmailFrom` and `identity.breakGlass`, and **one new CLI command**, `tale auth hash-password`. A specification that declares neither behaves as before.\n- **One new error code**, `SIGN_UP_CLOSED` (403), on the Better Auth door only.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change, and neither does the managed proxy policy the CLI renders. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the sign-up gate, the verified-account rule and its boot catch-up, the provider lifetime, the evaluation fixes, the governance pickers) and the **docs** image (five pages in each of English, German and French: the CLI install page, first administrator, authentication, the environment reference and members and roles) carry source changes. **web** and **ui-docs** rebuild with no change of their own. The `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has source changes in this range** — the operator rename, the break-glass administrator, `auth hash-password`, the sign-up refusal and the provider-policy pin — so a managed deployment must move its pinned CLI reference as well as its platform reference. **A CLI from before this release refuses to provision a managed client against this platform**: its expectation of the provider's code lifetime moved with the platform. The release executables report 0.5.33.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.33` and `marketing-ui-v0.5.33` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.33\"`. Neither package has a source change in this range, so both tags are content-identical to their 0.5.32 predecessors.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.32):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  The migration and the account catch-up run at boot. Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **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. Pin the CLI first: a CLI older than this release refuses to provision a managed client against a 0.5.33 platform. 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.\n\n- **Enforcing two-factor sign-in on a managed organization?** Give the deploy operator a passkey and no authenticator app before you enforce the policy, or the next deploy stops at the challenge — the refusal names which wall it met. Then consider giving the operator a machine address with `identity.migrateEmailFrom`, and declaring `identity.breakGlass` with a hash from `tale auth hash-password`, so people sign in with their own accounts and a person can still recover the deployment. All three are under _Managed deployments_ on the **CLI install** page.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n- **Running a test stack that creates its own accounts over HTTP?** Set `TALE_ALLOW_OPEN_SIGN_UP=true` on it, or every account after the first is refused. Never set it on a real deployment.\n\n## What's Changed\n\n- feat(cli): rename the deploy operator and declare a break-glass administrator by @yannickmonney in https://github.com/tale-project/tale/pull/3406\n- fix(platform): close sign-up once a deployment has an account by @yannickmonney in https://github.com/tale-project/tale/pull/3407\n- fix(platform): treat a provisioned account as a verified account by @yannickmonney in https://github.com/tale-project/tale/pull/3408\n- fix(platform): close the 2026-09-18 round-J API evaluation findings by @larryro in https://github.com/tale-project/tale/pull/3410\n- fix(platform): fill the governance model pickers from the live catalog by @larryro in https://github.com/tale-project/tale/pull/3411\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.32...v0.5.33","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.33","publishedAt":"2026-09-18T08:58:26Z"},{"tag":"v0.5.32","version":"0.5.32","name":null,"body":"**0.5.32 carries 4 merged pull requests.** The large one teaches Tale to pace its embedding work to what an embedding server can actually take: how many requests it may have in flight, and how long a request may take once the server's own queue is counted — both stated by the operator, next to the model. Beside it, self-hosting gains a **Deploy on Kubernetes** guide with five apply-ready manifests, a code file the browser names no type for can be attached and sent again, and the chat budget banner becomes an announced, readable Alert in the composer's own column. No migration, no contract change, and no image in the stop-gated tier: the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### Embedding requests are paced to the server's stated capacity (#3402)\n\nKnowledge indexing sent a document's 64-text batches with up to three requests in flight per `Embedder`, a flat 60-second timeout per request and three attempts. Every indexing job, website scan and search builds its own `Embedder`, so concurrent jobs multiplied that bound: five jobs put fifteen requests on a server sized for three.\n\nOn a hosted embedding API that never showed. On a server you run yourself — one that computes a request at a time and queues the rest — a request queued behind three full batches is answered after about two minutes. Tale abandoned it at 60 seconds and sent it again into the same queue, so large documents failed to index, and the pile-up starved whatever else shared those GPUs.\n\nTwo optional settings in `embedding.json` now describe the server, and everything else follows from them.\n\n- **`maxConcurrentRequests`** (1–64, default 3) is how many requests may be in flight **per organization, endpoint and model**, shared by every embedder in the process rather than counted per embedder. Requests wait in arrival order, except that a search query — someone is waiting on it — takes the next free slot ahead of queued batches, and never interrupts one in flight. A lowered limit applies at once; a raised one applies once the requests started under the old limit have finished. A failed, timed-out or abandoned request always frees its slot, and the slot is held across a request's own retries so a retrying batch does not rejoin the queue at its end. The organization is part of the key because a lane is a queue callers wait in, and nothing one organization does may hold up another's.\n\n- **`minTokensPerSecond`** is the rate the server sustains for this model under its usual load — measured with the other work on that hardware running, but *not* counting time a request waits behind other requests, because Tale allows for that itself. Stated, it sizes each request's timeout as queue wait plus compute: the request's own tokens, estimated generously from its characters, plus `2 × maxConcurrentRequests − 1` other requests each counted as at least a full batch of 64 texts of 1,024 tokens (and at least this request's own size), divided by the rate and multiplied by 1.5. Never under 60 seconds, never over the ceiling.\n\n**The ceilings are the budgets that already existed.** A batch gets at most 15 minutes — the budget of one `rag.index_file` attempt. A search query gets at most 5 minutes, for each request *and* for the whole search including its wait for a slot and any pause between attempts: a query runs inside a chat turn's tool call, which does not move the turn's heartbeat, and the chat generation watchdog takes a turn for dead after ten minutes of stillness. A guard test holds the search ceiling at or below half that window so the turn has time to report the search's own error instead of being swept as \"interrupted by a restart\". Without a stated rate, nothing says how long the server's queue may take, so a request may use its whole ceiling.\n\n**Retries no longer pile work onto a busy server.** A timed-out request is not sent again: the server held it the whole time, and a repeat only lengthens its queue. A refused connection, a rate limit or a server error is retried after a growing pause, or after the pause the server asked for with `Retry-After` when that is longer; a server that asks for more than a minute is left alone and the caller's own slower retry decides when to come back. The OpenAI SDK's internal retries stay off, so this module's loop is the one retry policy.\n\n**One failed batch stops its siblings.** The batches of a call run on a signal of their own, combined with the caller's through `AbortSignal.any`. The first batch to fail cancels the running ones and leaves the queued ones unsent, so no slot is held for vectors nobody will store, and that first error is the one the caller sees — the aborts it caused never replace it.\n\n**An expired indexing job stops instead of racing its own retry.** The worker now hands each handler pg-boss's job signal, which pg-boss aborts when a job outlives its queue's budget or the worker shuts down. `rag.index_file` stops before its next slice and cancels the request in flight. It writes no `failed` status — the row keeps its progress — so the retry resumes after the stored slices rather than running beside the attempt it replaces; a retry that never comes is settled by the RAG watchdog as before.\n\n**The CLI speaks the same three ways.** `knowledge-embedding` declares both fields and checks convergence per setting: a value sets it, an omitted key leaves whatever the file holds, and `null` clears it. A change confined to `minSimilarity`, `maxConcurrentRequests` or `minTokensPerSecond` leaves every stored vector valid, so it skips the empty-corpus refusal that a model change still triggers.\n\nBoth settings are documented with a worked example under _Pace requests to a self-hosted embedding server_ on the **Data residency and knowledge storage** page, and in the `knowledge-embedding` section of the **CLI install** page.\n\n### Deploy on Kubernetes (#3404)\n\nA new self-hosted install page, **Deploy on Kubernetes**, in English, German and French. It carries five apply-ready manifest files for a single namespace, installed and upgraded by one `envsubst '${VERSION}' | kubectl apply` loop:\n\n- `00-namespace.yaml` — namespace, shared Secret, the `config-data` claim\n- `10-stores.yaml` — the Postgres StatefulSet behind the `db` and `knowledge-db` Services, and MinIO\n- `20-application.yaml` — API, worker, web tier, video-token provider, and the backend egress NetworkPolicy\n- `30-proxy.yaml` — Caddy on `hostPort` 80/443 with its certificate claim\n- `40-sandbox.yaml` — the egress proxy, the LLM gateway with both Services, and the spawner's ServiceAccount, Role, RoleBinding and Deployment\n\nAround them: the prerequisites (a NetworkPolicy-enforcing CNI, a StorageClass, RWX or a single node for `config-data`, node sizing, the entry point, image access, a RuntimeClass for nested Docker), the namespace layout, the Service names, the probe table, what the spawner enforces, and the commands that prove a deployment — the migration count, both policies, health, fence probes from a session Pod, and a rolling restart with two replicas.\n\nThe page exists because the whole 0.5.31 stack was run on a kind cluster — ten services, the spawner on `SANDBOX_BACKEND=kubernetes`, first owner, an agent task with a deliverable, session lifecycle including idle stop and resume, cross-replica access and a rolling restart. Five contract gaps came out of that run and are the load-bearing warnings on the page:\n\n1. `enableServiceLinks: false` is mandatory. A Service named `sandbox` injects `SANDBOX_PORT=tcp://…` and the spawner exits parsing it; `DB_PORT` corrupts the URL `env.sh` derives.\n2. The platform image's `NET_ADMIN` iptables fence self-severs on a Pod network — only scope-link routes are accepted, so even CoreDNS is rejected (`getaddrinfo EAI_AGAIN db`). The page uses `TALE_SKIP_SSRF_FIREWALL=1` together with a backend egress NetworkPolicy, which is the fence on this path.\n3. Session Pods must reach `backend-api` inside the spawner policy's namespace allowance, so the application roles live in the sandbox namespace.\n4. That same allowance exposes the stores' Service ports to session Pods. Credentials are still required, and it is documented as a known limit.\n5. Caddy listens on the port named in `SITE_URL`, so an address with a non-standard port also needs that port as the proxy's `containerPort` and `hostPort`.\n\n**Run Compose yourself** keeps the service contract — names, volumes, probes, environment — and now points at the new page instead of carrying the Kubernetes tables itself; the install index links both.\n\n### A code file the browser names no type for can be sent again (#3403)\n\nAttaching a source or config file the browser reports no MIME type for — `render.cjs`, and the same for `.mjs`, `.go`, `.rs`, `.sh` — made the send fail. `resolveFileType` answered `''`, the composer staged `fileType: ''`, and both chat send doors refuse an empty type (`z.string().min(1)` on `POST …/messages` and on its deferred-send twin), so the request came back `400 {\"error\":\"invalid body\"}` in about ten milliseconds. Indexing was fine throughout; only the send was refused.\n\n`resolveFileType` never answers an empty content type now. Six files had already patched the hole with their own `|| 'application/octet-stream'` — eight copies in all, three of them in the upload hook alone, including the metadata write three lines above the staged attachment, which is why the stored row said `application/octet-stream` while the wire said `''`. The fallback lives in the resolver, the copies are gone, and the store and the wire carry one value. It widens nothing: `application/octet-stream` is in no allowlist, and the text, RAG and size gates key on the extension.\n\nThe second half is what the failure looked like. The refusal branch and the start-throw catch already cleared the optimistic send, but the rejection branch restored the composer text and left the bubble and its `Thinking · Ns` shell on screen, so a refused send read as a turn that never answers — until a reload showed the message gone. A rejected turn now drops its overlay with the text it gives back.\n\n### The chat budget banner is announced, and readable (#3405)\n\nThe \"Usage limit reached\" banner above the chat composer is a `@tale/ui` `Alert` in the composer's own column instead of a full-width strip with a bare bottom border.\n\nThe strip was designed for the top of the chat pane and kept those classes when it was moved above the composer, so it spanned the pane while the composer and the deferred-send tray sit in a centred column. Worse, the exceeded state painted the whole line in the destructive colour: 3.3:1 on its own tint in light mode, below the WCAG AA floor of 4.5:1. The Alert doctrine puts the accent on the fill, the border and the glyph and keeps the copy at foreground contrast. And the banner had no live region at all, so the hard block — the state in which sending is refused — was never announced; `Alert` supplies `role=\"alert\"` with `aria-live=\"polite\"`.\n\nThe period was also interpolated raw, which produced \"2,000 of 10,000 token left this monthly\", \"setzt sich monthly zurück\" and \"se réinitialise monthly\". The four period strings in English, German and French now use an ICU `select`: *today / this week / this month* for what is left, and *daily / weekly / monthly* for when it resets. No key was added or removed.\n\n### A menu reopens on the click right after a pick (#3404)\n\nShipping with the Kubernetes page, because it was what kept the end-to-end lane red: Radix keeps a closed dropdown menu mounted while it animates out, and during that window the old content is still a dismissable layer whose own trigger counts as \"outside\". Picking an item and clicking the trigger again inside the exit animation toggled the menu open and the layer's outside-dismiss closed it again — the click was lost, and nothing opened. The end-to-end language spec did it reliably fast and failed on two of three runs; a person does it whenever they are quick.\n\n`@tale/ui`'s shared `DropdownMenu` wrapper now ignores a left-button pointer-down that lands on the menu's own trigger, so the trigger owns that click. The regression test drives real Chromium with the exit animation pinned: uncontrolled and controlled menus reopen when clicked right after an item was picked, and a plain trigger click still closes an open menu.\n\n## Behaviour changes\n\n- Embedding requests to one organization's model share one in-flight bound per endpoint and model across every indexing job, website scan and search in a Tale process. Unset, that bound is **3** — the value each embedder used before, now shared instead of multiplied — so an organization that states nothing sends **fewer** concurrent requests than it did on 0.5.31, not more.\n- An embedding request that runs out of time is no longer retried. Connection refusals, rate limits and server errors still are, and a `Retry-After` longer than one minute ends the request instead of waiting it out.\n- When one batch of a multi-batch embedding call fails, the rest are cancelled or never sent, and the first error is the one reported.\n- An indexing job that reaches its 15-minute attempt budget, or whose worker is shutting down, now stops and cancels its request in flight. It records no `failed` status; the retry resumes after the slices already stored.\n- Without `minTokensPerSecond`, a single embedding request may now take up to 15 minutes (5 for a search query) instead of being cut at 60 seconds. This is the intended change — the flat minute was what abandoned work the server was about to finish — but a genuinely unreachable server is now noticed later than it was.\n- A file whose name and browser report give no MIME type is stored and sent as `application/octet-stream` rather than an empty string. Nothing is admitted that was not admitted before: that type is in no upload allowlist, and the text, RAG and size gates decide on the file extension.\n- A chat turn whose send is rejected clears its optimistic message and thinking shell instead of leaving them on screen.\n- The chat budget banner renders in the composer's column as an `Alert` with `role=\"alert\"`, and its period phrasing is selected per locale rather than interpolated.\n- A `@tale/ui` `DropdownMenu` reopens when its trigger is clicked during the menu's exit animation. Any consumer of the package inherits this; inside this repository only the platform renders the component.\n\n## API contract changes\n\n- **None.** The OpenAPI document stays at **1.14.0** with **80 paths, 127 operations and 58 schemas**, and the `Error.code` enum keeps its 150 values. No operation was added, removed or renamed, and no request or response shape changed — regenerating the specification from this commit reproduces the committed file and its contract fingerprint exactly.\n- The two new embedding settings are organization configuration in `embedding.json`, reached through the app's knowledge administration and the CLI's `knowledge-embedding` resource. They are not on the `/api/v1` surface.\n\n## Security\n\n- **No security advisory is fixed in this release**, and no dependency in this range carries one. Two packages are added: `p-queue` 9.3.0 as a platform dependency, and `p-timeout` 7.0.1 — which p-queue depends on — pinned through the root `overrides` because 7.0.2 is younger than Renovate's 90-day minimum release age.\n- **The MIME fallback widens nothing (#3403).** `application/octet-stream` belongs to no upload allowlist, and the text, RAG and size gates key on the file extension. What changed is that the stored row and the wire now carry the same value instead of disagreeing.\n- **The Kubernetes page turns off one fence and names its replacement (#3404).** `TALE_SKIP_SSRF_FIREWALL=1` is required there because the platform image's in-container iptables fence self-severs on a Pod network; the backend egress NetworkPolicy in `20-application.yaml` is what constrains backend egress instead, and the page says so rather than leaving the variable unexplained. The page also states, as a known limit, that the spawner's namespace allowance lets session Pods reach the stores' Service ports — credentials are still required, and a two-namespace layout is the recorded follow-up.\n- **An announced hard block (#3405).** The budget banner's exceeded state is the one in which sending is refused. It had no live region, so a screen-reader user met a disabled composer with no explanation; it is now a `role=\"alert\"` region whose copy also meets AA contrast.\n\n## Known issues\n\n- **The embedding pacing is proved against a controlled server, not a long soak.** The lane, the priority, the timeout formula, the retry rules and the abort behaviour are covered by 58 automated cases plus observed runs of the production Node runtime against a local HTTP server — two embedders sharing a limit of 2 never exceeding it at the server, an abandoned request's connection closing, a search overtaking queued batches, a `Retry-After: 2` honoured at 2,006 ms, and a failed batch stopping its siblings. What a real self-hosted model server does under a day of production load remains a deployment check.\n- **The bound is per Tale process, not per deployment.** The API and every worker replica can each reach `maxConcurrentRequests`. Size the value for one process and count your replicas; the timeout formula already allows for one more process with the same bound, and an operator who shares a server more widely than that should state a lower rate.\n- **`minTokensPerSecond` is a statement, not a measurement.** Nothing verifies it. Set it too high and requests are cut before the server finishes; too low and a genuinely dead server is noticed late. Its worked example on the data-residency page is the recommended starting point.\n- **The Kubernetes page's verified scope is one cluster.** A fresh single-node kind cluster on Kubernetes 1.36, kube-network-policies, the local-path StorageClass, and Tale 0.5.31 — every Pod ready, 104 migrations counted, both policies present, the edge at 200 with HTTP→HTTPS at 308, the full session lifecycle and an agent task with a deliverable. A managed distribution, a different CNI or a multi-node cluster will need its own run of the page's own verification commands, and the page lists the scope it was proved at.\n- **`config-data` needs RWX or a single node.** The shared configuration volume takes writes and locks from more than one role; the page says so, and a cluster without a suitable StorageClass cannot follow it as written.\n- **Tale still ships no Helm chart.** The page is manifests and `envsubst`, deliberately, and it is not an operator: the CLI's Docker rollout coordination does not run a Kubernetes deployment for you.\n- Unchanged from v0.5.31, where each is described in full: a managed deployment picks up the proxy *policy* added in that release only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in `TLS_MODE=letsencrypt` only; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut; both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; `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; 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.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0106** and the knowledge database is unchanged, so nothing runs at boot beyond the usual convergence check.\n- **No new environment variable.** `.env.example` and the environment reference are unchanged. `TALE_SKIP_SSRF_FIREWALL`, which the Kubernetes page uses, has shipped since long before this release; the page documents it, it is not new.\n- **No new configuration file**, but an organization's existing `embedding.json` accepts two more optional keys, `maxConcurrentRequests` and `minTokensPerSecond`. A file that states neither behaves as before, except that the default bound of 3 is now shared across the process rather than counted per embedder. Both follow the platform's preserve-or-clear rule: a value sets it, an omitted key leaves what the file holds, and `null` clears it.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change in this range, and neither does the managed proxy policy the CLI renders — unlike 0.5.31, nothing about the proxy needs a newly prepared bundle. A plain `tale deploy` is the whole upgrade: no `--stop`, no downtime window.\n- The **platform** image (the embedding lane, the job signal, the MIME fallback, the chat surface and budget banner) and the **docs** image (five pages in each of English, German and French — the new Kubernetes guide, the install index, Run Compose yourself, the CLI install page and data residency — plus the navigation and search index) carry source changes. **web** and **ui-docs** rebuild only because `@tale/ui` moved; neither renders a `DropdownMenu`, so nothing in them behaves differently. The `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change.\n- **The CLI has source changes in this range** (#3402's `knowledge-embedding` schema and convergence), so a managed deployment should move its pinned CLI reference as well as its platform reference. The release executables report 0.5.32.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.32` and `marketing-ui-v0.5.32` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.32\"`. `@tale/ui` carries the dropdown-menu trigger fix, so it is **not** content-identical to `ui-v0.5.31`; `@tale/marketing-ui` has no source change in this range and its tag is content-identical to its predecessor.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.31):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **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. Pin the CLI reference too: this range changes it. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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`.\n\n- **Running your own embedding server?** Nothing is required — an organization that states nothing keeps working, with a bound of 3 now shared across the process instead of multiplied by it. But if indexing has been failing on large documents, this is the release to state the two facts about that server in `embedding.json`: how many requests it can take at once, and the rate it sustains. _Pace requests to a self-hosted embedding server_, on the **Data residency and knowledge storage** page, works an example through.\n\n- **Deploying on Kubernetes?** The **Deploy on Kubernetes** page is new in this release, under Self-hosted › Install. Read its prerequisites before applying anything — a NetworkPolicy-enforcing CNI, a StorageClass, and RWX or a single node for `config-data` are requirements, not recommendations — and run its verification commands before admitting users.\n\n## What's Changed\n\n- fix(platform): pace embedding requests to the server's stated capacity by @yannickmonney in https://github.com/tale-project/tale/pull/3402\n- fix(platform): send a chat attachment the browser gave no MIME type by @larryro in https://github.com/tale-project/tale/pull/3403\n- docs(docs): add the Kubernetes deployment guide by @larryro in https://github.com/tale-project/tale/pull/3404\n- fix(platform): restyle the chat budget banner to the composer column by @larryro in https://github.com/tale-project/tale/pull/3405\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.31...v0.5.32","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.32","publishedAt":"2026-09-17T15:32:38Z"},{"tag":"v0.5.31","version":"0.5.31","name":null,"body":"**0.5.31 carries 17 merged pull requests.** Six of them do one thing: a managed agent turn against a model you host yourself now finishes. Six changes close the separate ways such a turn used to be re-sent, abandoned, un-cached or silently settled as \"done\". Beside that, chat gains an organization-wide **Audio transcription model** setting and stops warning about audio nobody asked it to transcribe, a UI evaluation round's findings are fixed across dialogs, settings and governance, two paginated lists stop hiding rows the caller can read, **Approve** can ask before it decides something outside the task, and an administrator can delegate the notification export without handing out an admin seat. No migration, no image in the stop-gated tier: the upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### A managed agent turn against a self-hosted model finishes (#3387, #3396, #3397, #3398, #3400, #3401)\n\nA Claude Code or Codex turn is run by a real CLI inside the sandbox, talking to Tale's LLM gateway. Both ends carry their own patience, their own idea of how big the model's context is, and their own retry rule — and none of them had been told what the other was doing. On a fast hosted model that never showed. On a model you host yourself, whose prefill can take minutes, every one of those mismatches turned into a turn that ran twice, ran forever, or ended as a success that changed nothing. Six changes close them, each with the measurement that found it.\n\n- **Two ends, one budget (#3387).** The `claude-code` harness set no `CLAUDE_STREAM_IDLE_TIMEOUT_MS`, so the pinned CLI gave up on a stream after its own 300-second default without a chunk and **sent the same request again** while the gateway — which waits `SANDBOX_LLM_GATEWAY_STREAM_IDLE_TIMEOUT_SECONDS`, 600 seconds by default — was still serving the first one. Any model whose prefill outlasts 300 seconds ran the turn twice, visible as two identical runs on the same task. `gatewayStreamIdleTimeoutSeconds()` is now the single reader of that budget, feeding both the gateway's provider config and the harness credential, and Codex gets the same number through `model_providers.tale.stream_idle_timeout_ms` — it runs its own idle watchdog and resends on the same rule. BYO-key runs keep the CLI defaults.\n\n- **The wait before the first byte (#3396).** An idle-stream budget covers the gap between chunks, not the gap before the first one, and a slow prefill sends nothing at all until its first token. Two further bounds apply there: Bun's own fetch timeout, about 300 seconds, which the CLI lifts only for `api.anthropic.com` or when `API_FORCE_IDLE_TIMEOUT` is explicitly false; and the SDK's request timeout `API_TIMEOUT_MS`, 600 seconds by default. Past either one the CLI re-sent the request while the gateway kept serving the abandoned copy — its server does not cancel upstream when the client leaves. Against a model that serves one request at a time the copies queue behind each other and each resend waits behind them: a livelock, not a slow turn. Seen live on two deployments — one turn's request re-sent at 16:20:48, 16:25:49, 16:30:51 and 16:35:56, the original answering 200 after 1,298 seconds to a client that had long gone. The managed env now sets `API_FORCE_IDLE_TIMEOUT=0` and ties `API_TIMEOUT_MS` to the gateway's budget, so the gateway is the single bound on a silent upstream. In a controlled run of the real CLI against an endpoint that stays silent for 420 seconds, the default env re-sent at t=303.7 s and never finished; with the fix, one request, answered at t=423 s, `result: success`.\n\n- **The gateway's own ceiling (#3397).** Raising the idle budget was not enough, because the gateway's per-request timeout was pinned at 600 seconds. Its streaming client has no read timeout, but when a stream ends before its first event Claude Code falls back to a **non-streaming** request that carries the whole prefill — and that one is bounded by the request timeout. On a deployment whose operator had raised the idle budget to 1800 seconds, a restarted model server produced a **504 after exactly 600,049 ms**. The gateway's request timeout is now `max(600, the stream idle budget)`, the turn gets it as `API_TIMEOUT_MS`, and a lowered idle budget still leaves the request timeout at its 600-second floor.\n\n- **Compacting inside the window the model actually serves (#3398).** A managed Claude Code turn never learned its model's context window. For a model it does not know, the CLI assumes 200,000 tokens and compacts only near 167,000 prompt tokens. On a self-hosted model whose catalog says 32,768, a production turn grew to about 140,000 tokens; its cold prefill then outlasted the CLI's own 30-minute stream watchdog — which the CLI caps there whatever the environment says — so the turn could never be answered and looped until it was cancelled. A new harness placeholder `${model.contextWindow}` resolves the turn model's effective window (the connector catalog's window, narrowed by the organization's context limit) exactly as the chat lane does, and the harness passes it as `CLAUDE_CODE_AUTO_COMPACT_WINDOW`, gated to windows below 200,000 so a Claude model's own larger window is never cut down. All five managed exec builds resolve it: automation start, answered-ask resume, task start including a fresh relaunch, and task steer restart. When the window is unknown the value is empty, which the CLI ignores; a turn never fails over it.\n\n- **A foreign model's prompt cache survives the turn (#3400).** The pinned CLI opens every system prompt with an attribution line whose checksum changes on every request. Anthropic's API reads that line; any other server sees plain text near the start of the prompt, so a local model's prefix cache ends right there. Measured on a split model behind a managed desk turn: every turn reused exactly the 16,276 tokens of tool definitions the chat template renders first and then prefilled the whole conversation again — 74 to 93 seconds per turn at about 28K tokens, extrapolating to roughly 15 minutes per turn near 100K. Diffing consecutive requests in the gateway's log store showed the only difference was that line, at character 79 of 85. For a model that is not Claude, the exec now also sets `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, in the same branch that already turns thinking off for such models. Claude models keep the line, and so does the subscription lane, which only serves Claude.\n\n- **A turn that answered nothing is a failure (#3401).** A serving cluster that fails mid-prefill can answer a model call with an empty 200 — stream open, `end_turn`, stream closed, zero usage. The CLI then ends the turn cleanly with no assistant message, and the classifier, which only failed on an error or a missing turn end, settled the node **`ok` with empty text**: the workflow went on as though the agent had deliberately changed nothing. This was observed live. A completed turn that got nothing from its model now fails with *\"The model returned an empty answer, so the agent did nothing this turn.\"* — \"nothing\" meaning no text, no timeline part and no output tokens. A turn that only called tools, only reasoned, or only reported tokens still counts as an answer. An automation node settles as a retryable `harness_error` so the stepper re-kicks it in place; past the deadline it still settles as `deadline`. The task lane keeps its retryable path and its conversation handle, and a resumed start whose conversation answered nothing is no longer mistaken for a dead handle. The same change fixes the usage ledger: a Claude Code turn's totals now come from the result's own `usage`, where a reasoning-only turn used to book zero.\n\n### Chat gets an audio transcription model, and stops warning about audio you never sent (#3399)\n\nServer-side transcription had no setting: it took whatever the organization's credentials happened to offer, and the composer carried a standing warning about audio configuration whether or not anyone had tried to send audio.\n\n**Settings › Governance › Models** now carries **Audio transcription model**, with **Automatic** — choose an available transcription model from the organization's active default provider credentials — or an exact provider and model pin. One resolver answers every caller: the settings status, the composer's capability read, server dictation, audio and video attachments, and a video link's audio fallback. An explicit pin that is unavailable reports an error and is **never** silently substituted; an unsupported audio upload is refused before the file is transferred. OpenRouter's dedicated speech-to-text catalog is discovered separately from its chat listing, so transcription models it omits from the default list — including entries with zero token context — are selectable, while pure speech-to-text entries stay out of chat and harness selection. Transcript caching is scoped to the serving provider, model, endpoint, response format and the file's bytes, so changing the target re-transcribes rather than answering from a stale entry. Existing completed attachments keep their transcripts.\n\nOrdinary chat is quiet again. Instead of the standing composer warning there is one shared recovery dialog, shown only after an attempted server transcription is actually refused — for dictation, for an audio or video attachment, and for a retry. It offers the settings action the reader is permitted to take, or tells them whom to ask. Known unavailability blocks microphone capture and media transfer before they begin, while ordinary files and the browser's own speech recognition — still the preferred dictation path — remain usable. Dismissing it preserves the draft and returns focus, and a late failure from another conversation or another organization cannot open or overwrite the dialog you are looking at.\n\nThe policy is organization-scoped file configuration seeded through the existing config catalog. An absent file, or `{}`, means Automatic. There is no SQL migration and no backfill.\n\n### The UI evaluation round's findings are fixed (#3399)\n\nThe same change closes a round of confirmed UI defects that left dialogs unresponsive, navigation and settings stale, governance actions incomplete, and task completion able to publish after cancellation.\n\n- **Menus and dialogs hand off cleanly.** A dropdown menu can hand off to a modal while its own exit animation is still mounted, and two modal layers left Radix's outside-pointer lock behind when the second overlay closed — a dialog that no longer answered the pointer. `@tale/ui`'s `DropdownMenu` now leaves modality to the dialog, and `DataTableActionMenu` takes a `triggerRef` so the toolbar button, not the menu item that vanished, is where focus returns. Keyboard access, notification destinations, navigation context, responsive layouts and accessible editor labels are restored alongside it, together with organization-access failures, managed organization-creation capabilities, and the return navigation after a forced password change.\n- **Actions that quietly did nothing now work**: bulk chat archive and delete, retained notification preferences, the team list's refresh, trash row identity, URL-backed usage filters, erasure approval actions, legal-hold release and refusal detail, contact import validation, indexed-page feedback, and the OAuth error page's way back into the app.\n- **Task completion and cancellation are fenced in one transaction** with consistent lock ordering, so a cancelled or superseded agent completion cannot publish comments, outputs or reviews. Remote effects that already completed before the cancellation are outside that guarantee.\n- **Automation authoring is server-validated**: saves and deployments run the same acceptance checks and test gates the server owns, editor metadata survives a save, the settings and trigger dialogs close on success, copied run JSON is the run's input, and a diagnostic trace that hits its cap no longer takes the functional output with it.\n- **From a goal is gone.** Goal-based automation creation and the private app builder flow behind it are retired; its route answers 404. Blank creation, package upload and shared MCP authoring are unchanged.\n\n### A list now pages the rows you can actually see (#3392, #3393)\n\nTwo doors carried the same defect in the same shape: read a page of rows, cut it at `LIMIT`, then drop the rows the caller may not read. A row the caller cannot see still spent a slot, so the page came back short — and when a whole page of them sat newer than the caller's own rows, it came back empty.\n\n- **Inbox (#3392).** A member whose conversations sat behind a page of unassigned, admin-triage rows saw an empty tab under a badge reading 2. An empty first page is terminal in the UI: there is no row to scroll, so nothing loads more, and the tab stayed blank however many matching rows sat behind it. The assignment predicate moves into the list query's `WHERE`, and one `resolveAssignmentScope` fragment is now interpolated by the list and both count doors, so the list and its badge cannot disagree again.\n- **REST documents (#3393).** `GET /v1/documents` now returns every hub document the caller can read. With `?limit=1` and one team-scoped document newer than the caller's own, the page came back empty while `continueCursor` pointed past it — and a client that stops on an empty page stops there. The page query carries the same `hubAccessClause` the in-app listing and hub search already run, the post-cut filter is gone, and the cursor is now the last row of the page the caller was actually given.\n\nNeither door ever returned a row the caller was not entitled to; both returned too few. Two bounded readers in the same documents file still filter after their cut and report an honest `truncated`, which is left as a separate decision.\n\n### Approve can ask before it decides something outside the task (#3385, #3394)\n\n**Approve** on an automation-owned task writes Done in one click. That fits most automations, but not one whose Done means something outside Tale — a relay, a filing, a message sent on the organization's behalf. During a test round a stray click attested an action nobody had taken.\n\nThe task subject contract gains `review.approve.confirm`: a sentence the automation declares, localised under `i18n` with the same fallback chain as every other declared text — exact tag, then base language, then the English sentence. When an automation declares it, **Approve** opens a confirmation showing exactly that sentence; without it, Approve stays a one-click close. An empty sentence, one over 500 characters, or a malformed locale tag is refused.\n\n#3394 is why it works on a real deployment. The task modal resolves its owning contract through `resolveTaskSubjectContract`, which rebuilt the resolved entry field by field and left `approveConfirmation` out — so a deployment whose automation declared the sentence, and whose listing returned it in all three languages, still closed the task on the first click. The narrowing now drops only the ownership tag, so every field the entry resolves reaches the task, including any field added later.\n\n### Delegate the notification export without handing out an admin seat (#3388)\n\nA worker that only needs to read notifications was refused `GET /api/v1/notifications/sync` with `403 ROLE_FORBIDDEN` on a Developer seat, and the only way to unblock it was promotion to **admin** — which also hands over member management, SSO and SCIM administration, and password resets.\n\nAn administrator can now delegate that one right through the existing competence register, with no new table and no migration: grants and revocations use the current governance routes. `tale:` is a reserved namespace holding a closed set of platform capabilities; a grant naming any other `tale:` slug, in any casing, is refused with `400 COMPETENCE_CAPABILITY_UNKNOWN` and audited as `competence_grant_denied`. Removing a membership stamps that member's live `tale:` grants revoked, so a re-added member starts without the right. The endpoint passes an owner or administrator by role, or a member holding one live, unexpired `tale:notifications.export` grant and an enabled seat — checked before the query is parsed or any member row is read — and `GET /api/v1/me` answers `capabilities.notificationExport` so a client can tell before it polls.\n\n### A managed deploy says why it failed, and checks packs before it pulls images (#3395)\n\nA managed deploy ran silently for 38 minutes and then printed only *\"Deployment failed. Review the source pins, paths, permissions and retained recovery receipts.\"* The cause was a pinned CLI older than the packs it was deploying: its embedded manifest schema stripped a field the pack declared, the release builder correctly refused the pack — and `deploy` replaced that authored refusal with its own fixed line. The 38 minutes were the runtime image pull, which ran *before* the configuration was ever validated.\n\nAll three parts are fixed. One failure rule now covers every configuration and deployment command: deliberate, bounded errors keep their words, while anything unexpected still collapses to the caller's fixed summary so no credential, registry or Docker output reaches a log — `deploy` had a stricter private copy of that rule, and that copy is what hid the cause. The refusal names the fields it could not read (`…normalization changes release semantics at subjects.task.review.approve; this Tale CLI does not read those fields as written, so use a Tale CLI at least as new as the Tale the pack targets`), listing paths only, never values, capped at five. And preparation now checks configurations before it pulls the runtime, logging each phase and image as it goes, so a pack this CLI cannot read fails within seconds instead of after the pull.\n\n### The first managed deploy after a snapshot no longer fails (#3389)\n\nEvery **first** managed deploy after a snapshot failed with *\"Docker refused managed Compose startup\"*, while the rerun — which skips the snapshot — passed. A snapshot pauses the containers using each volume; Docker marks a paused container unhealthy, and unpausing keeps that status until the next probe. The deploy read the stale status as a runtime change and ran `docker compose up`, which refused immediately because a dependency read unhealthy.\n\nThe snapshot now reads each container's health *before* pausing and, after unpausing, waits for every container that was healthy or still starting to report healthy again — each with its own window of `retries × (interval + timeout) + 5 s` using Docker's defaults for unset settings, which is as long as Docker itself would take to call it unhealthy. A container that was healthy and does not recover fails the call by name and health status; one that was already unhealthy or has no health check is not awaited. Stale runtime health is now waited out rather than handed to Compose, the proxy is paused once for both of its snapshot volumes instead of twice, and a managed-Compose failure names the unhealthy services instead of the generic refusal. The config-volume migration gets the same guarantee.\n\n## Behaviour changes\n\n- A managed Claude Code turn now carries `CLAUDE_STREAM_IDLE_TIMEOUT_MS`, `API_FORCE_IDLE_TIMEOUT=0`, `API_TIMEOUT_MS`, `CLAUDE_CODE_AUTO_COMPACT_WINDOW` (only when the model's effective window is below 200,000) and, for a model that is not Claude, `CLAUDE_CODE_ATTRIBUTION_HEADER=0`. A managed Codex turn carries `model_providers.tale.stream_idle_timeout_ms`. BYO-key runs keep each CLI's own defaults.\n- The gateway's per-request timeout is `max(600, SANDBOX_LLM_GATEWAY_STREAM_IDLE_TIMEOUT_SECONDS)` rather than a fixed 600 seconds. Raising the idle budget therefore also lengthens how long a stalled non-streaming answer is held before it is cut.\n- An agent turn whose model answered nothing settles as a retryable failure instead of a success. An automation node re-kicks in place; past its deadline it settles as `deadline`.\n- A Claude Code turn's usage totals are read from the result's `usage`, so a reasoning-only turn is no longer booked as zero tokens.\n- Chat shows no audio setup warning until a server transcription is actually refused. An explicit transcription pin that is unavailable errors rather than falling back to another model.\n- **Approve** on an automation-owned task opens a confirmation when the deployed automation declares `subjects.task.review.approve.confirm`, and stays a one-click close otherwise.\n- Goal-based automation creation is removed; its route answers 404. Blank creation, package upload and MCP authoring are unchanged.\n- The Inbox list and `GET /api/v1/documents` page over rows the caller can read, so a page that used to come back short or empty now carries them. The cursor of a documents page is the last row of that page.\n- A member holding a live `tale:notifications.export` grant passes `GET /api/v1/notifications/sync` without an admin seat.\n- The managed proxy answers `GET /api/app/organizations/capabilities` with `{\"canCreate\": false}`, so the app hides organization creation and points at the operator instead of failing the attempt.\n\n## API contract changes\n\n- The OpenAPI document moves **1.13.0 → 1.14.0**, one additive step. The surface is unchanged at **80 paths, 127 operations and 58 schemas**, and the `Error.code` enum keeps its 150 values.\n- `GET /api/v1/me` gains `capabilities.notificationExport`: true when the key holder may export members' notifications, by role or through a live `tale:notifications.export` grant.\n- `GET /api/v1/notifications/sync` documents the delegated path; its 403 keeps `ROLE_FORBIDDEN` and now names the capability. `COMPETENCE_CAPABILITY_UNKNOWN` is on the app's governance route, not this surface.\n- No operation was added, removed or renamed, and no existing response shape changed.\n\n## Security\n\n- **hono 4.12.34 → 4.13.5** ([CVE-2026-84363](https://nvd.nist.gov/vuln/detail/CVE-2026-84363), #3300). Hono's query parsing did not stop at a URL fragment, so a `?` after a `#` was read as a query string. Every other consumer of a URL — browsers, `new URL()`, reverse proxies — ignores everything from the first `#`, which makes this an interpretation differential: anything in front of the application that inspects the query string sees no parameters while the application reads and acts on them. Hono is the platform backend's HTTP framework, so this one ships in the running product.\n- **sharp 0.35.3 → 0.35.4** ([GHSA-rgj7-g3m4-5g8c](https://github.com/advisories/GHSA-rgj7-g3m4-5g8c), #3298), which picks up fixes for libheif vulnerabilities, two rated critical, that can lead to remote code execution on glibc-based Linux under certain conditions. In this repository `sharp` is a **development** dependency only — image optimisation for the marketing site and the documentation screenshot pipeline — so no running Tale service decodes user-supplied images with it.\n- **vitest 4.1.10 → 4.1.11** (#3299), a path-traversal / arbitrary-file-read fix in `@vitest/mocker`. Test tooling only; it is in no shipped image.\n- **Least privilege for the notification export (#3388).** The previous answer to a refused export was to promote the caller to administrator, which also granted member management, SSO and SCIM administration and password resets. The delegated capability grants exactly the one right, is organization-scoped, optionally expiring, revocable, and revoked when the membership ends; the `tale:` namespace is closed, so an unknown slug cannot be granted by typo and the attempt is audited.\n- **An approval that decides more than the task now says so (#3385, #3394).** A one-click Approve on an automation whose Done relays a decision outside Tale is a real hazard — a stray click attested an action nobody had taken during a test round. The confirmation is declared by the automation, so it can only appear where the automation's author said it should.\n- **A cancelled agent run cannot publish (#3399).** Task completion and cancellation are fenced in one transaction with consistent lock ordering, so a superseded or cancelled run cannot write comments, outputs or reviews after the fact.\n- **Not a disclosure.** The two list fixes (#3392, #3393) made short pages whole; neither ever returned a row the caller was not entitled to read. The scoping rule is unchanged — only the place it is applied.\n\n## Known issues\n\n- **A managed deployment needs a new bundle for the organization-creation capability.** The proxy *image* does not change in this release, but the managed proxy *policy* the CLI renders does: it gains the `GET /api/app/organizations/capabilities` response that lets the app hide organization creation. A managed deployment gains it only when a newly prepared bundle is applied; updating the platform image alone leaves the retained proxy policy as it was. Self-hosted and workspace deployments get the answer from the platform backend and need nothing.\n- **The transcription setting is only as good as the organization's credentials.** Automatic selects from the active default provider credentials that offer a compatible model; with none configured, the capability read answers `NO_TRANSCRIPTION_MODEL` and server dictation is unavailable. Browser speech recognition and published video captions keep working regardless. The OpenRouter smoke test covered a 1.78-second recording, which says nothing about long-recording limits.\n- **The agent-turn fixes are bounded by the CLI they were read from.** The values, the gates and the env variable names come from the pinned Claude Code build; a future pin can change them, and each of the six is proved by fixtures and one or two controlled runs rather than by a long soak on a slow model.\n- **The 0.5.29 proxy change is exercised live in one TLS mode only.** The hosted fleet proved the rendered `trusted_proxies` block and the removal of the `X-Forwarded-Proto {scheme}` pins in `TLS_MODE=letsencrypt`. No deployment on `TLS_MODE=external` has exercised them; an operator there should still verify sign-in callbacks, secure cookies, uploads and streaming through the full path.\n- **The web tier's backend-URL default lives in the image, not in the generated compose** (0.5.30). A workspace deployed with the CLI behaves correctly once its platform container runs a 0.5.30-or-later image, but the compose file the CLI writes still names no `TALE_BACKEND_URL`. Any deployment still on an older platform image needs the explicit variable.\n- **The scheduled-pack fix does not reach an existing install** (#3381, 0.5.29). Provisioning skips an automation an organization already has, so an upgraded instance keeps the version whose input schema refuses its own scheduler. Edit that automation's `inputs` to admit `trigger` and `firedAt` and deploy a new version; a new organization is seeded correctly.\n- **A budget hold covers a turn's first round.** A turn that calls tools runs up to five model rounds, each billing its full prompt again, and only the first round's worst case is held while it runs. Concurrent sends can no longer each pass a cap with room for one, but a long multi-round turn can still settle above the cap it was admitted under.\n- **A run still carries no usage or cost.** #3401 fixes the Claude Code turn totals that fed the usage ledger; it does not add a `usage` block to `GET …/runs/{runId}`, which remains contract debt with its design recorded.\n- **Nothing backfills a task timeline** (#3379, 0.5.29). Edits made before that release wrote audit rows only and do not appear; a label deleted from the catalog renders as its raw id rather than dropping the row.\n- 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.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- Two bounded document readers still filter after their cut (#3393's scope note); both report an honest `truncated`, so a caller can tell the answer was cut.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; `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; 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.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0106** and the knowledge database is unchanged, so nothing runs at boot beyond the usual convergence check.\n- **No new environment variable.** `SANDBOX_LLM_GATEWAY_STREAM_IDLE_TIMEOUT_SECONDS` is not new — it has shipped since 0.2.93 — but this release documents it in `.env.example` and the environment reference for the first time, and widens what it does: above 600 it now also raises the gateway's per-request timeout, which bounds a whole non-streaming answer. Recreate the backend services after changing it.\n- **One new configuration file.** `governance/transcription-model.yml` joins the seeded per-organization catalog. An absent file, or `{}`, means Automatic, so an upgraded organization needs nothing; a pin is two fields, `providerSlug` and `modelId`. The shipped `claude-code` and `codex` harness definitions change too; those are system configuration and travel inside the platform image.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change in this range and the object store runs its pinned third-party image, so a plain `tale deploy` is the whole upgrade — no `--stop`, no downtime window. The managed proxy *policy* does change (see Known issues): a managed deployment picks it up with its next prepared bundle.\n- The **platform** image (all six agent-turn changes, the transcription lane, the UI round, the two list fixes, the approval confirmation, the delegated capability, hono) and the **docs** image (15 pages in each of en, de and fr — 45 files — plus a regenerated Models screenshot) carry source changes; **ui-docs** gains the `DataTable` guide's `triggerRef` paragraph, and **web** only a development dependency. The `db`, `proxy`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images carry no source change — the gateway's README moved, its binary did not.\n- **The CLI has source changes in this range** (#3389, #3395, and the managed proxy policy), so a managed deployment should move its pinned CLI reference as well as its platform reference. The release executables report 0.5.31.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.31` and `marketing-ui-v0.5.31` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.31\"`. Unlike the 0.5.30 tags, these are **not** content-identical to their predecessors: `@tale/ui` carries the dropdown-menu modality change and `DataTableActionMenu`'s `triggerRef`, and a consumer that relies on the menu owning pointer modality should re-check its own dialogs.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.30):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **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. Pin the CLI reference too: this range changes it, and #3395 means a CLI older than the packs it deploys now says so within seconds instead of after the image pull. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n- **Running agents against a self-hosted model?** Nothing is required, but two knobs are worth a look. `SANDBOX_LLM_GATEWAY_STREAM_IDLE_TIMEOUT_SECONDS` is now the single patience budget both the gateway and the agent CLI obey, so raise it to cover your model's worst cold prefill rather than leaving a turn to be re-sent. And make sure the connector catalog states your model's real context window: `CLAUDE_CODE_AUTO_COMPACT_WINDOW` is derived from it, and an unknown window leaves the CLI assuming 200,000.\n\n## What's Changed\n\n- fix(cli): wait for paused containers to report healthy again by @yannickmonney in https://github.com/tale-project/tale/pull/3389\n- fix(platform): stop Claude Code and Codex re-sending a turn on a slow prefill by @yannickmonney in https://github.com/tale-project/tale/pull/3387\n- feat(platform): ask before an approval the automation says decides more by @yannickmonney in https://github.com/tale-project/tale/pull/3385\n- feat(platform): delegate the notification export as a capability by @yannickmonney in https://github.com/tale-project/tale/pull/3388\n- fix(platform): scope the inbox list in SQL, not after the page by @Israeltheminer in https://github.com/tale-project/tale/pull/3392\n- fix(platform): scope the REST documents page in SQL, not after the cut by @Israeltheminer in https://github.com/tale-project/tale/pull/3393\n- fix(platform): carry the approve confirmation to the task modal by @yannickmonney in https://github.com/tale-project/tale/pull/3394\n- fix(cli): say why a deploy failed, and check packs before images by @yannickmonney in https://github.com/tale-project/tale/pull/3395\n- fix(platform): stop Claude Code resending a request before its first byte by @yannickmonney in https://github.com/tale-project/tale/pull/3396\n- fix(platform): let the gateway wait a raised budget for a whole answer by @yannickmonney in https://github.com/tale-project/tale/pull/3397\n- fix(platform): let Claude Code compact inside the serving model's window by @yannickmonney in https://github.com/tale-project/tale/pull/3398\n- fix(platform): keep a foreign model's prompt cache across Claude Code turns by @yannickmonney in https://github.com/tale-project/tale/pull/3400\n- fix(platform): fail an agent turn whose model answered nothing by @yannickmonney in https://github.com/tale-project/tale/pull/3401\n- fix(platform): resolve UI findings and add audio model settings by @larryro in https://github.com/tale-project/tale/pull/3399\n- chore(deps): update dependency sharp to v0.35.4 [security] by @renovate in https://github.com/tale-project/tale/pull/3298\n- chore(deps): update dependency vitest to v4.1.11 [security] by @renovate in https://github.com/tale-project/tale/pull/3299\n- fix(deps): update dependency hono to v4.13.5 [security] by @renovate in https://github.com/tale-project/tale/pull/3300\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.30...v0.5.31","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.31","publishedAt":"2026-09-17T08:05:42Z"},{"tag":"v0.5.30","version":"0.5.30","name":null,"body":"**0.5.30 is a two-change patch on the 0.5 line**, and both changes are for a deployment an operator assembled themselves: a healthy stack no longer reports **Service outage** on its public status page, and the Own Compose guide finally lists the egress proxy's real capability set and the IPv6 sysctls it has needed since 0.5.18. Nothing changes shape — no migration, no contract change, no configuration file, no image in the stop-gated tier. The upgrade is `tale update` followed by a plain `tale deploy`.\n\n## Highlights\n\n### A healthy stack no longer reports \"Service outage\" (#3390)\n\nThe web tier reads `TALE_BACKEND_URL` to find the application backend: the public `/status` page probes its liveness route, and the web server asks it for the verdicts only a database can answer. Both fell back to `http://127.0.0.1:3005` — the loopback address `bun dev` uses on a developer's machine. Nothing listens on loopback inside the platform container, so a deployment whose compose file left the variable unset showed **Service outage** on `/status` and `backend: outage` in `/status.json`, with the database and object-store rows following the same failed probe, while `/api/health`, the login page and every API door answered normally. The page that exists to tell your users the service is fine was the only thing claiming it was not.\n\nThe shipped `compose.yml` sets the variable and a managed deployment ships that file, which is why the hosted instances never showed it. Two paths did, and both were seen live this week: an install assembled from the **Own Compose** guide, whose `platform` example carried only `env_file`, and a **CLI workspace**, whose generated colour compose does not set it either — confirmed on a workspace freshly upgraded to 0.5.29, answering `outage` while its `/api/health` reported 0.5.29 and its REST door answered the contract.\n\nThe platform image's entrypoint now defaults `TALE_BACKEND_URL` to `http://backend-api:3005` for the web role, exactly as it has always defaulted `SANDBOX_URL`. The fix therefore travels with the image and asks nothing of the operator's compose file. An explicit value still wins, so a backend service under another name is unaffected, and the `api` and `worker` roles — which exec before the entrypoint sources that script — never read it. Outside a container, `bun dev` and `vite preview` keep the loopback default.\n\nThe variable is also documented for the first time: the environment reference's deployment-topology table, the Own Compose service table and `.env.example`, in English, German and French. A deployment that cannot move to this release yet sets `TALE_BACKEND_URL: http://backend-api:3005` on its `platform` service — or in the workspace `.env` — and recreates that container.\n\n### The Own Compose guide states the egress fence's real requirements (#3391)\n\n\"Run Compose yourself\" is the page for assembling the stack by hand, and its capability table for `sandbox-egress` had frozen at the pre-0.5.22 set, with the IPv6 sysctls never mentioned at all. An operator building the service from that table got one of two failures, neither of which the page explained:\n\n- **A proxy that cannot stop.** Without `KILL`, the root supervisor cannot signal tinyproxy once it has dropped to `nobody`, so every `docker stop` waits out the grace period and ends in exit 137 instead of draining. `KILL` has been in the shipped capability set since 0.5.22, and those release notes told own-compose operators about it.\n- **A proxy that refuses to start.** The egress firewall fails closed: it needs working IPv6 netfilter, or IPv6 disabled for the default and for every interface in the container's network namespace, and it exits rather than serving traffic it cannot fence. A container cannot write those sysctls itself through a read-only `/proc/sys`, so on a kernel without the `ip6_tables` module the service never comes up. The two `disable_ipv6` sysctls have been in the shipped stack since 0.5.18 and were never announced.\n\nThe capability row now carries the full set including `KILL` with its reason, and a new **Egress IPv6** row carries both sysctls, the reason and a link to the reference's Sandbox infrastructure section, which already explains the same requirement for Kubernetes. The shipped `compose.yml` and the CLI's generator have carried both all along; only the guide lagged. English, German and French.\n\n## Behaviour changes\n\n- The platform container's entrypoint defaults `TALE_BACKEND_URL` to `http://backend-api:3005` for the `web` role. An explicit value from compose or `.env` still wins; the `api` and `worker` roles never read it; `bun dev` and `vite preview` keep the loopback default.\n- Nothing else changes at runtime. The rest of the range is documentation, and the two code edits beside the entrypoint line are comments.\n\n## API contract changes\n\nNone. The OpenAPI document stays at **1.13.0**, unchanged at **80 paths, 127 operations and 58 schemas**, and the `Error.code` enum keeps its 150 values.\n\n## Security\n\n- **The sandbox's network fence is unchanged; the page that tells an operator to build it is not.** Nothing about the egress proxy moved in this release — the shipped stack and the CLI generator already carry the sysctls and the least-privilege capability set. But an operator whose hand-built proxy would not start had no documented cause to reach for, and the nearest guesses — a wider capability set, dropping the `cap_drop: ALL` line, leaving the egress service out of the stack — all weaken the only outbound boundary the sandbox runtime has. The guide now names the requirement and the reason. The fence itself behaves correctly under the failure: it refuses to start rather than passing traffic it cannot filter.\n\n## Known issues\n\n- **The 0.5.29 proxy change is now exercised live, but only in one TLS mode.** The hosted fleet moved to 0.5.29 after those notes were written, so the rendered `trusted_proxies` block and the removal of the `X-Forwarded-Proto {scheme}` pins are proved on a running deployment in `TLS_MODE=letsencrypt`. No deployment on `TLS_MODE=external` has exercised them; an operator there should still verify sign-in callbacks, secure cookies, uploads and streaming through the full path.\n- **The web tier's new default lives in the image, not in the generated compose.** A workspace deployed with the CLI gets the correct behaviour once its platform container runs a 0.5.30 image, but the compose file the CLI writes still names no `TALE_BACKEND_URL`, so the value is invisible to an operator reading that file. Any deployment still on an older platform image needs the explicit variable.\n- **The scheduled-pack fix does not reach an existing install** (#3381, 0.5.29). Provisioning skips an automation an organization already has, so an upgraded instance keeps the version whose input schema refuses its own scheduler. Edit that automation's `inputs` to admit `trigger` and `firedAt` and deploy a new version; a new organization is seeded correctly.\n- **A budget hold covers a turn's first round.** A turn that calls tools runs up to five model rounds, each billing its full prompt again, and only the first round's worst case is held while it runs. Concurrent sends can no longer each pass a cap with room for one, but a long multi-round turn can still settle above the cap it was admitted under.\n- **Nothing backfills a task timeline** (#3379, 0.5.29). Edits made before that release wrote audit rows only and do not appear; a label deleted from the catalog renders as its raw id rather than dropping the row.\n- 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.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page. This is the edge proxy, not the sandbox egress fence #3391 documents.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; `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; 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.\n\n## Migration notes\n\n- **No migration.** The application database stays at **0106** and the knowledge database is unchanged, so nothing runs at boot beyond the usual convergence check.\n- **No new environment variable.** `TALE_BACKEND_URL` is not new — the shipped compose has always set it. This release gives it a default inside the image and documents it for the first time. No configuration file changes shape, and the shipped automation catalog is unchanged.\n- **No image in the stop-gated tier changes.** The `proxy` and `db` images carry no source change in this range and the object store runs its pinned third-party image, so a plain `tale deploy` is the whole upgrade — no `--stop`, no downtime window.\n- The **platform** image (the entrypoint's new default, and two code comments) and the **docs** image (the en, de and fr Own Compose and environment-reference pages) 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 one file it gained is a guard test — so a managed deployment's pinned CLI moves only for the version stamp; the release executables report 0.5.30.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.30` and `marketing-ui-v0.5.30` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.30\"`. Neither package changed in this range, so the content is identical to the `…-v0.5.29` tags.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.29):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  Nothing in this release needs `--stop`. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: its `proxy` image change is only applied by a `--stop` deploy.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(platform): default the web tier's backend URL to the compose alias by @larryro in https://github.com/tale-project/tale/pull/3390\n- docs(docs): list the egress proxy's KILL capability and IPv6 sysctls by @larryro in https://github.com/tale-project/tale/pull/3391\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.29...v0.5.30","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.30","publishedAt":"2026-09-16T09:14:14Z"},{"tag":"v0.5.29","version":"0.5.29","name":null,"body":"**0.5.29 is a patch tag on the 0.5 line that carries far more than its fixes — read it as a minor.** Twenty-three changes since 0.5.28, and three of them undo product that was quietly broken: budget caps were documented but no server path enforced them, no scheduled automation had started on any install since 0.5.17, and IMAP mail ingest threw on every message. It ships one forward-only migration on the application database, an API contract that moves from 1.12.0 to 1.13.0, one new environment variable, a changed shipped automation catalog — and **a changed `proxy` image**, so the upgrade is `tale update` followed by `tale deploy --stop`.\n\n## Highlights\n\n### Budget caps now refuse work, and members can see their own (#3372, #3373, #3375)\n\nPolicies & Limits let an admin cap tokens, cost and requests per member, team, role and API key, and the documentation said those caps applied to chat. They did not. The only thing stopping a chat turn was the composer's send block in the browser: the REST send, regenerate, edit, both arena columns and a parked send all went through, and a cap could be overshot N-fold by N sends racing it, because a turn books its usage only once it settles.\n\nEvery chat lane now measures the sender on the server before the turn spends anything. A reached cap refuses the send — 429 in the app with the cap named, 429 `BUDGET_EXCEEDED` over REST, `budget_exceeded` on the reply for a deferred or REST turn that was accepted before the cap closed. And a live turn no longer spends invisibly: it holds its first round's worst case — the assembled prompt plus the output reserve, at the model's catalog rates — on its own row for as long as it runs, and every admission of the organization queues behind one lock and reads the holds the previous one wrote. That is what migration 0106 adds.\n\nThe caps also measure what they claimed to. Team shared caps count the usage of the team's **current members**, read through membership, instead of a ledger column that chat, tool and agent rows never carried. API-key caps are enforced and metered for the first time — a keyed turn books its key. Role caps now also bind voice output and video-link ingest.\n\nBeside the enforcement, members get an answer to \"how much have I used\". **Settings › Usage** is a personal page every role can open: each personal token, cost and request cap with a used-of-limit meter and its reset time, the team and organization caps measured against everyone's combined usage, and uploaded storage against the upload policy. It answers from the same gate that would refuse the next request, so what it shows is what will happen.\n\nAnd **Request usage credits** now reaches the people who can grant it. It used to write an organization-wide `system` notification, so the whole company's bell announced that a named member had hit their limit while the banner told that member the request went \"to your admins\". Each owner and admin now gets a personal row and email; a repeated click rewrites the unread row instead of stacking another; and when nobody else can grant credits the button no longer claims it asked.\n\n### Scheduled automations start again (#3381)\n\nEvery shipped pack that ships a schedule has been unable to start since **0.5.17** — on any install, on every release since. The scheduler starts a run with the trigger wrapper (`{trigger: 'schedule', firedAt}`), which the run gate validates against the deployed version's `inputs` schema, and all eight packs declared `additionalProperties: false` without `trigger` or `firedAt`. The refusal landed before a run row existed, so there was nothing in the run list to see. The eight packs now declare the wrapper, a guard test fails any future pack that closes its inputs against it, and the trigger documentation no longer shows `firedAt` as a string when the scheduler sends epoch milliseconds.\n\nRead the upgrade note: this fixes what a **new** organization is seeded with. Provisioning skips an automation an organization already has, so an existing install keeps its refused version until someone edits that automation's input schema and deploys a new version.\n\n### IMAP mail ingest works again (#3382)\n\n`imap-smtp.get_message` threw on every message in every mailbox, so a self-hosted instance reading mail over IMAP imported nothing. The root `overrides` forced `linkify-it` 6 on the tree; `mailparser` pins 5.x and calls it as a function, and version 6 is a dual build that resolves under `require` to a namespace object, not a callable — so `mailparser` threw while still loading. Sending was unaffected, which is what hid it: the product looked healthy and the scheduled sync reported `success` on every pass, with only `output.inbound.reason` naming the failure.\n\nThe override was a deliberate ReDoS remediation, not a stray pin, so it is not simply deleted: `mailparser` moves to 3.9.28, which pins `linkify-it` 5.0.2 — patched for both advisories — and the override comes off with nothing left to protect. A runtime test now imports `mailparser`, `imapflow` and `nodemailer` through the resolver production uses and parses a message end to end, because importing the entry alone would not have caught this.\n\nAn instance needs both this and the pack fix above before a scheduled mailbox sync does anything.\n\n### One documentation frame behind both sites (#3370, #3371)\n\ndocs.tale.dev and [ui.tale.dev](https://ui.tale.dev) each carried a fork of the same chrome, and the forks had drifted: the design-system site hand-rolled its outline, its \"Open in\" menu and its edit link, put the page title in the trail, and had no footer. The chrome is now one `@tale/ui/docs/*` family — rail, phone drawer, header strip, article, outline, neighbour cards, edit link, page actions, footer, back-to-top, 404 and the ⌘K static-index search — and a site feeds it only its navigation, search index and copy.\n\nReaders of the design-system guide get the product docs' shape: the `h1` in the article, the page actions in the header strip, a footer carrying the theme switcher, the repository link and the llms indexes, and a 404 with suggestions inside the frame. Both sites gain three fixes the shared frame brings: the header strip is 52px, ending on the rail's logo-row line instead of one pixel below it; every frame link matches its route exactly, so the logo and the first page's Previous card no longer claim `aria-current=\"page\"` on a locale home; and the chrome is hidden in print. Both sites also now declare the same theme behaviour — with nothing saved the page follows the operating system, and the footer switch saves Light, Dark or System.\n\n### One deployment on several origins (#3384)\n\nA managed deployment can now answer on more than one HTTPS origin as fully respected origins, including behind an external TLS terminator, rather than redirecting to a primary name.\n\n- The proxy trusts that terminator. In `TLS_MODE=external` another proxy terminated TLS and Tale's Caddy serves plain HTTP, so the browser's scheme arrives only as the terminator's `X-Forwarded-Proto` — which Caddy honours only from a peer it trusts. Trusting nobody, every lane forwarded `http`. The entrypoint now renders `trusted_proxies` from the new `TRUSTED_PROXIES` variable (CIDR ranges, or `private_ranges` by default) in strict mode, and the explicit `X-Forwarded-Proto {scheme}` pins are gone. Every other TLS mode terminates in this Caddy and trusts no peer, as before.\n- Browser file links are signed for the origin the browser is on, when the published object-store endpoint is itself one of the deployment's site origins. Background work, which has no browser, keeps the configured endpoint.\n- The managed specification gains `additionalOrigins` (literal origins or environment references), written to the runtime's `ADDITIONAL_SITE_URLS`: managed like every other key, compared by adoption and ready-state checks, and refused at preparation time against a runtime whose proxy image is too old to carry the trust placeholder. Native identity, the OIDC issuer, passkey rpID and email links stay on the primary origin.\n\n### Catalog records: one view, one edit, one delete (#3369, #3357)\n\nProducts, contacts, knowledge entries and websites each had their own way to show, edit and delete a record — four dialog widths from 384 to 1100px, some with no minimum height, and different delete wording. All four now share one 448px entity dialog with a floor of `min(34rem, 90dvh)`, one details layout (image or icon tile, summary and badges, a facts grid ending in a copyable id, then record-specific sections), one row menu reading **View · Edit · record actions · Delete**, and one **Delete {name}?** confirmation. Edit morphs inside the same card instead of closing the view to open a second overlay, and **Cancel** brings the details back.\n\nFocus comes back to the row's menu button whenever a dialog closes, including after Edit → Cancel — rows cannot be focused with the keyboard, so that menu is how a keyboard user opens a record at all. Two long-standing defects fall out: **Edit** inside contact details never opened (closing the details unmounted the edit dialog nested in them), and knowledge-entry version history never appeared (the adapter returned a bare array where the contract expects `{entry, versions}`).\n\nWebsite scan failures stop dumping sandbox JSON and `getaddrinfo` into the list — status stays a badge, the reason is a muted caption, the dump is on hover — and a scan that indexed nothing reads as a teaching empty instead of a hollow `0 words` row.\n\n### An opt-in confidentiality notice, configurable at last (#3366)\n\nThe line under the chat composer was both invisible and unconfigurable: a freshly scaffolded organization gets `enabled: false`, nothing in the app could change it, and only organizations **without** the policy file showed a notice — a missing file read as \"on\". It is now opt-in everywhere (missing file, disabled policy, loading read and failed read all show none) and editable under **Settings › Governance › Policies & limits**: an instant switch and the notice text per shipped language, edited in the shared locale tabs with an *untranslated* pill and a 280-character cap per language. Saving keeps what the editor does not show, including regional texts.\n\nTurning it on no longer pushes the composer up a quarter-second after the page becomes usable: the chat loader warms the policy, the pre-hydration script reserves the row from the remembered text, and the live footer holds that row until the read settles. The notice also moves to the caption tier, which it was already styled as but did not render as.\n\n### Also in this release\n\n- **A document is prepared once, not once per slice** (#3368). Indexing a large document re-ran the whole-document preparation — secret scan, PII policy, chunker, content hash, repeat map — for every slice, making it O(slices²), and the synchronous PII pass held the worker's event loop, stalling every other job on it. A 2 MB document with PII detection on drops from 9.8 s to 0.56 s; 600 KB drops from 1.06 s to 0.17 s.\n- **The queued-send tray settles when its turn starts** (#3367). A send parked behind attachment processing kept its **Queued** row and left its own bubble out of the transcript for the whole generation — the reply streamed under an empty gap. Both now flip the moment the turn opens.\n- **The chat list splits between Projects and Chats** (#3383). Projects takes the height its folders need up to half the panel, Chats takes the rest, each scrolls under its own header, and a chat dropped on the room under a short list lands in Chats — previously drag-and-drop measured a folder scrolled out of view and filed the chat into a hidden project.\n- **Provider changes apply without a page reload** (#3363). Enabling the first key left the agent **Model** picker saying \"No models found\" and the providers page saying there was no usable credential, while the backend already answered six models: those reads were cached outside the `provider_credential` hint entity. All of them now key under it, so **Refresh catalogs** and a model-serving policy save reach them too.\n- **Document sources read as icons** (#3386). The **Source** column spelled its source out, so a German \"OneDrive (synchronisiert)\" wrapped onto two lines into the RAG status; Google Drive showed a bare logo that never said whether it synced; and the preview sidebar printed internal slugs like `google_drive` for anything outside its own three-provider map. One provenance map now feeds both, as a vendor logo plus a small glyph, with the words as the tooltip and the accessible name.\n- **Task field edits appear on the timeline** (#3379). Title, description, priority, labels, attachments, start and due date and reviewer each write their own event, with label ids resolved to catalog names. Nothing is backfilled: tasks edited before this stay as sparse as they are.\n- **Every unknown route gets the app's not-found page** (#3362). A typo outside the dashboard rendered the router's bare \"Not Found\" string in the top-left corner under the marketing tab title; the in-dashboard 404 was hand-rolled from the error display's style. Both now render the app's own empty-state dead end, and every miss sets the \"Page not found\" title.\n- **New credentials open with a name filled in** (#3365) — the vendor's own name, numbered past that vendor's existing credentials (\"OpenRouter\", then \"OpenRouter 2\"), taken once when the vendor is picked. An untouched suggestion closes without a discard prompt. A second OAuth grant is now \"Slack 2\" rather than \"Slack (2)\".\n- **Phone layout**: the composer no longer moves when the loading skeleton becomes the real page (#3374), the automation canvas stops clipping its zoom controls below `lg` (#3364), and the mobile action dock pads its buttons evenly once they wrap (#3364).\n- **The image preview dialog keeps its size** (#3380) — a floor of `min(600px, 70dvh)`, so a small image no longer produces a cramped box with the zoom controls against its edges.\n\n## Behaviour changes\n\n- A chat send — in the app, on regenerate or edit, in either arena column, parked behind attachments, or over REST — is refused on the server when a budget cap that binds the sender is reached. The app shows a refusal toast naming the cap; REST answers 429 `BUDGET_EXCEEDED` with `Retry-After`; a send accepted before the cap closed settles its reply with `errorCode: budget_exceeded`.\n- A live chat turn holds its first round's worst case against the caps until it settles, so concurrent sends can no longer each pass a cap with room for one.\n- Team shared caps count the current members of the team; API-key caps are enforced on REST and metered; role caps also bind voice output and video-link ingest.\n- **Settings › Usage** is a new personal page for every role, linked from the chat budget banner's **View usage**.\n- **Request usage credits** notifies each owner and admin personally instead of writing an organization-wide row, and answers that it sent nothing when the requester is the only owner or admin.\n- The Inbox status and unread tiles count only conversations the viewer can open; they used to show organization-wide totals above a list that showed a subset.\n- Shipped automation packs that carry a schedule declare the scheduler's `trigger` and `firedAt` input, so a scheduled occurrence starts a run. Existing organizations keep the automation they were seeded with.\n- A mailbox read over IMAP imports messages again.\n- Settings › Governance › Policies & limits carries a **Confidentiality notice** section; the notice under the composer is opt-in and shows nothing for an organization without the policy, with it switched off, or while the read is in flight.\n- The Documents **Source** column shows a vendor mark plus an import glyph instead of words, and a failing sync is a red mark that opens the same reason and reconnect dialog.\n- A task's timeline records edits to its title, description, priority, labels, attachments, start date, due date and reviewer.\n- Products, websites, contacts and knowledge entries share one details dialog, one row menu, one delete confirmation and one size; a row click morphs into edit in the same card rather than opening a second overlay.\n- The chat list gives Projects up to half the panel and Chats the rest, each scrolling under its own header.\n- Both documentation sites render one shared frame and follow the operating-system theme when nothing is saved; the design-system guide's page title moves into the article, its page actions into the header strip, and it gains a footer.\n- An unknown URL anywhere in the app renders the app's not-found state and sets the \"Page not found\" document title.\n- **Add credential** prefills **Name** from the provider or connector.\n- In `TLS_MODE=external`, the proxy accepts `X-Forwarded-Proto` and `X-Forwarded-For` from the peers named by `TRUSTED_PROXIES` (`private_ranges` by default) and reads them strictly, right to left. Other TLS modes ignore the variable and trust no peer.\n- A browser on an additional configured origin gets object-store links signed for that origin, when the published endpoint is itself one of the deployment's origins.\n\n## API contract changes\n\nThe OpenAPI document moves from **1.12.0** to **1.13.0** (dated 2026-09-15). The surface is unchanged at **80 paths, 127 operations and 58 schemas**. The `Error.code` enum rises from 149 to **150** values with `BUDGET_EXCEEDED`.\n\n**Added**\n\n- `BUDGET_EXCEEDED` in the `Error.code` enum, answered **429** by `POST /api/v1/threads/{id}/messages` and `POST /api/v1/projects/{id}/threads/{threadId}/messages` when a budget cap that binds the key holder is reached — their own, one of their teams', the organization's or this API key's. Nothing is queued. `Retry-After` names the wait in whole seconds until the period resets.\n- Six `data` fields on that refusal: `scope` (`user` | `team` | `org` | `apiKey`), `period` (`daily` | `weekly` | `monthly`), `limitCode` (`TOKEN_LIMIT` | `COST_LIMIT` | `REQUEST_LIMIT`), `used` and `limit` in the cap's unit, and `resetsAt` in epoch milliseconds.\n- `budget_exceeded` in the chat `errorCode` classification: a cap reached **after** a send was accepted, so the turn never ran. A send made once the cap is already reached is refused up front with the 429 instead.\n\n**Changed**\n\n- Both chat send operations document the budget refusal in their description and in their 429 response, beside the existing `RATE_LIMITED` case. The budget check runs **after** the idempotency claim, so a replay of an accepted send still answers its 202.\n\n## Security\n\n- **The Inbox count tiles published organization-wide totals to every member** (#3378). The list door filters conversation by conversation through the one definition of inbox visibility; the two count doors did not filter at all. A member saw how much traffic exists that they cannot open, including the size of the unassigned triage queue, above a list showing them a subset. Both count queries now carry a SQL mirror of the visibility rule and are pinned by tests that go red on each way of widening it — in particular on any clause that would admit a null assignee, which is the admin-only triage case and the way this failure would publish an entire inbox.\n- **A credit request named the member to the whole organization** (#3373). Hitting a usage limit and clicking **Request usage credits** wrote a `system` notification, which every member can see, announcing that a named person had run out of credit — while the banner said the request went to the admins. It now writes a personal row to each owner and admin only.\n- **Budget caps were a documented control that nothing enforced** (#3375). Only the browser's composer stopped a chat turn: a REST send, an edit, a regenerate or a parked send spent past any cap, an API-key rule was never enforced or metered at all, and concurrent sends could overshoot a cap several times over because usage books only on settle. All of those now refuse on the server, and a live turn holds its worst case while it runs.\n- **`TRUSTED_PROXIES` is a trust boundary** (#3384). In `TLS_MODE=external` the proxy now believes a forwarded scheme and client address from the ranges it names, defaulting to every private and loopback range. The proxy container publishes port 80, so a client that can reach that port from inside a trusted range could claim an HTTPS connection it never made — allow only your TLS terminator to reach it, and narrow `TRUSTED_PROXIES` to the range it connects from. Every other TLS mode trusts no peer. The proxy refuses to start on a value that is neither a CIDR range nor `private_ranges`.\n- **The ReDoS remediation is kept, not dropped** (#3382). The `linkify-it` override that broke IMAP parsing was covering GHSA-22p9-wv53-3rq4 and GHSA-v245-v573-v5vm in the component that parses attacker-controlled mail. Rather than removing it and reopening those, `mailparser` moves to 3.9.28, whose own pin is the patched 5.0.2. The tree resolves `linkify-it` 5.0.2; no advisory exposure is introduced or reopened.\n\n## Known issues\n\n- **The proxy change ships unexercised on a running deployment.** The hosted fleet has not moved past 0.5.27, so the rendered `trusted_proxies` block, the strict right-to-left read and the removal of the `X-Forwarded-Proto {scheme}` pins have been proved by the CLI's compose-parity rendering and by tests, not by a live external-TLS deployment. An operator on `TLS_MODE=external` should verify sign-in callbacks, secure cookies, uploads and streaming through the full path after this upgrade.\n- **The pack fix does not reach an existing install** (#3381). Provisioning skips an automation an organization already has, so an upgraded instance keeps the version whose input schema refuses its own scheduler. Edit that automation's `inputs` to admit `trigger` and `firedAt` and deploy a new version; a new organization is seeded correctly.\n- **A budget hold covers a turn's first round.** A turn that calls tools runs up to five model rounds, each billing its full prompt again, and only the first round's worst case is held while it runs. Concurrent sends can no longer each pass a cap with room for one, but a long multi-round turn can still settle above the cap it was admitted under.\n- **Nothing backfills a task timeline** (#3379). Edits made before this release wrote audit rows only and do not appear; a label deleted from the catalog renders as its raw id rather than dropping the row.\n- 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.\n- 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.\n- **Cloud sync, left for later**: there is still 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.\n- **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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- **Recorded as contract debt, each with its design in the ledger:** a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed `inputs` schema refuses moves no trigger stamp; the MCP `run_deployed` tool keys its idempotency apart from `start_run` and REST; `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; 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.\n- One of 0.5.28's known issues is closed here: the documentation chrome is no longer duplicated between the two sites.\n\n## Migration notes\n\n- **One migration, forward-only and rolling-safe.** **0106 `budget_reservations`** on the application database, applied by the backend at boot inside the advisory lock while the previous image keeps serving. It adds four columns to `app.generations` — `user_id` and `api_key_id` (nullable text) and `reserved_cost_cents` / `reserved_tokens` (`NOT NULL DEFAULT 0`) — with `ADD COLUMN IF NOT EXISTS`, and creates `app.budget_admissions` (`org_id` primary key, `admitted_at_ms`), the row an organization's budget admissions lock and bump. The previous image holds nothing and never reads the new table, so a mid-roll window is a window where the older replica's turns are admitted the way 0.5.28 admitted them. 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.\n- **`tale deploy --stop` is required.** The `proxy` image carries source changes — the `trusted_proxies` rendering in the entrypoint and the removal of the `X-Forwarded-Proto {scheme}` pins in the Caddyfile — and the proxy is in the stop-gated tier, which a default deploy leaves running with a warning. Without `--stop` the new image is built and pinned but never takes over, and an `additionalOrigins` deployment will not work. Expect a short availability blip while the proxy container is recreated.\n- **One new environment variable**, and two that existed but were undocumented:\n  - `TRUSTED_PROXIES` — `TLS_MODE=external` only. Whitespace-separated CIDR ranges, or `private_ranges` (the default). The proxy refuses to start on any other value; the other TLS modes ignore it. Documented on **TLS and domains** and in the environment reference.\n  - `WORKER_CONCURRENCY` (default 5) — jobs one `backend-worker` process runs at once: indexing, crawls, automations and agent turns share it.\n  - `KNOWLEDGE_DB_POOL_MAX` (default 10) — connections one backend process opens to the knowledge corpus. An indexing job holds one while it commits a slice, so keep it at or above `WORKER_CONCURRENCY`; the worker now warns at boot when it is below. Count both per replica against the corpus database's `max_connections`, like `DATABASE_POOL_MAX`.\n- **The shipped automation catalog changes.** The eight packs that ship a schedule (GitHub review/triage, Gmail sync/triage, IMAP sync/triage, Outlook sync/triage) declare the scheduler's `trigger` and `firedAt` inputs. Organizations created after this upgrade get the fixed packs; existing ones keep the automation they were seeded with, as described under _Known issues_.\n- **No other configuration file changes shape.** The confidentiality-notice policy keeps its fields — `requireAcknowledgment` and `version` are still stored and preserved on save, and still drive nothing.\n- The `platform`, `proxy`, `docs` and `ui-docs` images carry source changes; the `web` image has none of its own and rebuilds on the shared `@tale/ui` package. The `db`, `sandbox`, `sandbox-runtime`, `sandbox-buildkitd`, `sandbox-egress` and `sandbox-llm-gateway` images are unchanged. **The CLI has source changes in this range** (`additionalOrigins` and its validation, adoption, receipt and preparation checks), so a managed deployment must move its pinned CLI as well as its runtime; the release executables report 0.5.29.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.29` and `marketing-ui-v0.5.29` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.29\"`. `@tale/ui` gains the `docs/*` frame family, the static-index search engine moved out of the docs site, `locale-tabs`, `bottom-tab-bar`'s placeholder, the entity dialog family and `Dialog`'s `size=\"entity\"`.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.28):\n\n  ```bash\n  tale update\n  tale deploy --stop\n  ```\n\n  The migration runs at boot. `--stop` is needed for the proxy image; see _Migration notes_. A deployment crossing from a version older than 0.5.27 should read that release's notes too.\n\n- **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. To answer on more than one origin, declare `additionalOrigins` in the deployment specification; the CLI writes `ADDITIONAL_SITE_URLS` and keeps native identity, the OIDC issuer, passkey rpID and email links on the primary origin. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(platform): apply AI provider changes without a page reload by @yannickmonney in https://github.com/tale-project/tale/pull/3363\n- feat(platform): give every unknown route the app's not-found page by @yannickmonney in https://github.com/tale-project/tale/pull/3362\n- feat(platform): prefill new credential names from the vendor by @yannickmonney in https://github.com/tale-project/tale/pull/3365\n- fix(platform): settle the queued-send tray the moment its turn starts by @larryro in https://github.com/tale-project/tale/pull/3367\n- fix(platform): unclip the mobile canvas and even out the action dock by @yannickmonney in https://github.com/tale-project/tale/pull/3364\n- feat(ui): share one docs frame between docs.tale.dev and ui.tale.dev by @yannickmonney in https://github.com/tale-project/tale/pull/3370\n- fix(platform): prepare a document once across indexing slices by @larryro in https://github.com/tale-project/tale/pull/3368\n- feat(platform): unify knowledge record view, edit and delete dialogs by @yannickmonney in https://github.com/tale-project/tale/pull/3369\n- fix(docs): align the docs site's theme default with ui-docs by @yannickmonney in https://github.com/tale-project/tale/pull/3371\n- feat(platform): add an opt-in chat confidentiality notice setting by @yannickmonney in https://github.com/tale-project/tale/pull/3366\n- fix(platform): keep the mobile composer steady while loading by @yannickmonney in https://github.com/tale-project/tale/pull/3374\n- fix(platform): morph catalog views into edit in one overlay by @AdeolaAdekoya in https://github.com/tale-project/tale/pull/3357\n- feat(platform): show members their usage limits under settings by @yannickmonney in https://github.com/tale-project/tale/pull/3372\n- fix(platform): send credit requests to owners and admins only by @yannickmonney in https://github.com/tale-project/tale/pull/3373\n- fix(platform): enforce budget caps on every chat turn by @yannickmonney in https://github.com/tale-project/tale/pull/3375\n- fix(platform): scope inbox count tiles to the viewer by @Israeltheminer in https://github.com/tale-project/tale/pull/3378\n- feat(platform): record task field edits on the timeline by @Israeltheminer in https://github.com/tale-project/tale/pull/3379\n- fix(platform): stop the image preview dialog collapsing on small images by @Israeltheminer in https://github.com/tale-project/tale/pull/3380\n- fix(platform): let a scheduled pack start from its own trigger by @Israeltheminer in https://github.com/tale-project/tale/pull/3381\n- fix(deps): restore IMAP ingest by unpinning linkify-it from mailparser by @Israeltheminer in https://github.com/tale-project/tale/pull/3382\n- feat(platform): split the chat list between projects and chats by @yannickmonney in https://github.com/tale-project/tale/pull/3383\n- feat(cli): serve managed deployments on additional origins by @yannickmonney in https://github.com/tale-project/tale/pull/3384\n- fix(platform): show document sources and sync failures as icons by @yannickmonney in https://github.com/tale-project/tale/pull/3386\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.28...v0.5.29","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.29","publishedAt":"2026-09-16T03:44:36Z"},{"tag":"v0.5.28","version":"0.5.28","name":null,"body":"**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`.\n\n## Highlights\n\n### A broken cloud sync is visible, and its owner is told once (#3360)\n\nA 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**.\n\nNow 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.\n\nA Google Drive folder also reads **Google Drive (synced)** rather than the hardcoded **OneDrive (synced)**.\n\n### The ninth API evaluation pass (#3361)\n\nThe 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.\n\nFive 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_.\n\n## Behaviour changes\n\n- 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.\n- 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.\n- 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.\n- A Google Drive synced folder is labelled **Google Drive (synced)**.\n- 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.\n- The backend stamps no `sentry-trace` or `baggage` header onto any outgoing request.\n- 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.\n\n## API contract changes\n\nThe 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.\n\n**Added**\n\n- 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.\n- `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.\n\n**Changed**\n\n- `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.\n- `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.\n\n**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.\n\n## Security\n\n- **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.\n- **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.\n- The crawler names itself, so a site owner can refuse or throttle it specifically rather than blocking an anonymous client.\n\n## Known issues\n\n- 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.\n- 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.\n- **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.\n- **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.\n- **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.\n- The docs chrome is duplicated between the documentation site and the design-system site rather than shared.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- **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.\n- **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.\n- 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.\n\n## Migration notes\n\n- **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.\n- **No new environment variable**, and no configuration file changes shape.\n- **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.\n- 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.\n- **`@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\"`.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.27):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  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.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- feat(platform): surface broken cloud syncs and notify the owner once by @larryro in https://github.com/tale-project/tale/pull/3360\n- fix(platform): close the 2026-09-15 API evaluation's round-i findings by @larryro in https://github.com/tale-project/tale/pull/3361\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.27...v0.5.28","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.28","publishedAt":"2026-09-15T07:28:13Z"},{"tag":"v0.5.27","version":"0.5.27","name":null,"body":"**0.5.27 is a patch tag on the 0.5 line that carries more than its fixes — read it as a minor.** It ships three forward-only schema migrations (one on the application database, two on the knowledge corpus), an API contract that moves from 1.9.0 to 1.11.0 in two additive steps, a new edge rule and a recovery selector for managed deployments; nothing in it is breaking, no configuration file changes shape, no environment variable is added, and the upgrade is still `tale update` followed by `tale deploy --stop` (the proxy image changes — see _Upgrading_). Four changes since 0.5.26: the eighth external API evaluation's findings closed at their root, the documentation rewritten as complete tasks in three languages together with the platform defects its walkthroughs found, the Documents hub listing one folder at a time, and a way out for a managed deployment whose native configuration plan has to be replaced.\n\n## Highlights\n\n### The eighth API evaluation pass (#3359)\n\nThe eighth external black-box evaluation of the REST and MCP API ran against 0.5.26 and reported 3 S2, 20 S3 and 35 S4 findings. Every finding was re-verified against the source before anything changed: the three S2 are real defects, and so is the dense-retrieval finding the report itself had marked unconfirmed — all four fixed at the root with regression tests and integration lanes; the rest split three ways: code defects fixed, contract-precision items closed in the OpenAPI document and the en/de/fr reference, and four product gaps recorded in the contract debt ledger with their pay-down (listed under _Known issues_).\n\n- **`robots.txt` governs every path into the crawler.** The `Disallow` rules were applied at sitemap and link-walk discovery only; the links a _rendered_ page yielded were admitted without them, so a site whose `robots.txt` disallowed its legal pages had them fetched, indexed and served the moment a rendered page linked to them. The rules now govern every admission path and every non-listed fetch, and are kept per site (`robots_disallow`, knowledge migration `public_web` 11): a `robots.txt` that answers 5xx or 429 keeps the last known rules, a 4xx clears them; a stored page a new rule covers is retired on the next scan; a URL you listed stays your instruction, and a listed page the site answers **404** for keeps an honest `http_error` row; `<meta name=\"robots\" content=\"noindex\">` is honoured like the header; `http://` is refused as `WEBSITE_DOMAIN_INVALID` and a trailing dot stripped; `websites?scanInterval=` refuses a value outside its seven; `idle` leaves `Website.status` (a registered site starts `scanning`); a fetch error carries no OpenSSL handle or runtime path.\n- **The dense retrieval leg no longer starves.** A scoped knowledge query over a large corpus ran an HNSW scan whose post-filter could empty the vector leg silently, and a duplicate-heavy export — one line repeated 4,500 times — became 4,500 identical vectors crowding out nearer passages. Under 5,000 candidate chunks the dense leg runs an exact scan, above it an iterative HNSW scan (pgvector 0.8 and later; an older pgvector keeps the plain approximate scan and says so in the log); a passage repeated inside a document is embedded once (`passage_repeat`, knowledge migration `private_knowledge` 12 — such rows carry no vector and sit outside both legs); `diagnostics.dense` says whether the vector leg ran; RRF ties break keyword-first, then by id; control characters are stripped from served passages; the `query` cap of 2000 characters is named.\n- **One live run per task, whichever automation asks.** The rule 0.5.26 put in the schema was keyed per (task, automation) while the docs, the OpenAPI operation and the product say per task, so two engines could mutate one card at once. Migration 0104 collapses cross-automation live duplicates onto the earliest run and replaces the index with `automation_runs_one_live_per_task_subject`; the per-task lock and probe move with it, and a second start naming another automation answers `already_running` with the live run's id.\n- **The generation poll cannot cut a surrogate pair.** `?since=` and `?reasoningSince=` sliced the text in UTF-16 code units with no pair snapping, so a slice could begin with a lone low surrogate the write side refuses; both are snapped down to a pair's start and `textOffset` / `reasoningOffset` report where the slice began, so `held.slice(0, textOffset) + text` always reassembles. A prompt with no visible text (format controls, zero-width spaces) answers **400**; archiving a thread mid-turn answers **409** `CHAT_TURN_IN_PROGRESS`; a thread or project archived after a send was accepted settles the reply `cancelled` with the prompt kept; the execute lane is charged only after body and header pass; `GET /api/v1/models` answers `harnesses` and `PROJECT_AGENT_HARNESS_INVALID` names the eligible set.\n- **The CRM mirror can be thawed and listed.** `POST /api/v1/contacts/{id}/restore` is the remedy a frozen mirror's **409** names (the create's own 409s apply when a live contact has since taken the key); `GET /api/v1/conversations?source=` lists every mirror under a source newest first, `contactStatus` narrows, and the keyset cursor is signed under the source; a re-keyed contact's binding follows its current `externalId` (the old id answers **409** `CONVERSATION_CONTACT_CONFLICT`); the receipt carries `contactId`; every **201** that creates one addressable resource carries `Location` (thirteen operations); the document `PATCH` answers its `ETag`; the skill file read is validated (`ETag`, `Last-Modified`, **304**); `null` on a create reads as \"left out\" and the document says so.\n- **MCP tools say what they did.** `start_run` takes `idempotencyKey` through the REST ledger; `set_trigger` answers a webhook token once and says whether the trigger is `deployed`; `get_automation` reads `version: \"deployed\"`; `list_versions` marks the deployed row; every capability refusal carries a `code` (`CAPABILITY_NOT_FOUND`, `CAPABILITY_INPUT_INVALID`, the knowledge door's own); the registry holds deployed automations only; an enum argument outside its set is refused with the set named.\n- **The edge, the session doors and the rendered reference.** The proxy strips `Expect`: it answers `100 Continue` itself, and forwarding the header let the origin write a second one — two interim responses on one request, a desync for a strict HTTP/1.1 client. A missing session answers the coded `UNAUTHORIZED` envelope on `/events` and the app routes instead of a bare `{\"error\":\"unauthorized\"}`. The rendered API reference keeps a pasted key out of `localStorage`, and `swagger-ui-react` moves to 5.32.15 on `immutable` 5.1.9 — the pair built for each other — so its **Logout** works again (0.5.26 ran 5.32.2, declared for Immutable 3, on the 4.3.9 override).\n\n### The documentation, rewritten as tasks (#3342)\n\nAll 381 non-video product guides were rewritten in English, German and French to lead a reader through a complete task — prerequisites, choices, examples, the observable result and the recovery — with three new pages (**Ask about files and images**, **Manage tasks on a project board**, **Set automation approval rules**), the READMEs and the contributor guide, the documentation and translation authoring skills, and every screenshot retaken on the current platform (the README gallery's six sources link to their full-resolution captures). The nineteen component guides on the design-system site explain setup, composition, state ownership, accessibility and working examples; both docs shells keep the initial skip-link focus and the reduced-motion preference; the shared Markdown renderer keeps authored Frame figures and captions.\n\nThe walkthroughs drove the real product and fixed what they found (manual rounds R3–R6 in `services/platform/tests/manual/runs/`):\n\n- **An approval policy fails closed.** A new decision read the organization's approval policy through a path that could substitute an unrestricted default when the policy file was invalid or its configuration mount unavailable, so a platform-internal connector write could run unapproved. New decisions read the strict, uncached configuration path; invalid YAML, an invalid schema or a missing mount refuse instead of allowing; existing pending decisions keep their authority.\n- **A run asks for its input.** An automation whose version declares an `inputs` schema had no way to supply it from **Test run** or **Run live**; the confirmation dialog now collects the JSON against the saved test version or the deployed live version, shows the schema, validates without runtime code generation (the production CSP forbids it) and sends the original JSON. Native waiting runs render: the pending-approval card recognises UUID approval ids (both decision controls were hidden), and a run with no finish date no longer shows 1 January 1970.\n- **Product images upload through the platform.** The product editor takes an image (PNG, JPEG, WebP, GIF or SVG, inspected, up to 5 MiB) into an organization-protected app URL that only an authorised member's session can read; a file bound to a product refuses deletion (**409** `FILE_BOUND_TO_PRODUCT`) until the image is removed; the REST door answers that URL absolute and accepts it back on a create or patch — even on a private deployment — while every pasted external URL keeps the public-host rule.\n- **Conditional document writes hold under contention.** `If-Match` on the document `PATCH` was checked before the write lock, so a concurrent edit could still be overwritten; the fresh representation is compared inside the write transaction and a mismatch answers **412** with the other writer's change kept.\n- **A self-hosted provider behind private DNS reaches the gateway.** The private-network admission for a custom provider URL decided by hostname spelling and missed a name that resolved to a private address; every resolved address is inspected, a cloud-metadata address is refused whatever the name, the check is repeated before a cached provisioning is reused, and a public/private transition invalidates the provisioned fingerprint.\n- **Indexing says why it stopped.** The document dialog and the project files tab name the terminal cause — `unsupported_type`, `image_no_vision`, `empty`, `not_text`, `malformed` — instead of \"Format not supported\", and offer no retry for unchanged terminal content; knowledge entries show their real indexing status (they read **Not indexed** after a completed index), and a document hint refreshes the entry list without a reload.\n- **Smaller fixes.** The project-agent model picker finds a model by its API id as well as its display name; a website's detail dialog shows its creation date; contact and product rows show their dates instead of a dash; an active WebDAV app password stays revocable (a native `revokedAt: null` read as revoked) and a long device label no longer clips its badge; the rail's navigation memory survives a browser whose storage getters throw; the Vite dev server passes WebDAV discovery to the backend; `tale init --no-env` no longer claims it generated secrets.\n- **The settings copy says what the build does.** Personalisation instructions and memories are stored but not yet used by chat replies; a skill's \"hidden from the model\" switch is advisory metadata; the SSO description names the organization picker; Microsoft 365 file import has its own consent flow (do not add `Files.Read` or `Sites.Read.All` to the SSO scopes); the audit verifier's truncation and scrub notes read as the bounded PostgreSQL verifier behaves, and the environment reference says `TALE_AUDIT_SIGNING_KEY` is retained for compatibility and not read by it.\n\n### The Documents hub lists one folder at a time (#3356)\n\nSince the 0.5.0 Postgres port, the hub's root page listed every document in the organization: a file synced from a OneDrive or Google Drive folder appeared at the root and inside its folder, and deleting the folder took both rows with it. The root page now lists only the documents that sit in no folder, a folder page lists that folder, and no request shape lists the whole hub; a cloud import refreshes the folder list too, so the sync-root folder shows without a reload. Two defects went with it: a parent crumb in the breadcrumb navigated to the root (the folder rows were keyed `id`, the crumb read `_id`), and the manual-suite guide checker could not see the design-system catalog's keys. On REST, `folderId=root` on `GET /api/v1/documents` and `GET /api/v1/projects/{id}/files` lists the unfiled documents — the root no folder id could name; omitting the parameter still lists everything (contract 1.10.0).\n\n### A managed deployment recovers a replaced configuration plan (4f7b42637)\n\nA deployment that fails while verifying a provider catalog retains a native configuration plan; when a reviewed replacement bundle repaired the provider's endpoint, the native phase still selected the old plan, refused the changed declaration, and bundle recovery could not finish. `supersedesPendingConfigurationPlan` in a managed deployment declaration — `config apply --supersedes-pending-plan <sha256>` on a standalone workspace — selects the exact retained plan by the SHA-256 of its canonical JSON (keys sorted recursively, arrays kept, no whitespace). The engine keeps the prior journal and verified writes, requires the same organization, origin and resource identities, refuses native state outside the old operation's recorded preimages and results, keeps the native compare-and-set checks, and replays an interrupted replacement without duplicating completed writes; `supersedesPendingBundle` alone never replaces native configuration intent. The receipt keeps the superseded plan and its verified subset under `superseded`; drop the selector once the replacement reaches `ready`. The CLI README and the CLI install page carry the recipe.\n\n## Behaviour changes\n\n- The Documents page lists one folder at a time: the root shows folders and unfiled documents only, and a synced cloud folder's files stand inside their folder. A cloud import shows its sync-root folder without a reload; a parent crumb opens that folder.\n- **Test run** and **Run live** on a version that declares inputs open the confirmation dialog with a **Run input (JSON)** field and the schema beside it; malformed or non-conforming input is refused before anything is scheduled.\n- An invalid or unavailable approval policy refuses a new platform-internal write instead of allowing it.\n- The pending-approval card renders for UUID approval ids; a waiting run shows no finish date.\n- The product editor uploads an image; a file bound to a product cannot be deleted until the image is removed from the product; `Product.imageUrl` over REST is absolute, and the deployment's own image URL is accepted back.\n- A document that cannot be indexed names its cause (**Document cannot be indexed**, with the reason) and offers no retry; a project file in that state shows the same label; knowledge entries show their indexing status.\n- Websites: a registered site starts **Scanning** (the **Idle** status is gone); the page counts move while a scan runs; pages `robots.txt` has come to disallow leave the index on the next scan, and a page carrying `<meta name=\"robots\" content=\"noindex\">` is not indexed; an `http://` address is refused when adding a site; the detail dialog shows the creation date.\n- Knowledge search: a passage repeated inside a document is returned once; a scoped search over a large corpus keeps the dense hits it used to drop; control characters are stripped from passages.\n- Task automation: a task holds one live run whichever automation started it; a second start naming another automation returns the existing run as `already_running`. The guide's \"per-task safeguard\" pause never existed; the guide now says the one-engine rule is the stop.\n- REST chat: a prompt with no visible text answers **400**; archiving a thread mid-turn answers **409**; a thread or project archived after acceptance settles the reply `cancelled` with the prompt kept; `since` and `reasoningSince` never split a surrogate pair.\n- REST: every **201** that creates one resource carries `Location`; `null` on a create or bulk import reads as the field left out; the document `PATCH` answers its `ETag`; the skill file read answers **304** on `If-None-Match` / `If-Modified-Since`; `GET /api/v1/models` answers `harnesses`; `websites?scanInterval=` and an unknown `harness` are refused by name.\n- The session doors answer coded envelopes: a missing session `UNAUTHORIZED` with `Cache-Control: no-store` on `/events` and the app routes, a missing `orgId` `INVALID_QUERY`, a non-member `ORG_FORBIDDEN`.\n- The rendered API reference at `/docs` keeps a pasted key in page memory only (a reload asks again); its **Logout** works.\n- The edge strips `Expect` once the proxy is recreated (see _Upgrading_); the proxy's self-signed mode log names the root certificate to copy instead of `caddy trust`.\n- The project-agent model picker matches the API model id; contact, product and website rows show their dates; an active WebDAV credential stays revocable and its badge readable.\n- The settings copy for personalisation, memories, skill invocation, SSO and Microsoft 365 import describes the current build (see _Highlights_).\n- `tale deploy` on a managed deployment honours `supersedesPendingConfigurationPlan`; `tale config apply` takes `--supersedes-pending-plan`; `tale init` ends with a backup reminder instead of the \"production-ready by default\" line, and `--no-env` says the environment setup was skipped.\n- docs.tale.dev: three new pages; the attachments page is a page again (its redirect to the chat basics page is gone); the initial skip-link focus and the reduced-motion preference are kept.\n\n## API contract changes\n\nThe OpenAPI document moves from **1.9.0** to **1.11.0** in two additive steps (1.10.0 dated 2026-09-14, 1.11.0 dated 2026-09-15); the `Error.code` enum keeps its 149 values. One enum value leaves — `Website.status` loses `idle`, which no running instance ever answered (a registered site starts `scanning`) — and every nullable enum now lists `null`.\n\n**Added**\n\n- 1.10.0 — `folderId=root` on `GET /documents` and `GET /projects/{id}/files`: the documents (files) in no folder; a folder id answers that folder, an unknown id on the project door the opaque **404** `FOLDER_NOT_FOUND`, a blank value **400**; omitting the parameter still lists everything.\n- 1.11.0 — `POST /contacts/{id}/restore` (**200** with the contact; a live contact is a no-op; **409** `CONTACT_DUPLICATE_EMAIL` / `CONTACT_DUPLICATE_EXTERNAL_ID` when a live contact has since taken the key; a body other than `{}` **400**); `GET /conversations` (`source` required; `contactStatus`, `cursor`, `limit`) — `ConversationMirror` rows newest first under a source-signed cursor; `contactId` on the conversation-sync receipt; `Location` on the thirteen **201** creates (documents, websites, products, contacts, projects, project agents, folders, files, tasks, threads, project threads, knowledge entries, `PUT /skills/{slug}`); `harnesses` on `GET /models`; `ETag` on the document `PATCH` response; `ETag`, `Last-Modified` and **304** on `GET /skills/{slug}/files/{path}`; `KnowledgeDiagnostics.dense`; on the MCP endpoint `start_run.idempotencyKey`, `deployed` on `set_trigger`, `version: \"deployed\"` on `get_automation`, the `deployed` marker and `deployedVersion` on `list_versions`, and a `code` on every capability refusal.\n\n**Changed**\n\n- `PATCH /threads/{threadId}` with `archived: true` while the turn is queued or streaming answers **409** `CHAT_TURN_IN_PROGRESS`; a thread or project archived after a send was accepted settles that reply `cancelled`.\n- `POST /conversations/sync` — a re-keyed contact's binding follows its current `externalId`; a snapshot naming the old id answers **409** `CONVERSATION_CONTACT_CONFLICT`.\n- `POST /websites` — a `domain` carrying a scheme answers **400** `WEBSITE_DOMAIN_INVALID`; a trailing dot is stripped.\n- `GET /websites?scanInterval=` — a value outside `60m`, `6h`, `12h`, `1d`, `5d`, `7d`, `30d` answers **400** `INVALID_QUERY`; `Website.status` is `scanning`, `active`, `error`, `deleting`.\n- `POST /projects/{id}/agents` and its `PUT` — a `harness` outside the eligible set answers **400** `PROJECT_AGENT_HARNESS_INVALID` with the set in `data.harnesses`.\n- The generation poll — `since` and `reasoningSince` are snapped down to a surrogate pair's start; `textOffset` and `reasoningOffset` say where the slice begins.\n- Thread sends — a `content` with no visible text answers **400**; the execute-lane budget is charged only after the body and the `Idempotency-Key` header pass.\n- `POST …/tasks/{taskId}/start` — `already_running` may name a run another automation started (its `name` says which).\n- `ContactInput` and `ProductInput` declare their nullable fields; a `null` or blank on a create or bulk import reads as the field left out; the enum query filters are declared.\n- `Product.imageUrl` — the deployment's own product-image URL is accepted back on a create or patch, even on a private deployment; a missing or inaccessible image answers **404** `FILE_NOT_FOUND`.\n- The session doors (`/events`, `/api/app/…`, the control routes) — `UNAUTHORIZED`, `INVALID_QUERY` and `ORG_FORBIDDEN` beside the sentence.\n- The MCP endpoint — `set_trigger` takes one shape per kind (a key of another kind is refused by name); an enum argument outside its set is refused with the set named; `start_run` refuses a reused `idempotencyKey` with different arguments as `IDEMPOTENCY_KEY_REUSED`.\n\n**Documented, unchanged on the wire** (en, de, fr): the edge's bare **431** and **400** carry no `X-Request-Id`; `Idempotency-Key` is read only by the operations that declare it, and `curl -H 'Idempotency-Key:'` sends no header at all; the redirect ceiling (`redirect_limit_exceeded` past five redirects); the knowledge `query` cap of 2000 characters and the similarity-threshold recipe; a skill keeps no version history on the machine door; the per-task circuit breaker is not built; `Product.imageUrl` and `WebsitePage.lastErrorKind` say what they carry.\n\n## Security\n\n- **An approval policy fails closed** (#3342): an invalid or unavailable approval-policy file no longer lets a platform-internal connector write run without the approval the organization configured. If a deployment carried a malformed policy file, review the automation runs of that period.\n- **`robots.txt` is honoured on every crawl path** (#3359): pages a site asked crawlers to leave alone are no longer fetched, indexed or served through rendered-page links, and pages a rule covers leave the index on the next scan.\n- The rendered API reference no longer persists a pasted key in the browser's `localStorage`; `swagger-ui-react` 5.32.15 runs on `immutable` 5.1.9, clear of the two advisories the dependency gate tracks (GHSA-v56q-mh7h-f735, GHSA-xvcm-6775-5m9r).\n- The edge strips `Expect`, closing an interim-response desync a strict HTTP/1.1 client could be confused by.\n- The product-image intake inspects the bytes (the declared type is not trusted; an SVG with active content is refused), serves them only to an authorised app session — an API key does not authorise the route — and refuses a caller claiming the intake's reserved source on the generic file registration.\n- A custom provider host is resolved and every address checked before the gateway is provisioned; a cloud-metadata address is refused whatever the name.\n- Fetch errors from the crawler carry no OpenSSL handles or runtime paths; a missing session answers `Cache-Control: no-store`.\n\n## Known issues\n\n- 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.\n- 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.\n- **The `Expect` rule was not exercised on a running deployment.** It went into a `tale-proxy` image no deployment has run yet, and `services/proxy` has no test of its own. The 0.5.26 gap is closed: the hosted platform's edge has answered **400** `BODY_CHUNK_MALFORMED` to a malformed chunked body since 2026-09-14, and the analytics header rides the same recreated proxy.\n- **Documents indexed before this release 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's robots rules are empty until its first scan on this release; that scan reads `robots.txt` afresh and retires the pages it covers.\n- **The rail's navigation memory has had part of its manual round**: R5 drove six EN/DE/FR desktop and phone cases (restore, reset, a fresh chat, two-tab isolation) covering parts of `NAV-F16`–`NAV-F19`; the remaining section, the second-account cases and `NAV-B6`–`NAV-B9` are still unrun.\n- **ui.tale.dev is not live at this tag.** `tale-ui-docs:0.5.27` exists from this release, but the site goes live with the operator's deployment of it; until then the link on the contributor-setup page does not resolve. The site still has its nineteen seed pages, and the docs chrome is duplicated between the docs site and the design-system site rather than shared.\n- 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.\n- **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.\n- **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.\n- 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.\n- A tool call the reply cap cut keeps `input: {}` on the stored `tool-call` part; the part says its arguments were cut, but the raw text the model emitted is still not on the transcript.\n- 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.\n- 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.\n- **Recorded as contract debt by the eighth evaluation, each with its design in the ledger:** `GET /notifications` rows carry `type` as a free string and nothing pushes them to a machine caller (poll only); a skill keeps no version history on the machine door (`PUT /skills/{slug}` replaces, and the app's history is not readable over `/api/v1`); the per-task circuit breaker is not built — no counter pauses automation on a task after N runs in an hour, the one-engine rule and cancel are the only stops; `GET /conversations` lists the mirrors, but the messages a snapshot applied are readable only in the app.\n- **Still open from the seventh evaluation:** a run carries no `usage` or cost; approvals and asks have no REST twins (a run parked on `waitingFor: approval` or `ask` is decided in the app); 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 (a knowledge database without ParadeDB) stamps `score: 0` silently; no `Idempotency-Key` on the task start, so a retry after the run finished starts another; no queue position on a queued send; a corrupt Office document (`docx`, `pptx`, `xlsx`, `odt`) 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.\n\n## Migration notes\n\n- **Three migrations, all forward-only and rolling-safe.**\n  - **0104 `automation_runs_one_live_per_task_subject`** on the application database, applied by the backend at boot inside the advisory lock while the previous image keeps serving: it first collapses any live runs (`queued`, `running`, `waiting`) that share an organization, a project and a task subject across automations onto the earliest-started run, marking the rest `cancelled`, then adds the partial unique index over that key and drops 0102's per-(task, automation) index, which the new one implies. Inside the roll window a cross-automation start on the previous image answers a unique violation as a **500** for that one request — never a second run.\n  - **`public_web` 11 `website_robots_rules`** and **`private_knowledge` 12 `chunks_passage_repeat`** on the knowledge corpus — `ADD COLUMN IF NOT EXISTS` (`robots_disallow`, `robots_fetched_at`; `passage_repeat` defaulting to `false`), metadata-only and idempotent; the previous image neither reads nor writes the columns. The backend applies them to the deployment-default corpus at boot from the files the platform image ships (the same files, version-aware); the database image applies them in its knowledge role when its container starts on the new image; a bring-your-own corpus gets them from the same converge path on first use. Neither has a down migration by design.\n  - No backfill runs: rows indexed earlier read `passage_repeat = false` and heal on re-index; a site's rules are `NULL` until its next scan. 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.\n- **No new environment variable.** The root `.env.example` only rewrites the comment on `TALE_AUDIT_SIGNING_KEY`: the key is generated and retained by the CLI for compatibility, and the current PostgreSQL audit verifier does not read it. Keep the value with the deployment's secrets; nothing to change.\n- **The proxy image changes** — the `Expect` request header is stripped before the origin sees it, and the self-signed mode's startup hint names the root certificate to copy. 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 rule (a brief downtime while `db`, `object-store` and `proxy` recreate); until then a client sending `Expect: 100-continue` still gets 0.5.26's two interim responses. An own-Compose deployment pulls the new `tale-proxy` tag.\n- The platform (the backend, the app, the message catalogs), proxy, db (the two knowledge migration files), docs (the rewritten pages in en, de and fr and their screenshots) and ui-docs (the component guides and the docs chrome) images carry source changes; the web image rebuilds with the updated `@tale/ui` package and has no change of its own; the sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have none. The CLI has source changes — the pending-plan recovery selector and the `tale init` completion text — and the release executables report 0.5.27.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.27` and `marketing-ui-v0.5.27` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.27\"`.\n- **Documentation redirects**: `platform/chat/attachments` is a page again (its redirect to the chat basics page is removed); every other redirect stays.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.26):\n\n  ```bash\n  tale update\n  tale deploy --stop\n  ```\n\n  The three migrations run at boot. `--stop` recreates `db`, `object-store` and `proxy` so the edge carries the new rule (a brief downtime); a plain `tale deploy` applies everything else — the knowledge columns included — and leaves the running proxy on 0.5.26's rules.\n\n- **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. A deployment stranded on a retained native configuration plan declares `supersedesPendingConfigurationPlan` with the plan's hash beside the corrected `configuration`, as above. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(platform): list only unfiled documents at the hub root by @larryro in https://github.com/tale-project/tale/pull/3356\n- fix(cli): recover explicitly replaced native configuration plans by @yannickmonney in https://github.com/tale-project/tale/commit/4f7b426371423a1661e956a0769d5153d2334162\n- docs(docs): overhaul guides, translations and authoring skills by @yannickmonney in https://github.com/tale-project/tale/pull/3342\n- fix(platform): close the 2026-09-14 API evaluation's round-h findings by @larryro in https://github.com/tale-project/tale/pull/3359\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.26...v0.5.27","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.27","publishedAt":"2026-09-15T01:49:15Z"},{"tag":"v0.5.26","version":"0.5.26","name":null,"body":"**0.5.26 is a patch tag on the 0.5 line that carries more than its fixes — read it as a minor.** It ships two forward-only schema migrations, three optional environment variables, an API contract that moves from 1.6.0 to 1.9.0 in three additive steps, a new `tale-ui-docs` image and CLI changes for managed deployments; nothing in it is breaking, no configuration file changes shape, and the upgrade is still `tale update` followed by `tale deploy --stop` (the proxy image changes — see _Upgrading_). Fourteen changes since 0.5.25: the seventh external API evaluation's findings closed at their root, the platform's reusable components folded into `@tale/ui` with a design-system site of their own, a navigation memory and tabbed automation pages in the app, a notification export for connected applications, a hostname migration and a container-name prefix for managed deployments, and aggregate analytics that stays off unless an operator switches it on.\n\n## Highlights\n\n### The seventh API evaluation pass (#3353)\n\nThe seventh external black-box evaluation of the REST and MCP API ran against 0.5.25 and reported 3 S2, 24 S3, 34 S4 and 9 lead-lane findings. Every finding was second-confirmed against the source; all three S2 are real defects, fixed at the root with regression tests and integration lanes, and the rest split three ways: code defects fixed, contract-precision items closed in the OpenAPI document and the en/de/fr reference, and thirteen product gaps recorded in the contract debt ledger with their pay-down (listed under _Known issues_).\n\n- **A mirrored conversation could re-home onto a recreated contact.** `DELETE /api/v1/contacts/{id}` frees the contact's `externalId`, and the next `POST /api/v1/conversations/sync` re-resolved the conversation's `externalContactId` onto whichever contact now carried it, so an unrelated contact inherited the history. A content snapshot for a conversation whose contact is in the trash answers **409** `CONVERSATION_CONTACT_TRASHED` and applies nothing; a `deleted: true` teardown still closes the mirror; the receipt reports `contactStatus` (`active`, `trashed`, `missing`) and the bound `externalContactId`; an existing conversation's contact is never re-resolved.\n- **Documents released from a deleted project stayed unfindable.** A project delete in `detach` mode (and the app's delete) released the documents without re-stamping the corpus rows retrieval pre-filters on (`project_id`, `team_ids`, `folder_path`), and a `retry-indexing` reported success while changing nothing. Both delete doors re-stamp the released documents after the commit, and the indexer's skip-unchanged branch re-stamps off the current scope, so a retry heals a drifted stamp.\n- **Six concurrent task starts began five runs.** The start guard was a check-then-act inside a SERIALIZABLE transaction whose snapshot froze before the advisory lock, so every racer read \"no live run\". The rule now lives in the schema: migration 0102 collapses existing live duplicates onto the earliest and adds the partial unique index `automation_runs_one_live_per_task`; the REST door starts under READ COMMITTED, and a loser's unique violation is reconciled by re-reading the winner (`already_running`, never a second billable run). The integration lane starts six at once and counts one run.\n- **Runs say why they failed.** `Run.failureCode` and `RunSummary.failureCode` carry one enum over the engine's, the provider's and the agent turn's causes — `node_error`, `connector_error`, `credit_exhausted`, `rate_limited`, `context_length`, `turn_crashed`, `deadline`, `ask_expired` and twenty more — written when a run lands on `failed` (migration 0103); a cancel clears the stale park `detail` and answers the run's `status` beside `cancelled`; `GET /api/v1/me` answers `capabilities.developer`.\n- **Indexing names its cause and knows what is terminal.** `indexing.errorCode` is a closed vocabulary of thirteen values: `unsupported_type`, `image_no_vision`, `empty`, `not_text` and `malformed` are terminal (`unsupported` — a retry reproduces the answer and is refused as terminal), the rest read `failed` with the job retrying (`embedding_upstream`, `indexer_error`, `index_rebuilding`, …). NUL bytes are stripped before the PII policy and the chunker (a `.txt` that was really binary used to fail after the embedding was paid for); the stored `mimeType` is resolved from the extension first (`json`, `yaml`, `yml` mapped); `GET /api/v1/projects/{id}/files/{documentId}` reads one file row with its `indexing` state — the single-file read 0.5.24 and 0.5.25 listed as missing.\n- **The crawler explains an empty page.** A page the crawler looked at and stored nothing for is `unsupported_content` instead of a row that reads unfetched; `X-Robots-Tag: noindex` is honoured (`robots_noindex`); a TLS handshake failure is `tls_error`; a same-host link naming a port is never dialed; a scan that stored nothing ends on `error` with its reason instead of `active`; `Website.status` is an enum and `?status=` refuses a value outside it.\n- **Contract doors hold their line.** A blank or over-long `Idempotency-Key` answers **400** `INVALID_HEADER` (a blank header no longer reads as \"no key\"); `POST /api/v1/contacts/bulk` judges rows one by one; `contacts?source=` is the closed set; `products.imageUrl` is held to the crawl-target host rule as a string, never fetched; a contact, product or project `PATCH` that changes nothing writes nothing; `If-Match` on the document update answers **412** `PRECONDITION_FAILED` with `data.etag`; the REST **429** carries a sentence and a `requestId` like every other refusal; a body string with a NUL or a lone surrogate is refused with its path.\n- **Chat, MCP and the edge.** A reply that settles with no text carries no empty text part (a reasoning-only cut keeps its `reasoning` part); a tool call the reply cap withheld says whether its arguments were cut or complete; every MCP string argument refuses a blank — whitespace alone included — at the transport (JSON-RPC `-32602`); a malformed HTTP/1.1 chunked request body answers **400** `BODY_CHUNK_MALFORMED` at the edge instead of the 502 outage envelope with retry advice; `//api/v1/…` answers the JSON 404; `robots.txt` no longer pairs `Allow: /` with `Disallow: /` and carves out the developer pages; `bluetooth` left the Permissions-Policy, and with it the one console warning every page load carried.\n- **A cloud-drive folder sync files its documents.** A OneDrive or Google Drive sync import whose files the hub already knew — an earlier one-time import at the root, a directly picked file — left them at the root with no folder row and nothing to stop the sync from. The sync root now exists from the moment the sync configuration does, and an adopted, unchanged document is moved into the folder its selection names. A live organization heals on its next scheduled sync.\n\n### One design system, two packages, and a site to read them (#3351)\n\n- **`@tale/ui` holds every reusable platform component.** 324 files moved out of the platform app into `packages/ui` (152 new export subpaths), their message keys into the package catalog, tests and stories alongside; the platform keeps only the wrappers that carry business logic — org-branded logos, the ability-gated tab strip, the app sidebar, error-scope and accent-colour providers — and imports everything else through `@tale/ui/<subpath>`.\n- **`@tale/marketing-ui` is the marketing language**, split out of `@tale/ui` and the web site — site chrome, primitives, feature frames, product-demo frames, entrance motion, marketing tokens — layered on `@tale/ui`; routing stays the host's.\n- **Both packages install from GitHub with Bun.** Bun cannot install a package from a subdirectory of a git repository, so every push to `main` republishes each package as a root-level snapshot on the `dist/ui` and `dist/marketing-ui` branches, and every release pins the same snapshot with a `ui-v<version>` and a `marketing-ui-v<version>` tag: `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.26\"` is reproducible, the branch moves. The consumer contract is in `packages/ui/README.md`.\n- **docs.tale.dev wears the app language**: a navigation rail, one sticky strip with the breadcrumb trail and the page actions, a mobile header with a drawer, neighbour cards and a compact footer — no marketing chrome. Three defects went with it: ancestor rows claiming `aria-current`, a drawer that needed two Escapes to close, an invisible scrim after a viewport change.\n- **ui.tale.dev is a new service, `services/ui-docs`** (port 3003, image `tale-ui-docs`, its own compose file): the front page in the marketing language, every `/docs/*` page in the app language, markdown content with live examples backed by the real components, search, prerender and the SEO artifact set, and a container test in the release gate. Nineteen pages seed it; the contributor-setup page points at it.\n\n### The app remembers where you were (#3347, #3345, #3338, #3346, #3340)\n\n- **Each rail section reopens the place you last had open there** — the task board you were on, not the projects list — and clicking the section you are already in goes to its default entry, the one-click way out of a deep page. The memory is per tab (two tabs on different projects do not overwrite each other), seeded across restarts, and expires eight hours after your last navigation; a deleted project or automation forgets its place. Section roots do not redirect, so a shared `/projects` link still means what it says. The Knowledge rail gained its missing knowledge-entries item, and a deleted project's page carries a link back to the list.\n- **Automations are built like projects.** The list ends its title row in a divider above the toolbar and its button reads **Create automation**; an automation's page has three tabs — **Editor**, **Versions**, **Runs** — with the version picker, **Deploy this version**, the run verbs and **Save**/**Discard** at the right end of the tab strip; the bare URL forwards to `/editor`, the canvas version lives in `?version=`, an unknown slug renders the not-found state instead of loading forever, and below `md` the verbs sit in the floating dock. The switcher in the breadcrumb lists every automation in the organization even from inside a project — organization-level entries above a divider, project-specific ones below — keeps the tab you were on, and resets the editor's version, draft and inspector when the automation changes.\n- **A notification link survives login.** Opening a protected task, document or conversation link in a signed-out browser used to land on a bare login page; both dashboard guards now carry the full destination through the login return, and expired-session recovery keeps query values and fragments too.\n- **Loading placeholders match what they replace.** Skeletons mask the real controls and reuse the loaded layouts for forms, tables, cards, chat, tasks, project tabs and document previews — no jump when the data lands — and a quick route transition no longer flashes the top progress bar; the masks follow the theme and the reduced-motion preference.\n\n### A notification export for connected applications\n\n`GET /api/v1/notifications/sync` lets an organization owner or administrator mirror every notification visible to a verified member: the personal and the organization stream, walked independently (`recipientEmail` and `stream` are required; `cursor`, `limit` 1..100 and `locale` optional) with signed cursors scoped to the recipient, the stream and the organization. Each row carries a stable, org-prefixed `id`, a `version` that moves with content or read state, `title`, `body`, `read`, `createdAt` and the `path` the bell itself would open — agent-question task and run links included — relative to the deployment's browser origin, in the requested locale or the organization's default. The export never marks a notification read and never deletes one; a missing, disabled, unverified or ambiguous recipient answers an empty completed page, and a `304` answers an unchanged `ETag`. Contract 1.8.0.\n\n### REST task intake binds the Setup folder by name (#3339)\n\nFolder-driven automations — the VAT return desk among them — read a task's `externalUrl` as the id of the project's Setup folder, and the app's intake resolves that folder by name; the REST door refines `externalUrl` to an absolute URL and had no way to bind a folder, so an external desk on an API key could not use them at all. `POST /api/v1/projects/{id}/tasks` takes an optional `setupFolderName`: the project's root folder of that name (matched without regard to case) is resolved inside the intake transaction — on the create and again on every repeat — and stored as the task's `externalUrl`; a name no root folder carries answers **400** `SETUP_FOLDER_MISSING` and creates nothing, and sent beside `externalUrl` it is refused as `INVALID_BODY`. Contract 1.7.0.\n\n### A managed deployment can change its hostname, and name its containers (#3343, #3344, f4ebf4d4f)\n\nChanging a managed Tale deployment's origin used to fail against the retained bootstrap, email-attestation and OAuth client journals. `identity.migrateOriginFrom: \"https://old.example.org\"` in the deployment declaration binds the exact previous HTTPS origin: the CLI re-authenticates the retained account, verifies the existing client secret and updates only the journals' origin bindings — identity ids and secrets stay — and the native configuration receipt migrates with it, keeping the organization id and slug and verifying every resource through the normal plan and readback flow. Missing, pending or unrelated journals refuse; an interrupted migration resumes with a mixture of completed old- and new-origin journals; a configuration write interrupted at the new origin resumes its exact pending plan, and a pending receipt at the old origin blocks the migration. After the ready receipt, drop the declaration and export consumer configuration for the new issuer; reversing is the same flow with the origins swapped. The CLI install page and the CLI README carry the walk-through.\n\nA managed deployment can also name its containers so an environment is recognisable in a container listing: optional `runtime.containerPrefix` — a lowercase hyphenated slug of at most 40 characters, `north-desk-prod` say — gives every managed service the container name `<prefix>-<service>` (`north-desk-prod-db`, `north-desk-prod-backend-api`) while the deployment's identity, Compose project, state paths and named volumes stay what they were; service DNS names do not change. Adding, changing or removing the prefix recreates the managed containers, which can briefly interrupt service, and readiness requires the observed container names; a rename that is interrupted replays, an unchanged one is a no-op, and a naming conflict refuses. It stays one complete managed runtime per Docker daemon — the prefix allocates no separate ports, sandbox networks or host workspaces.\n\n### Aggregate analytics, off unless you switch it on\n\nThe platform, the web site and the docs site can report aggregate traffic to a self-hosted Umami collector. It is an opt-in per deployment: set `UMAMI_URL`, `UMAMI_WEBSITE_ID` and `UMAMI_PROXY_TOKEN` together — all three, read at runtime, so clearing the website id switches it off without a rebuild; unset, nothing is loaded and nothing is sent. A first-party proxy serves the tracker and forwards only pageviews — and, on the marketing site, completed contact and demo submissions without their contents — with the token held server-side; a private platform route is reported as its template with placeholders for organization and resource ids; no cookies, no persistent identifiers, no page titles, query strings, form contents or session replay; Do Not Track and Global Privacy Control disable it. The edge sets `X-Analytics-Client-IP` from the client address it trusts on the platform and docs upstreams, so a browser-supplied value never counts. The observability page has the rollout check; the privacy policy on tale.dev and the docs' legal page describe what is collected.\n\n### Self-hosted providers on private addresses reach the sandbox gateway\n\nA provider on a private address could not start a sandbox agent turn even with `TALE_ALLOW_PRIVATE_PROVIDER_HOSTS=1` set: the LLM gateway rejected the upstream URL. The platform now sends the gateway's `allow_private_network` setting for a private host that opt-in admits; a custom provider URL passes the host policy before any gateway I/O and before a cached provision is reused; cloud metadata endpoints stay refused (the AWS IPv6 endpoint included), and a public provider keeps the gateway's default safeguards.\n\n## Behaviour changes\n\n- Clicking a rail section opens the place you last had open there (an eight-hour memory, per tab); clicking the section you are in goes to its default entry. `/projects` and the other section roots still render their lists when opened by URL.\n- **Automations**: the list's button reads **Create automation** (was **New automation**); an automation opens on `…/editor` under the **Editor | Versions | Runs** tab strip; a **Versions** row opens the editor at that version (`?version=`); a run page opens under the same strip with **Runs** lit; an unknown automation slug renders **Automation not found**. The breadcrumb switcher lists every organization automation from any project, in two groups.\n- A notification link opened signed-out lands on its task, document or conversation after login instead of on the dashboard; the destination keeps its query and fragment.\n- Loading skeletons take the shape of the loaded page; a quick navigation no longer flashes the progress bar.\n- A OneDrive or Google Drive sync creates its root folder with the sync configuration and moves adopted, unchanged documents into the selected folder on the next scheduled sync.\n- A run that fails on this build answers `failureCode` beside its sentence; cancelling a parked run clears the park detail and the cancel answers the run's `status`.\n- Six simultaneous starts of the same task's automation produce one run; any live duplicates the old race left behind are cancelled by migration 0102 at boot (the earliest run of each group is kept).\n- Deleting a contact and recreating it with the same `externalId` no longer hands the old contact's mirrored conversations to the new one: a snapshot for the trashed contact's conversation is refused with **409** until the contact is restored or the mirror is torn down.\n- Documents a deleted project released (`detach` mode) are findable in the hub again; `retry-indexing` on such a document re-stamps its scope.\n- Knowledge indexing: a document that cannot be indexed reads `unsupported` with a terminal `errorCode`, and a retry on it is refused as terminal; a text file that is really binary no longer costs an embedding before it fails; `.json`, `.yaml` and `.yml` are typed from the extension.\n- Website crawling: a page that stored nothing reads `unsupported_content`, a page answering `X-Robots-Tag: noindex` reads `robots_noindex`, a certificate failure `tls_error`; a same-host link naming a port is not dialed; a scan that stored nothing ends the site on **Error** with the reason (was **Active**).\n- A REST `PATCH` on a contact, product or project that changes nothing writes nothing: `updatedAt` stays, no audit row, no event — a retry is free.\n- The REST **429** body carries a sentence in `error` and a `requestId` (`error` used to repeat the code); the in-app doors are unchanged.\n- An `Idempotency-Key` header sent blank or over 255 characters is refused with **400** `INVALID_HEADER` and nothing starts (a blank used to read as no key).\n- MCP: a blank string argument — whitespace alone included — on any tool is refused at the transport as JSON-RPC `-32602` and no tool runs (0.5.25 refused the empty string on three arguments).\n- A chat reply that settles with no text carries no empty text part; a tool call the reply cap withheld says whether its arguments were cut or complete.\n- The edge answers **400** `BODY_CHUNK_MALFORMED` to a malformed HTTP/1.1 chunked request body (was 502 `UPSTREAM_UNAVAILABLE` with retry advice) once the proxy is recreated (see _Upgrading_); `//api/v1/…` answers the JSON 404.\n- `robots.txt` carves out the developer pages instead of pairing `Allow: /` with `Disallow: /`; the `/docs` page links the developer guides and `/openapi.json`; the user menu's Documentation item opens the docs site; the app shell's `<noscript>` names both.\n- `Permissions-Policy` no longer lists `bluetooth` on the platform, web and docs responses; the \"Unrecognized feature: 'bluetooth'\" console warning on every page load is gone.\n- docs.tale.dev reads in the app language (rail, sticky strip, a drawer on phones); the ancestor-row `aria-current`, the two-Escape drawer and the invisible scrim are fixed.\n- With the three `UMAMI_*` values set, the platform, web and docs sites load a first-party tracker and report pageviews; without them nothing changes.\n- A self-hosted provider on a private host, admitted by `TALE_ALLOW_PRIVATE_PROVIDER_HOSTS=1`, starts sandbox agent turns.\n- `tale deploy` on a managed deployment honours `identity.migrateOriginFrom` and `runtime.containerPrefix`; adding, changing or removing the prefix recreates the managed containers under their new names.\n\n## API contract changes\n\nThe OpenAPI document moves from **1.6.0** to **1.9.0** in three additive steps, each dated 2026-09-14; the `Error.code` enum grows from 145 to 149 values (`BODY_CHUNK_MALFORMED`, `CONVERSATION_CONTACT_TRASHED`, `PRECONDITION_FAILED`, `SETUP_FOLDER_MISSING`). Nothing was removed or renamed.\n\n**Added**\n\n- 1.7.0 — `POST /projects/{id}/tasks` takes `setupFolderName` (optional; sent beside `externalUrl` it answers **400** `INVALID_BODY`); **400** `SETUP_FOLDER_MISSING` when no root folder of the project carries the name.\n- 1.8.0 — `GET /notifications/sync` (`recipientEmail` and `stream` required; `cursor`, `limit` 1..100, `locale`): a keyset page (`page`, `isDone`, `continueCursor`, `recipientId`) of one member's personal or organization notifications, each with a stable `id`, a `version`, `title`, `body`, `path`, `read` and `createdAt`; owner or administrator only; **200**, **304** on an unchanged `ETag`.\n- 1.9.0 — `GET /projects/{id}/files/{documentId}`: one project file row with its `indexing` state, an `ETag` and **304**; `Run.failureCode` and `RunSummary.failureCode` (present with `status: \"failed\"` on a run that failed on this build or later); `Me.capabilities.developer`; `externalContactId` and `contactStatus` on the conversation-sync receipt; **409** `CONVERSATION_CONTACT_TRASHED` on a content snapshot for a trashed contact; `If-Match` on `PATCH /documents/{id}` → **412** `PRECONDITION_FAILED` with `data.etag`; `WebsitePage.lastErrorKind` gains `unsupported_content`, `robots_noindex` and `tls_error`; the edge's own **400** `BODY_CHUNK_MALFORMED`.\n\n**Changed**\n\n- **429** — `error` is a sentence naming the wait and the envelope carries `requestId` (was `error: \"RATE_LIMITED\"`); `code`, `Retry-After` and `data.retryAfterMs` are unchanged.\n- `Idempotency-Key` — blank, or over 255 characters, answers **400** `INVALID_HEADER` and nothing starts (a blank was read as no key).\n- `POST /contacts/bulk` — rows are created independently: a row the schema refuses fails alone, under `errors[]` as `INVALID_BODY` with its field-named `issues`, while every valid row lands (**201**); only the batch's own shape refuses the whole call.\n- `GET /contacts?source=` — the closed set; a value outside it answers **400** `INVALID_QUERY`.\n- `Website.status` — the enum `idle`, `scanning`, `active`, `error`, `deleting`; `GET /websites?status=` refuses a value outside it with **400** `INVALID_QUERY`.\n- `Product.imageUrl` — held to the crawl-target host rule as a string, never fetched: a loopback, link-local, private-network or cloud-metadata host answers **400** `INVALID_BODY` (private hosts admitted by `TALE_ALLOW_PRIVATE_CRAWL_HOSTS`, metadata hosts never).\n- `PATCH /contacts/{id}`, `PATCH /products/{id}`, `PATCH /projects/{id}` — a body that changes nothing writes nothing and answers the current representation; `updatedAt` does not move.\n- `Automation.document.version` — the schema declares the integer the wire always carried.\n- `indexing.errorCode` — the closed vocabulary of thirteen values, present with `failed` and `unsupported`; `retry-indexing` on an `unsupported` document answers `{\"status\": \"skipped\", \"reason\": \"unsupported\"}`.\n- `POST /runs/{runId}/cancel` and `POST /projects/{id}/runs/{runId}/cancel` — answer the run's `status` beside `cancelled`, and a cancel clears a parked run's `detail`.\n- The MCP endpoint — every string argument on every tool refuses a blank, whitespace alone included, as JSON-RPC `-32602` at the transport (0.5.25 held `name`, `runId` and `query` to `minLength: 1`).\n- Chat `Message` parts — a reply that settled with no text carries no empty `text` part; a `tool-call` part the reply cap withheld says whether its arguments were cut or complete.\n\n**Documented, unchanged on the wire** (en, de, fr): webhook delivery dedupe is by the delivery id, never the body, with the header precedence list on the parameter and the **202**; automation authoring lives on the MCP endpoint, in the app and in `tale deploy` (the Automations tag and the reference row say so); accepted thread sends form one oldest-first queue shared by the whole deployment, worked in batches of up to five turns (the operator's `WORKER_CONCURRENCY`), so a burst completes in waves; `/health` on a deployment's origin is the proxy's own liveness and a path no route owns answers the app shell with **200** — monitor `/status.json` or `/api/health`; the crawler's ceilings (10,000 tracked URLs per site, a scan of at most 200 five-minute links, 25 MB and 30 seconds per page, a discovered page dropped after five failed scans) and that it honours `robots.txt` `Disallow` for the `*` agent while discovering, never for a URL you listed; website search is BM25 with `score` semantics; what `startedAt` means on a run; `Contact.email` and `Product.imageUrl` carry their formats; the compression floor sits in the document's description; the keyless `jq` recipe over the `Error.code` enum; the tutorial's key placeholder reads `<api-key>`.\n\n## Known issues\n\n- 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.\n- 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.\n- **The proxy changes were not exercised on a running deployment.** The chunked-body rule (`BODY_CHUNK_MALFORMED`) and the `X-Analytics-Client-IP` header this release adds to the edge went into a `tale-proxy` image no deployment has run yet, and `services/proxy` has no test of its own; the manual register carries the `printf` recipe that provokes the refusal. The 0.5.25 gap (the one-year HSTS value on the edge's own refusals) is closed: the hosted platform's edge has answered `max-age=31536000` on its own **404** since 2026-09-14.\n- **The rail's navigation memory awaits its manual round**: restore, re-entry, expiry, a deleted target and two-tab isolation are manual boxes (`NAV-F16`–`NAV-F19`, `NAV-B6`–`NAV-B9`); the automated suites cover the store and the rail's resolution, not the browser choreography.\n- The docs screenshots `automations-catalog` and `automation-editor-canvas` still show the previous automations layout; the text beside them is current.\n- **ui.tale.dev is not live at this tag.** `tale-ui-docs:0.5.26` exists from this release, but the site goes live with the operator's deployment of it; until then the link on the contributor-setup page does not resolve. Nineteen pages seed the site — one page per remaining component family is follow-up work — and the docs chrome is duplicated between the docs site and the design-system site rather than shared.\n- 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.\n- **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.\n- **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.\n- 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.\n- A tool call the reply cap cut keeps `input: {}` on the stored `tool-call` part; the part now says its arguments were cut, but the raw text the model emitted is still not on the transcript.\n- 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.\n- 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.\n- **Recorded as contract debt by the seventh evaluation, each with its design in the ledger:** a run carries no `usage` or cost; approvals and asks have no REST twins (a run parked on `waitingFor: approval` or `ask` is decided in the app); 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`, `<meta name=\"robots\" content=\"noindex\">` is not honoured (the header is) 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 (a knowledge database without ParadeDB) stamps `score: 0` silently; no `Idempotency-Key` on the task start, so a retry after the run finished starts another; no queue position on a queued send; a corrupt Office document (`docx`, `pptx`, `xlsx`, `odt`) 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.\n\n## Migration notes\n\n- **Two platform migrations, both forward-only and rolling-safe**, applied at boot in filename order inside the advisory lock while the previous image keeps serving:\n  - **0102 `automation_runs_one_live_per_task`** first collapses any duplicate live runs (`queued`, `running`, `waiting`) that share an organization, an automation, a project and a task subject onto the earliest-started run, marking the rest `cancelled` — the race's own residue; a correct platform never produced them — then adds a partial unique index over that key. The previous image cannot violate it: it only ever starts the runs it already started.\n  - **0103 `automation_runs.failure_code`** adds a nullable text column with no backfill: a run that failed before this release keeps its sentence and answers no `failureCode` (read the absence as \"unknown\"). The previous image neither reads nor writes it.\n  - **No knowledge-database migration.** 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.\n- **Three new optional environment variables** — `UMAMI_URL`, `UMAMI_WEBSITE_ID`, `UMAMI_PROXY_TOKEN` — in the root `.env.example` (the platform) and in the web and docs sites' own. All three unset means analytics off, which is the default and the previous behaviour; no configuration file changes shape. A managed deployment carries them through its runtime environment when they are declared.\n- **The proxy image changes** — the chunked-body **400** and the `X-Analytics-Client-IP` header it sets on the platform and docs upstreams. 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 rules (a brief downtime while `db`, `object-store` and `proxy` recreate); until then a malformed chunked body still gets 0.5.25's 502 envelope. An own-Compose deployment pulls the new `tale-proxy` tag.\n- **A new image, `tale-ui-docs`**, joins the release matrix — eleven images, amd64 and arm64 — with its own compose file (`compose.ui-docs.yml`) and container test. It is not part of the platform stack; nothing pulls it unless you deploy the design-system site.\n- The platform (the backend, the app, and the message catalogs the notification export reads), proxy, web (the marketing-language package, the analytics opt-in, the privacy policy) and docs (the app-language chrome, the updated pages in en, de and fr) images carry source changes; the db, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have none. The CLI has source changes — `identity.migrateOriginFrom`, `runtime.containerPrefix` and the analytics pass-through — and the release executables report 0.5.26.\n- **`@tale/ui` and `@tale/marketing-ui` are pinned by this release** as the `ui-v0.5.26` and `marketing-ui-v0.5.26` tags on their snapshot branches; a consumer outside the monorepo installs `\"@tale/ui\": \"github:tale-project/tale#ui-v0.5.26\"`.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.25):\n\n  ```bash\n  tale update\n  tale deploy --stop\n  ```\n\n  The two migrations run at boot. `--stop` recreates `db`, `object-store` and `proxy` so the edge carries the new rules (a brief downtime); a plain `tale deploy` applies everything else and leaves the running proxy on 0.5.25's. To switch analytics on, set the three `UMAMI_*` values in `.env` before deploying; leave them unset to keep the previous behaviour.\n\n- **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. A hostname change is a deployment with `identity.migrateOriginFrom` declared, as above. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(platform): keep all automations reachable in the switcher by @yannickmonney in https://github.com/tale-project/tale/pull/3338\n- fix(platform): let REST task intake bind the setup folder by name by @yannickmonney in https://github.com/tale-project/tale/pull/3339\n- fix(ui): align loading skeletons with content layouts by @yannickmonney in https://github.com/tale-project/tale/pull/3340\n- feat(platform): export recipient-scoped notifications by @yannickmonney in https://github.com/tale-project/tale/commit/d1edf522c50c4b868119d7d2e722740220806e35\n- feat(cli): migrate managed deployment hostnames by @yannickmonney in https://github.com/tale-project/tale/pull/3343\n- fix(cli): migrate retained native configuration origins by @yannickmonney in https://github.com/tale-project/tale/pull/3344\n- feat(platform): give automations the projects page structure by @yannickmonney in https://github.com/tale-project/tale/pull/3345\n- fix(platform): retain notification destinations through login by @yannickmonney in https://github.com/tale-project/tale/pull/3346\n- feat(ui): connect optional deployment analytics by @yannickmonney in https://github.com/tale-project/tale/commit/2d62456230afd79d2fd2a10cf11f955815c755aa\n- fix(platform): admit self-hosted providers to the sandbox gateway by @yannickmonney in https://github.com/tale-project/tale/commit/eb50e2d201df57f991b8d699bcfb07d1e241d474\n- feat(platform): return to the last place in each nav section by @Israeltheminer in https://github.com/tale-project/tale/pull/3347\n- fix(platform): close the 2026-09-14 API evaluation's seventh-pass findings by @larryro in https://github.com/tale-project/tale/pull/3353\n- feat(ui): fold the platform UI into @tale/ui and add ui.tale.dev by @yannickmonney in https://github.com/tale-project/tale/pull/3351\n- feat(cli): name managed containers independently of storage by @yannickmonney in https://github.com/tale-project/tale/commit/f4ebf4d4f5b3412f5c98dc49e3cb1c321ae03488\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.25...v0.5.26","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.26","publishedAt":"2026-09-14T12:49:37Z"},{"tag":"v0.5.25","version":"0.5.25","name":null,"body":"**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.\n\n## Highlights\n\n### The sixth API evaluation pass (#3337)\n\nThe 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:\n\n- **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.\n- **`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.\n- **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`.\n- **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.\n- **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.\n\n### The web site's changelog prerenders by size, not by count (#3337)\n\nThe 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.\n\n## Behaviour changes\n\n- `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`.\n- 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.\n- The MCP developer-gate refusal on `save_automation`, `deploy_automation` and `set_trigger` carries `code: \"FORBIDDEN_DEVELOPER_SETTINGS\"` beside `error` and `hint`.\n- 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).\n- The web site's changelog, in each language, prerenders only the newest release bodies within the byte budget; the rest render on hydration.\n\n## API contract changes\n\nThe 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.\n\n**Changed**\n\n- `Strict-Transport-Security` — `max-age=31536000` on every HTTPS response of every door (was `max-age=15552000`).\n- 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.\n- 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.\n- `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.\n\n**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.\n\n## Known issues\n\n- 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.\n- 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.\n- **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.\n- 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.\n- **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.\n- **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.\n- 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.\n- 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.\n- 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.\n- 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.\n- 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.\n\n## Migration notes\n\n- **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.\n- **No configuration-file change and no new environment variable**; `.env.example` is untouched.\n- **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.\n- **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.\n- 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.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.24):\n\n  ```bash\n  tale update\n  tale deploy --stop\n  ```\n\n  `--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.\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(platform): close the 2026-09-14 API evaluation's sixth-pass findings by @larryro in https://github.com/tale-project/tale/pull/3337\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.24...v0.5.25","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.25","publishedAt":"2026-09-14T04:17:43Z"},{"tag":"v0.5.24","version":"0.5.24","name":null,"body":"**0.5.24 is a fix release on the 0.5 line that carries more than its fixes: the fifth set of REST contract corrections from an external API evaluation (contract 1.5.0 → 1.6.0), JSON refusals at the edge where a machine caller used to get the HTML maintenance page, a reply cap that bounds a whole chat turn, and a repaired managed on-premises deployment path in the CLI.** Two platform migrations (0100, 0101) apply themselves on `tale deploy`, each an additive nullable column written to run while the previous image is still serving. No knowledge-database migration, no configuration-file change and no new environment variable. The proxy image changes and a plain `tale deploy` does not roll it — see _Upgrading_. The Known issues from 0.5.20 are unchanged; the two verification gaps 0.5.23 listed are closed, and this release's own are listed with them.\n\n## Highlights\n\n### The fifth API evaluation pass (#3335)\n\nThe fifth external black-box evaluation of the REST and MCP API ran against 0.5.23 (~1,800 requests, no backend 5xx) and reported 0 S1, 2 S2, ~13 S3 and ~15 S4 findings. Every item was second-confirmed from source before a fix was designed. The headline S2 — an intermittently slow or failing TLS handshake over IPv4 while IPv6 was clean — was the evaluating machine, not the platform: a tunnel interface on that host captured every locally originated IPv4 flow while IPv6 left directly, and bound to the physical interface the IPv4 handshake was as fast and as reliable as IPv6 (40 of 40, about half a second). No platform change. The second S2, a listed model the provider's plan did not cover, was closed in the operator's model allowlist during the round, where 0.5.23's notes had placed it. The evaluation also drove the project-chat scope 0.5.23 shipped on live model turns — a project thread reads its project's file, the organization thread stays on the hub — which closes the verification gap those notes listed. What did hold is fixed here; every wire change is listed under _API contract changes_.\n\n- **Edge.** A truncated HTTP/2 upload — a declared `Content-Length` larger than the bytes sent — made the edge answer the HTML maintenance page with a 502, under the body cap too, so the platform never saw the request; it now answers **400** `BODY_LENGTH_MISMATCH` in the JSON envelope. While the platform restarts, a machine door (`/api/*`, `/scim/*`, `/http_api/*`, `/events`, `/status.json`, `/openapi.json`, `/.well-known/*`) gets a JSON `UPSTREAM_UNAVAILABLE` with the error's own status (502, 503 or 504) and `Retry-After: 5`, where a browser navigation still gets the maintenance page. A `HEAD` is never compressed, so it reports the uncompressed `Content-Length` the `GET` would carry (the encoder used to stamp the length of an empty gzip frame). The dot-segment rule also refuses a raw URI whose dots fold _into_ the API prefix (`/x/../api/v1/me`), which the backend's own parser folded and served.\n- **Door.** `Idempotency-Key` is printable ASCII, enforced: any other character answers **400** `INVALID_HEADER` on run starts and chat sends (the pattern was declared, not enforced, so two spellings of `é` named two starts of one retry). Starting a task's workflow answers **404** `AUTOMATION_NOT_FOUND` or **409** `AUTOMATION_NOT_DEPLOYED` where it answered `200 {started: false, reason: \"not_started\"}`. The upload mint and the bind require a file name that ends in an allowlisted extension whatever `contentType` declares — `CON`, an extension-less `attachment-4711` and `program.exe` declared as `text/plain` all got in through the MIME half of the check — and the app's document pickers inherit the rule. File and folder names are stored trimmed and NFC-normalized; `FOLDER_NAME_INVALID` names the rule broken and carries `data.issues`. A website search `limit` outside 1..100 is refused with **400** `INVALID_BODY` instead of clamped. `HEAD` on `/api/health`, `/status`, `/status.json` and `/openapi.json` carries the `GET`'s `Content-Length`; the two webhook doors answer `X-Tale-Api-Version`; the app-wide guard refusals (the `x-api-key` 401, the NUL-in-URL 400) carry `Cache-Control: no-store`.\n- **Chat.** `maxOutputTokens` bounds the whole turn across model rounds: a later tool round gets what the earlier ones left, a round that would start with nothing left is not run, `finishReason: \"length\"` marks the reply whenever any round was cut, and a tool call the cap cut mid-arguments is not executed — its result reads `status: \"invalid_args\"` (it used to run with whatever arguments fitted, and the next round settled `stop` over a cut reply). 0.5.23 said `locale` pins the reply language; the evaluation measured a hint (four German replies out of six). The directive now rides the system prompt, naming the language, and the message itself, where a reasoning model on a short prompt looks; the reference says \"asked to answer in\" and tells a client that must have the language to check the reply and resend. `costEstimateCents` is rounded to a millionth of a cent (the rates' binary noise, `0.042601999999999994`, reached the wire and the ledger). The assistant is told to search before answering that it does not know a term.\n- **Projects, tasks, automations, skills, documents.** `POST /projects/{id}/files/{documentId}/retry-indexing` indexes a REST-bound project file without delete and re-bind — 0.5.23's notes pointed at the Hub document's retry, which answers **404** for a project file. `externalState: \"open\"` reopens a task the mirror itself parked at `in_review` (migration 0101 stamps such a park; a park a person or an agent made stays theirs) — a mirror that closed an item and reopened it upstream used to leave the card parked for good. `testsPassed` reads `false` after the deploy gate refused a version (it persisted nothing, so a release pipeline could not tell \"no tests\" from \"the tests fail\"), `testsCheckedAt` says when (migration 0100), and the MCP `save_automation` records the save's own test run. A run summary carries `id` beside `runId`. An identical `PUT /skills/{slug}` writes nothing — the history trail no longer evicts real versions for a mirror's re-push. A content-only document download honours `If-Modified-Since` (it re-downloaded every inline document on every poll), and `GET /documents/{id}/content` declares its conditional headers and 304.\n\n### Managed on-premises deployments repaired (#3333, #3336)\n\nA managed deployment's backend-local phases — `deploy provision`, and the native half of `deploy export-client` — run inside the backend container and import the backend's own database and auth modules, which import packages from the backend's `node_modules`. They ran as the compiled executable, which resolves bare imports against its embedded filesystem, so they failed with `Cannot find package 'postgres'`, swallowed twice and surfaced as an operator email attestation failure (or an export failure), which sent debugging the wrong way. Every managed bundle now ships an interpreted build of the CLI, `cli/tale.mjs`, beside the executable under the same manifest hashes, and both phases run under the container's own bun, so the imports resolve the way the running backend's do. The CLI also requested the provider-credentials collection with a trailing slash the backend does not serve (a 404); the three collection calls use the slashless route. Both were verified live on a real on-premises backend and locked with regression tests.\n\n## Behaviour changes\n\n- A 502, 503 or 504 at the edge answers JSON to a machine door and the maintenance page to a browser; a client that parses JSON no longer meets 4 KiB of markup mid-roll. A body shorter than its declared length over HTTP/2 is a 400, not an outage.\n- A `HEAD` through the edge is never compressed; its `Content-Length` is the uncompressed length.\n- A file name without an extension, or with one outside the allowlist, is refused at the mint, at the bind and in the app's upload and replacement pickers, whatever MIME type is declared; a declared type never stands in for the extension.\n- File and folder names are stored trimmed and NFC-normalized from now on; rows written before keep their bytes (no backfill).\n- Starting a task's workflow on a missing or undeployed automation is a refusal, not a 200 that started nothing; `reason: \"not_started\"` is left for a deployment withdrawn between the check and the start.\n- A tool-calling chat turn spends `maxOutputTokens` round by round, a cut call is not run, and `length` is reported when any round was cut.\n- A REST send with `locale` carries the reply-language directive on the system prompt and on the message; the app's chat is unchanged.\n- Re-pushing an unchanged skill bundle through `PUT /skills/{slug}` leaves `etag` and `updatedAt` alone and writes no history entry.\n- A website search `limit` out of range is refused, not clamped.\n- An external `open` reopens a task the mirror parked; any move through the board's own doors ends the mirror's claim on that park.\n- The deploy gate persists a failed test verdict; the latest verdict wins over the one recorded at save time.\n- An `Idempotency-Key` outside printable ASCII starts nothing.\n- `costEstimateCents` on a chat message and in the usage ledger is rounded to a millionth of a cent.\n\n## API contract changes\n\nThe OpenAPI document moves to **1.6.0**; every response the platform answers names it in `X-Tale-Api-Version`. A refusal answered at the edge (the dot-segment 404, `BODY_LENGTH_MISMATCH`, `UPSTREAM_UNAVAILABLE`) carries a fresh `requestId` of its own and no version header: only the platform knows the contract it implements.\n\n**Changed**\n\n- `Idempotency-Key` (run starts, chat sends) — a value outside printable ASCII (0x20–0x7E) answers **400** `INVALID_HEADER`, the header named under `data.issues`; nothing starts.\n- `POST /projects/{id}/tasks/{taskId}/start` — a `workflowSlug` that names no automation answers **404** `AUTOMATION_NOT_FOUND`; a saved-but-undeployed one **409** `AUTOMATION_NOT_DEPLOYED` (was **200** `{started: false, reason: \"not_started\"}`), judged before the execute budget is charged.\n- `POST /projects/{id}/uploads` and `POST /projects/{id}/files` — a `fileName` without an allowlisted extension answers **400** `UNSUPPORTED_FILE_TYPE` whatever `contentType` says; `fileName` is stored trimmed and NFC-normalized.\n- `POST /projects/{id}/folders` — `name` is stored trimmed and NFC-normalized; **400** `FOLDER_NAME_INVALID` carries `data.issues` naming `name` and a sentence naming the rule.\n- `POST /websites/{id}/search` — `limit` outside 1..100 answers **400** `INVALID_BODY` (was clamped).\n- `POST …/messages` — `maxOutputTokens` bounds the whole turn; `finishReason: \"length\"` whenever any round was cut; a cut call's `tool-result` part reads `status: \"invalid_args\"`; `locale` is a directive on the system prompt and on the message; `costEstimateCents` is rounded to a millionth of a cent.\n- `PUT /skills/{slug}` — a composed `SKILL.md` byte-identical to the stored one writes nothing: **200** with the stored `etag` and `updatedAt`, no history entry; `If-Match` and `If-None-Match: *` are still evaluated first.\n- `GET /documents/{id}/content` (content-only document) — `If-Modified-Since` is honoured against `Last-Modified` at whole-second precision; `If-None-Match` decides alone when both travel.\n- `HEAD` — never compressed through the edge (uncompressed `Content-Length`); on `/api/health`, `/status`, `/status.json` and `/openapi.json` it carries the `GET`'s `Content-Length`.\n- `POST /api/automations/webhook/{token}` and the project twin — every response carries `X-Tale-Api-Version`. The `x-api-key` 401 and the NUL-in-URL 400 carry `Cache-Control: no-store`.\n- `GET /automations/{name}/versions` — `testsPassed` reads `false` after the deploy gate refused a version (was `null`); the latest verdict wins.\n- Raw dot-segments — a URI whose dots fold into the API prefix (`/x/../api/v1/me`) is refused at the edge with **404** `NOT_FOUND` like one whose dots fold out of it.\n\n**Added**\n\n- `POST /projects/{id}/files/{documentId}/retry-indexing` — **200** `{status: \"indexing\"}` (lifting the bind-time `skipRagIndexing` opt-out) or `{status: \"skipped\", reason}`, under the same 10-per-user-per-minute budget as the Hub document's retry (**429** `RATE_LIMITED`).\n- `AutomationVersion.testsCheckedAt` and `Automation.testsCheckedAt` — epoch milliseconds when `testsPassed` was judged, `null` with it.\n- `RunSummary.id` — the run id beside `runId`, on the REST listings and MCP `list_runs`.\n- `POST /projects/{id}/tasks` — `externalState: \"open\"` reopens a task the mirror parked at `in_review`, or a `done` one, to `backlog`.\n- **400** `BODY_LENGTH_MISMATCH` — answered at the edge for an HTTP/2 body that ended before its declared `Content-Length`.\n- **502**, **503**, **504** `UPSTREAM_UNAVAILABLE` — answered at the edge on every machine door while the platform cannot be reached, with `Retry-After: 5`.\n- `GET /documents/{id}/content` declares `If-None-Match`, `If-Modified-Since` and its **304**.\n- `Error.code` gains `INVALID_HEADER`, `BODY_LENGTH_MISMATCH` and `UPSTREAM_UNAVAILABLE`.\n\n**Documented, unchanged on the wire** (en, de, fr): the 64 KiB header budget allows a few KiB of slack on HTTP/1.1 (a 66 KiB URL still answers **414**); a never-bound automation runs in any project the caller can edit; an automation's runs outlive its deletion; `GET …/triggers` answers `triggers`, a list of at most one; the duplicate **409** codes are named (`CONTACT_DUPLICATE_EMAIL`, `CONTACT_DUPLICATE_EXTERNAL_ID`, `DUPLICATE_PRODUCT_NAME`, `DUPLICATE_PRODUCT_EXTERNAL_ID`, `KNOWLEDGE_ENTRY_DUPLICATE`, `PROJECT_DUPLICATE_EXTERNAL_ID`); a `%00` in the URL is `INVALID_URL` ahead of every route; **422** `SKILL_MALFORMED` is about the bundle already stored, never the JSON body you send; `KNOWLEDGE_ENTRY_STORE_TIMEOUT`; a keyless request to an unknown `/api/v1` path answers **401** before **404**; the webhook door takes `POST` only and answers its **404** to every other verb; `GET /models` omits `maxOutputTokens` when the catalog declares no ceiling; a model failure's `error` is the provider's own answer prefixed with its HTTP status, so branch on `errorCode`; `similarity` is a scale, not a calibrated confidence; the Own Compose page explains IPv6 behind a Docker-published port and `trustedProxies`.\n\n## Known issues\n\n- 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.\n- 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.\n- **The proxy changes were exercised through the real entrypoint in a local Caddy 2.11 container** (a short HTTP/2 body over and under the cap, the platform down, `HEAD` with gzip, the dot-segment spellings), not on a running deployment's edge.\n- A reply-language directive is a directive: a model may still answer in the prompt's language (most often a reasoning model on a short prompt), and nothing on the wire marks a slip.\n- 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.\n- 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.\n- 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.\n- Folder names written before this release keep their bytes; a sync engine's hub-path lookup can create an NFC twin beside a legacy NFD folder. No backfill ships.\n- 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.\n\n## Migration notes\n\n- **Two platform migrations apply on the first boot of the new images**, each an additive nullable column with no backfill that the previous image neither reads nor writes:\n  - `0100_automations_tests_checked_at` adds `tests_checked_at_ms` to `app.automations` — when a version's test verdict was reached; `null` for a version saved without one, and an existing verdict has no known time.\n  - `0101_tasks_external_closed_at` adds `external_closed_at_ms` to `app.tasks` — the stamp of a park the mirror made; an existing park reads as a person's until the next external close stamps it.\n- **No knowledge-database migration** in this range.\n- **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.\n- **The proxy image changes** (the error routes, the encoder skipping `HEAD`, the widened dot-segment rule in the entrypoint). 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 edge rules (a brief downtime while `db`, `object-store` and `proxy` recreate); until then the edge keeps 0.5.23's rules while the platform's own changes apply. An own-Compose deployment pulls the new `tale-proxy` tag.\n- **A managed bundle now carries `cli/tale.mjs`**, the interpreted CLI, beside `cli/tale`. `deploy prepare` needs that file beside the executable it runs as, which the `setup-cli` action and `bun run --filter @tale/cli build` produce at this commit; the executable from the release page has no interpreted bundle beside it and cannot prepare a managed bundle — build the CLI from the pinned commit, as the CLI install page describes.\n- **The upload allowlist keys on the file name alone** from now on; a client that relied on a declared MIME type for an extension-less name must name the file with its extension. Files already stored are untouched.\n- **No configuration-file change and no new environment variable**; `.env.example` is untouched. The db, web, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have no source change in this range; the docs image carries the updated pages in all three languages.\n- **Operators of the hosted platform**: the IPv6 item above is a Docker daemon setting on the host (`ip6tables`, no userland proxy, an IPv6-enabled proxy network), not a platform release.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.23):\n\n  ```bash\n  tale update\n  tale deploy --stop\n  ```\n\n  `--stop` recreates `db`, `object-store` and `proxy` so the new edge rules take effect (a brief downtime); a plain `tale deploy` applies everything else and leaves the running proxy on 0.5.23's rules. The migrations above apply on the first boot of the new images.\n\n- **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. Pin the CLI at this commit, not an older one: the bundle's backend-local phases run under the interpreted CLI that #3333 ships. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n\n- fix(cli): repair on-prem deploy provision and credential path by @yannickmonney in https://github.com/tale-project/tale/pull/3333\n- fix(cli): run backend-local client export under interpreted bun by @yannickmonney in https://github.com/tale-project/tale/pull/3336\n- fix(platform): close the 2026-09-13 API evaluation's fifth-pass findings by @larryro in https://github.com/tale-project/tale/pull/3335\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.23...v0.5.24","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.24","publishedAt":"2026-09-14T00:58:42Z"},{"tag":"v0.5.23","version":"0.5.23","name":null,"body":"**0.5.23 is a fix release on the 0.5 line that carries more than its fixes: the fourth set of REST contract corrections from an external API evaluation (contract 1.4.0 → 1.5.0), a repaired and now enforced rule for caller-owned external keys, observable crawl failures, an honest 408 at the door, and a server-pinned scope for the chat assistant inside a project.** Two platform migrations (0098, 0099) and one knowledge-database migration apply themselves on `tale deploy`, each written to run while the previous image is still serving. No configuration-file change and no new environment variable. The Known issues from 0.5.22 are unchanged; two verification gaps are listed with them.\n\n## Highlights\n\n### The fourth API evaluation pass (#3334)\n\nThe fourth external black-box evaluation of the REST and MCP API ran against 0.5.22 (~600 requests, no 5xx) and reported 0 S1, 3 S2, ~18 S3 and ~15 S4 findings. Every item was second-confirmed from source before a fix was designed. Several of the report's headline findings did not hold: the \"SSRF through a 302 redirect\" is not one (the fetch client validates every hop before it dials, now proven again by an integration lane with a loopback listener that receives nothing), the listed-but-unusable model is the documented \"catalog, not an account promise\" and belongs to the operator's model allowlist, the \"unknown 409\" was in the OpenAPI document, the OIDC endpoints were documented, and the slow inline document was the tester's uplink. What did hold is fixed here; every wire change is listed under _API contract changes_.\n\n- **Door and edge.** The proxy no longer answers 414 itself: the backend guard is the one producer, and its envelope carries `requestId` and `X-Tale-Api-Version` like every other refusal — the `x-api-key` 401 and the NUL 400 carry the door headers too. A request must finish arriving within 15 minutes (Node's default was five, answered as a bare 408 with no body); a slower one answers **408** `REQUEST_TIMEOUT` in the envelope. `PUT /skills/{slug}` answers **201** when it creates; the skills 304 is decided by the validated-read lane like every other JSON read. The webhook door answers `Cache-Control: no-store`.\n- **Pagination.** Every keyset list now answers `continueCursor`; `GET /projects` and `GET /projects/{id}/files` keep their `cursor` as a deprecated twin, and the website pages list gains `isDone`, `continueCursor` and `?cursor=` beside its offset window. Every list schema declares its family as `x-tale-pagination: keyset | offset | none`, the spec's own guard holds the families together, and `INVALID_LIMIT` / `INVALID_CURSOR` name their parameter under `data.issues`.\n- **Validation.** A blank project `key`, a blank folder `parentId`, a folder name carrying a control character or a backslash, and an empty `contacts/bulk` batch answer **400**; the folder rule is one shared character class the file-name and WebDAV checks use too. A malformed or foreign `storageId` on a conversation sync answers **400** `ATTACHMENT_NOT_STAGED` instead of an undeclared 403.\n- **Chat.** A `locale` on the REST send now pins the reply language (the field was wired, but the runtime directive told the model to follow the user's language, the opposite of what the reference promised). The generation poll slices `reasoning` with `?reasoningSince=` the way it slices `text`; `DELETE …/generation` stops a send that is still queued (202 `cancelling`, settling as a `cancelled` reply) and names the last reply on its 404; the tutorial's Python sample keeps the reply id and checks `status` (it used to print an empty string on any failed turn and read the wrong row on a long thread).\n- **Knowledge and websites.** Superseding a knowledge entry with an identical body writes no version. A page the crawler could not store now says why — `failCount`, `lastError`, `lastErrorKind`, `lastErrorAt` on the page, `failedPageCount` on the website — and a listed page is never dead forever (it used to leave the crawl after five failed attempts with no way back). Every operation with its own body cap declares it in its 413 text. The REST `retry-indexing` door shares the app's guards: an in-flight or unsupported file is `skipped` with its reason, and an explicit retry opts a bind-time-skipped file back in.\n- **Projects, tasks, automations.** Migration 0098 repairs the external-key twins migration 0093 left behind (a project stored with a decomposed `é` beside a newer composed twin could not be found by `GET /projects?externalItemId=` under either spelling) and encodes the canonical rule as unique indexes on projects and tasks. A task whose `automationSlug` names a saved-but-undeployed automation answers **409** `AUTOMATION_NOT_DEPLOYED` (it was a 404 that said \"not deployed\"). The automation summary carries its trigger's health (`lastFiredAt`, `lastSkippedAt`, `lastSkipReason`) and `PUT …/triggers` says whether the automation is `deployed`; project files carry `size` and `indexing`, the bind answers `size` and `mimeType`, and a task carries `archivedAt`.\n\n### The chat assistant's scope inside a project (#3334)\n\nA chat started inside a project listed only the organization's hub documents, had to list every project to find its own, and could not read a project file that had never been indexed. The documentation already promised the opposite, so the platform now keeps that promise on the server, not by trusting the model: the thread's project is the boundary of `rag_search` and `rag_fetch`. A project chat reaches its project's files plus the organization's knowledge hub as the person sees it; the organization chat reaches the hub only; the assistant's task and project questions inside a project stay on that project's board, and another project's id is refused. The model in one paragraph, now in the docs, the prompt and the tool descriptions: a document lives in exactly one place, the hub or one project; team tags decide who sees a hub document; project access alone decides who sees a project file, which never carries team tags.\n\n- The document listing carries each row's `scope`, `project` and `indexing` state, so an unindexed file is visibly unreadable by search.\n- `rag_fetch` reads a never-indexed plain-text file of up to 4 MiB on request and otherwise names the file and its true state (skipped by the uploader, pending, failed with its error, unsupported) instead of \"may not be indexed yet\". One shared reader serves the chat and the sandbox bridge.\n- The project's **Knowledge** tab shows **Not indexed** for a file bound without indexing and offers **Index now**; the timeline step never shows a raw blob reference.\n\n## Behaviour changes\n\n- The organization chat no longer reaches project files through the assistant's tools; project files are read from the project's own chat. A project chat's task and project listings are that project's.\n- A project chat's system prompt names the project and its key and states the boundary; the \"prefer its material, but anything the user can read\" clause is gone.\n- A REST send with `locale` answers in that language whatever language the prompt is written in; a send without it answers in the prompt's language as before. The app's chat is unchanged.\n- An explicit re-index of a file bound with `skipRagIndexing: true` — **Index now** on the Knowledge tab or `POST /documents/{id}/retry-indexing` — opts the file into indexing; before, the request was refused as `rag-opt-out`.\n- A page the crawler failed on keeps its `status` and carries the failure beside it; a listed URL is probed once per scan even after five failures; an extraction failure counts as a failure, not a visit.\n- The backend accepts a request body for up to 15 minutes; a client slower than that gets a JSON 408 and a closed connection, where it used to get a bare status line after five minutes.\n- The proxy's 32 KiB API-URL rule is gone; URLs between 32 and 64 KiB reach the backend and answer its 414, and above 64 KiB the edge's header budget still closes the connection as documented.\n- A project or task whose external key was one of two normalisation twins has been released by migration 0098 (its `externalItemId`, or the task's `externalSystem`/`externalId`, reads empty); `PATCH /projects/{id}` re-keys a project.\n- A hidden regenerate-sibling thread is no longer readable or editable by id through the REST door.\n\n## API contract changes\n\nThe OpenAPI document moves to **1.5.0**; every `/api/v1` response names it in `X-Tale-Api-Version`.\n\n**Changed**\n\n- `POST /projects` — a `key` that is blank once trimmed answers **400** `INVALID_BODY` (it was derived from the name; the reference had promised the 400).\n- `POST /projects/{id}/folders` — a blank `parentId` answers **400** `INVALID_BODY` (was an opaque 404); a `name` carrying a control character or `\\` answers **400** `FOLDER_NAME_INVALID`.\n- `POST /contacts/bulk` — `contacts: []` answers **400** `INVALID_BODY` (was 201 with nothing created).\n- `POST /conversations/sync` (and the native-reply path) — a malformed or foreign-organization `storageId` answers **400** `ATTACHMENT_NOT_STAGED` (was 403 `BLOB_REF_INVALID`, a code outside the enum).\n- `POST /projects/{id}/tasks` — an `automationSlug` naming a saved-but-undeployed automation answers **409** `AUTOMATION_NOT_DEPLOYED`; an unknown one stays **404** `AUTOMATION_NOT_FOUND`.\n- `PATCH /knowledge-entries/{id}` — a body that repeats the active row's `topic` and `content` writes no version and answers the active row's own id.\n- `POST /documents/{id}/retry-indexing` — `reason` is one of `content-only`, `untracked-blob`, `unsupported`, `in-progress`; `rag-opt-out` is retired (an opted-out file now queues and answers `indexing`); the per-user retry budget answers the door-wide **429**.\n- `POST …/messages` — `locale` pins the reply language; omitted, the assistant answers in the prompt's language.\n- `GET /skills/{slug}` — the 304 carries `Cache-Control: private, no-cache` (was `no-store`) and matches the edge's `-gzip`/`-zstd` tags.\n- `GET …/threads/{id}` by id — a hidden regenerate-sibling thread answers **404**.\n- 414 `URI_TOO_LONG` — answered by the backend for every API URL above 32 KiB, with `requestId` in the envelope and the door headers; the proxy rule is removed. The `x-api-key` 401 and the NUL 400 carry `X-Tale-Api-Version` and `Cache-Control: no-store`.\n- `Idempotency-Key` — the parameter declares a printable-ASCII pattern (a header value carrying a control character never reaches the platform); no length is enforced.\n\n**Added**\n\n- `GET /projects`, `GET /projects/{id}/files` — `continueCursor` (required, `''` when done) beside the deprecated `cursor`; a lookup answers `isDone: true, continueCursor: ''`.\n- `GET /websites/{id}/pages` — `isDone`, `continueCursor` and `?cursor=` beside `total/offset/hasMore`; `cursor` and `offset` together answer **400** `INVALID_QUERY`. `WebsitePage` gains `failCount`, `lastError`, `lastErrorKind`, `lastErrorAt`, and its `status` is the declared enum `discovered | active`; `Website` gains `failedPageCount`.\n- Every list envelope carries the vendor extension `x-tale-pagination` (`keyset`, `offset` or `none`); `INVALID_LIMIT` and `INVALID_CURSOR` carry `data.issues`; `Error.data` documents `providers`, `lastMessageId` and `lastStatus`.\n- `GET …/generation` — `?reasoningSince=`, `reasoningOffset`, `reasoningLength`. `DELETE …/generation` — **202** `{status: 'cancelling', messageId}` for a queued send; its **404** `CHAT_TURN_NOT_RUNNING` carries `data.lastMessageId` / `data.lastStatus`.\n- `PUT /skills/{slug}` — **201** on create, 200 on update.\n- **408** `REQUEST_TIMEOUT` — door-wide, in the envelope, when a request does not finish arriving within 15 minutes.\n- `AutomationSummary.trigger` — `lastFiredAt`, `lastSkippedAt`, `lastSkipReason`; `PUT /automations/{name}/triggers` — `deployed`.\n- `ProjectFile` — `size`, `indexing`; `POST /projects/{id}/files` 201 `file` — `size`, `mimeType`; `Task` — `archivedAt`.\n- Every operation with its own body cap declares it in its 413 (documents 32 MiB, contacts bulk 8 MiB, conversation sync 8 MiB, staged upload 30 MiB, skill save 4 MiB, delivery claim/fail/ack 64 KiB).\n- The webhook door (`/api/automations/webhook/{token}` and the project twin) answers `Cache-Control: no-store` on every response.\n\n## Known issues\n\n- 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.\n- **The project chat's new scope was proven by unit tests and the real-database integration lane, not by a live model turn in a browser**; the manual box `CHAT-F36` covers the live reading of the boundary sentence.\n- **The proxy entrypoint change (the removed URL rule) was reviewed and built into the image, not exercised against a running edge**; the backend's 414 lane is covered by tests.\n- 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.\n\n## Migration notes\n\n- **Two platform migrations and one knowledge-database migration apply on the first boot of the new images**, each written to run while the previous image is still serving:\n  - `0098_external_keys_canonical_twins` groups `app.projects` by `(org_id, canonical external_item_id)` and `app.tasks` by `(project_id, canonical external_system, canonical external_id)` — canonical being NFC-normalised and trimmed — keeps one row per group (the row already holding the canonical bytes, else the oldest), sets the others' key columns to `NULL` (a task keeps its `external_url`), brings a surviving non-canonical key to canonical form, and adds the unique indexes `projects_org_external_item_canonical` and `tasks_project_external_canonical` beside the byte-exact ones. Detached rows stay reachable by id, in the listing and on the board; the header of the file carries the detection query. Idempotent.\n  - `0099_websites_failed_page_count` adds the nullable `failed_page_count` column to `app.websites`, filled by the next corpus-to-row sync.\n  - Knowledge database `public_web` migration 10 adds `last_error`, `last_error_kind` and `last_error_at` to `website_urls`; the database image applies it in its knowledge role at boot, and a fresh or bring-your-own corpus gets the columns from the converge path. It has no down migration by design.\n- **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.\n- **The proxy image changes**: the entrypoint no longer renders the 32 KiB API-URL rule. It rolls with `tale deploy`; an own-Compose deployment pulls the new `tale-proxy` tag.\n- **The backend listener now enforces a 15-minute request arrival budget** with a JSON 408; nothing to configure.\n- **No configuration-file change and no new environment variable**; `.env.example` is untouched. The web, sandbox, sandbox-runtime, sandbox-buildkitd and sandbox-llm-gateway images have no source change in this range; the docs image carries the updated pages in all three languages.\n- **Operators of the hosted platform**: the model allowlist of a provider credential is where a model the provider's plan does not cover is excluded from `GET /models` — the evaluation's \"listed but not entitled\" finding is closed there, not in code.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.22):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  The migrations above apply on the first boot of the new images.\n\n- **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. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n* fix(platform): close the round-d API findings and pin project chat scope by @larryro in https://github.com/tale-project/tale/pull/3334\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.22...v0.5.23","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.23","publishedAt":"2026-09-13T08:16:11Z"},{"tag":"v0.5.22","version":"0.5.22","name":null,"body":"**0.5.22 is a fix release on the 0.5 line that carries more than its fixes: a security fix at the API door, a third set of REST contract corrections (contract 1.3.0 → 1.4.0), validated reads and realtime hints where the app used to poll, archived work back in search, a clean shutdown for the sandbox egress proxy, and one opt-in CLI build target.** Four platform migrations (0094–0097) and one knowledge-database migration apply themselves on `tale deploy`, each written to run while the previous image is still serving. No configuration-file change and no new environment variable. The Known issues from 0.5.21 are unchanged; two verification gaps are listed with them.\n\n## Highlights\n\n### An API key no longer opens a browser session (#3332)\n\nAny request carrying an `x-api-key` header acted as the key holder's browser session: through it a key could mint further API keys at the auth mount and open `/api/app/*` and the `/events` stream, none of which a key is meant to reach. Found beyond the evaluation report and proven on a local stack. The header is refused with 401 on every route; the REST door authenticates with `Authorization: Bearer` only. Nothing changes for a client that already sends Bearer.\n\n### The third API evaluation pass (#3332)\n\nThe third external black-box evaluation of the REST and MCP API ran against 0.5.21 and reported 83 findings. Every one was second-confirmed from source before a fix was designed, and the server logs of the test window turned wire symptoms into root causes. Three headline corrections to the report: the \"double send\" is a settle race, not a missing guard; skills writes were already serialized, what was missing was a precondition; orphan upload blobs are reclaimed 24 hours after their 30-minute expiry. The contract moves to 1.4.0, and every wire change is listed under _API contract changes_. By family:\n\n- **Door and edge.** An over-long URL answers **414** in the JSON envelope, from the proxy's own rule and from the backend's guard alike. The proxy's header budget is 64 KiB: it was 32 KB, and over HTTP/2 a 33 KB URL used to fail header decoding and close the connection with no response, so a client retried it forever. A key-less `OPTIONS` answers 204 with `Allow`; `X-Organization-Slug` is folded to lowercase; one trailing slash under `/api/v1/` routes; a HEAD of a JSON route carries `Content-Length`; a blank `cursor` or `limit` is a 400; every schema refusal reads as prose; a 413 names the cap.\n- **Chat.** A second send while a turn generates or is queued answers **409** `CHAT_TURN_IN_PROGRESS`, and a thread mid-turn refuses `DELETE`. A send takes an `Idempotency-Key`: a retry replays the first 202 with `duplicate: true`, and the same key with another body is **409** `IDEMPOTENCY_KEY_REUSED` (migration 0094). Messages carry `finishReason`, `usage.estimated` and `stepLimitHit`; a thread lists its messages with `?order=` and `?since=` and reads one by id. The automatic thread title used to be asked of a thinking-by-default model that spent its whole reply budget reasoning and answered nothing, a paid miss on every new thread; the title call now prefers a model that runs without thinking, and a call that returns no text is logged as a miss with its tokens booked.\n- **Knowledge and documents.** A search hit names the legs that matched it (`matchedLegs`, `similarity`, `keywordScore`); fusion is by rank, with admission judged before fusion; the organization's embedding settings take a `minSimilarity` floor, cleared with `null` (from the CLI's configuration declaration too). `Document.contentHash` is filled for the knowledge entries that only carried it in `metadata` (migration 0095); a `PATCH` of metadata is an RFC 7396 merge and a no-op merge skips the write; a document's content is served inline for text; entries carry `supersededAt`, filter by `?topic=` and list their versions.\n- **Automations.** `lastFiredAt` now means a run started. The schedule scan used to stamp the field before asking for a run, so a binding whose automation had nothing deployed \"fired\" every occurrence while the worker logged that it had nothing to run. A trigger now carries `id`, `lastRunId`, `lastSkippedAt` and `lastSkipReason` (`not_deployed`, `unusable_cron` or `start_refused`) (migration 0096). A cron expression the scheduler cannot honour is refused at write time (`-5` no longer reads as `0-5`), fields of another trigger kind are refused, a webhook revocation reads `revoked: 'webhook'`, a parked run says what it is `waitingFor`, and erasure deletes the runs a user started under a bare id.\n- **Skills.** Every skill names its version: `etag` and `updatedAt`. `GET` carries `ETag` and answers 304; `If-Match` guards a save or a delete (**412** `SKILL_STALE`); `If-None-Match: *` creates only (**412** `SKILL_EXISTS`); any file of the bundle is readable; a bundle the file layer refuses answers **422** `SKILL_MALFORMED`; `disableModelInvocation` is writable.\n- **Files, websites, projects, tasks.** A `Range` is judged locally: an unsatisfiable one answers a bodiless **416** with `Content-Range: bytes */size`. The backend used to copy the object store's `Content-Length` onto that empty answer, and the edge aborted the response with \"unexpected EOF\". A malformed or multi-range request answers 200 with the whole body; a HEAD carries `ETag`, `Last-Modified` and `Accept-Ranges`. `POST /websites` for a domain already tracked, or for its www/apex sibling, answers **409** `WEBSITE_DUPLICATE_DOMAIN` naming the row; the shared web corpus keys its chunks by domain as well as URL, so the sibling of a tracked site indexes instead of colliding (knowledge-database migration 09). A taken project-agent name is **409**, not 400. Task labels keep the spelling they were given and are unique per project without regard to case (migration 0097). `PATCH /projects/{id}` edits `name`, `description` and `externalItemId`; a folder names its `parentId` and can be read alone; an upload handoff names its `maxBytes`, and a mint whose `size` exceeds it is refused before any byte moves.\n- **Auth, status, WebDAV.** At the auth mount a non-JSON body is **400** `invalid_request` (it was a bare 415), a token request without `grant_type` is told so, an unknown `client_id` at authorize is named in the redirect, and discovery lists the `acr` claim and only the prompt values the provider honours. `/status.json` carries `Access-Control-Allow-Origin: *`, so a browser dashboard can poll it without a proxy, and the status page shows a backend, a database and an object-store row from a new internal probe. A WebDAV `OPTIONS` answers the methods each target supports. `/me` names the key that made the request under `key` and says whether the caller may edit the deployment configuration.\n\n### Validated reads, and hints instead of polling (#3331)\n\nA source review of the 2026-09-12 efficiency report (18 findings against 0.5.21). The report's premise was wrong: the app is not \"pure polling\", it holds two EventSource lanes, the organization hint stream and a per-thread reply stream, both proxied in production. Three of its findings were real:\n\n- **The video-link chips polled an empty endpoint every two seconds, forever.** Video links were the only domain that never emitted a realtime hint, so their reads polled to compensate, on every idle chat page, for as long as it stayed open. Every write to a video-link job now hints its uploader; the chip reads key under that entity and poll only as a five-second fallback while a job is live.\n- **Two reads were fetched twice.** The composer's model catalog was one fetch per mount with two callers per chat page; it is one shared query per organization, invalidated by an org-wide hint when a credential changes. The Inbox status badges share one counts request per connector.\n- **Nothing on either JSON surface could be validated.** Every 200 JSON `GET` or `HEAD` on `/api/app/*` and `/api/v1/*` now carries an `ETag` and `Cache-Control: private, no-cache`, and a matching `If-None-Match` answers a bodiless 304 (weak tags, tag lists and the `-gzip`/`-zstd` suffix the proxy appends all match). A route that sets its own directive keeps it; what changes is the REST door's blanket `no-store`. File content forwards `If-None-Match`, `If-Modified-Since` and `If-Range` to the object store, which answers 304 itself. A run read takes `?fields=status,finishedAt`, and a run listing with `?include=` reads at most 25 rows and answers at most 8 MiB of them, ending early with a cursor at the last row that fit. The API reference shows `--compressed` on every curl example and gains a section on caching, compression and partial reads; a 304 still costs one request against the rate limit.\n\n### Archived work shows in search, labelled (#3326)\n\nThe ⌘K palette returns archived tasks and projects, and rows belonging to an archived project, each labelled **Archived** or **Archived project**; live rows still list first. Archived material was decided to stay searchable, and its two labels shipped, some releases ago, but the palette kept the older predicate while the agent's search path implemented the decision, so the same query answered differently depending on who asked, and a live task inside an archived project was unfindable through either. Archived work also says so where it lives: a board card and a list row carry the badge (a lighter colour was the only cue, which WCAG 2.1 AA 1.4.1 does not allow), and an archived project carries it beside its breadcrumb name on every tab. On the tasks page, **Show archived** and **Search tasks** now agree; typing in the search box used to silently remove the rows the toggle had just revealed. Chats and contacts are unchanged.\n\n### The sandbox egress proxy shuts down cleanly (#3324)\n\nForeground Tinyproxy wrote a PID file it did not need during startup, and its root supervisor lacked the permission to signal the proxy after it changed user to `nobody`, so a stop could not drain and ended with the container killed (exit 137). The PID file is gone and `KILL` joins the proxy's restricted capability set, in the repository Compose file and in the managed Compose the CLI renders; the SSRF firewall and the privilege drop are unchanged. The container smoke test boots with a read-only `/tmp`, checks the graceful stop and starts the same container again. A managed deployment failure now says whether Compose validation or Compose startup failed, with fixed labels only; Docker output and credentials stay private.\n\n### A baseline x64 CLI executable for CPUs without AVX2 (#3327)\n\nThe default Linux x64 executable targets `bun-linux-x64`, which assumes AVX2; on an on-prem host with an Intel Ivy Bridge Xeon it dies on startup with `Illegal instruction`. `bun run build:linux-baseline` in `tools/cli` compiles the same sources for Bun's `bun-linux-x64-baseline` target, and the `setup-cli` GitHub action takes `linux-baseline: 'true'` on a Linux x64 runner; any other runner, or any other value, is refused with an error before the toolchain is installed. The build is opt-in: there is no `tale_linux_baseline` release asset, and the install script still downloads the default executable.\n\n## Behaviour changes\n\n- **Any request with an `x-api-key` header is refused with 401**, on every route.\n- **A URL past the documented cap answers 414** in the JSON envelope instead of a bare 431 or a dropped connection.\n- **A second chat send during a turn is refused** (409), and a thread cannot be deleted mid-turn.\n- **A trigger's `lastFiredAt` reads `null` until it has started a run.** After migration 0096, schedule and event bindings that never started a run lose the stamp the scan had been advancing; a binding that did fire keeps its stamp.\n- **Task labels keep their case.** `Bug` stays `Bug` (it used to come back as `bug`); labels that differed only in case are merged onto the oldest one.\n- **A website registered twice, or under its www/apex sibling, is refused** with 409 instead of creating a second row.\n- **The ⌘K palette lists archived tasks and projects**, labelled, after the live rows; board cards, list rows and an archived project's breadcrumb carry an **Archived** badge.\n- **Video-link chips no longer poll an idle chat page.**\n- **JSON reads on both API surfaces carry `ETag` and `Cache-Control: private, no-cache`**; the REST door used to answer `no-store`.\n- **The automatic thread title is asked of a model that can answer without thinking** wherever the organization has one.\n- **The status page shows backend, database and object-store rows**, and `/status.json` answers cross-origin requests.\n- **The sandbox egress proxy stops gracefully**, and a managed deployment failure names the Compose phase that failed.\n- **A `tale config` declaration can clear the embedding similarity floor** with `minSimilarity: null`; an omitted floor leaves the stored one in place.\n\n## API contract changes\n\n`info.version` of the OpenAPI document moves from 1.3.0 to **1.4.0**, and every `/api/v1` answer names the version it implements in `X-Tale-Api-Version`. Changed meanings first, additions after.\n\n**Changed**\n\n- Every 200 JSON read of `/api/v1` answers `Cache-Control: private, no-cache` (was `no-store`) with an `ETag`; a matching `If-None-Match` answers **304** with no body. Routes that chose their own directive keep it. `/api/app/*` JSON reads gain the same pair (they had no directive).\n- `GET …/files/{documentId}/content` honours `If-None-Match`, `If-Modified-Since` and `If-Range`, answers **304**, and its `Cache-Control` is `private, no-cache` (was `private, no-store`). An unsatisfiable `Range` answers a bodiless **416** with `Content-Range: bytes */size` and `Content-Length: 0`; a malformed or multi-range request answers **200** with the whole body; a HEAD carries `ETag`, `Last-Modified` and `Accept-Ranges`.\n- `x-api-key` is refused with **401** everywhere; only `Authorization: Bearer` authenticates.\n- An over-long URL answers **414** `URI_TOO_LONG`; the documented 431 was never observed.\n- A second `POST …/threads/{id}/messages` while a turn generates or is queued answers **409** `CHAT_TURN_IN_PROGRESS`, and `DELETE` on such a thread is refused the same way. A thread title is non-blank and at most 120 characters.\n- `POST /websites` for a domain already tracked as a whole site, a bare re-post, or the www/apex sibling answers **409** `WEBSITE_DUPLICATE_DOMAIN` with `data.websiteId` and `data.domain`; only a list under the same spelling merges.\n- `PROJECT_AGENT_NAME_TAKEN` answers **409** (was 400); `DOCUMENT_TITLE_INVALID` is answered as `INVALID_BODY` at the door.\n- Task labels are stored as sent, unique per `lower(name)`, and read back in the order sent.\n- A trigger's `lastFiredAt` is stamped only when a run starts. A cron expression the scheduler cannot honour, and a trigger field of another kind (`cron` or `timezone` on a webhook, `event` on a schedule), are refused at write time.\n- A blank `cursor` or `limit` answers **400** `INVALID_QUERY`; a negative website page `offset` answers **200** clamped; run listings with `?include=` clamp `limit` to 25 and cap a page at 8 MiB of rows (`isDone: false` plus a cursor when cut).\n- Auth mount (`/api/auth/oauth2/*`): a non-JSON body answers **400** `invalid_request` (was a bare 415); a token POST without `grant_type` answers `invalid_request` naming it; an unknown `client_id` at authorize is named in the redirect (302 for a navigation, `200 {redirect, url}` for a fetch-mode client); discovery lists the `acr` claim and only the prompt values the provider honours.\n- `Error.code` is required on every error, and 43 codes no REST route can answer leave the enum (`THREAD_NOT_IN_PROJECT`, `THREAD_SCOPE_CHANGED` and `AUTOMATION_PROJECT_ARCHIVED` among them).\n\n**Added**\n\n- `Idempotency-Key` on chat sends: a replay answers **202** with `duplicate: true`; the same key with another body answers **409** `IDEMPOTENCY_KEY_REUSED`.\n- `Message.finishReason`, `usage.estimated` and `stepLimitHit`; idle-thread facts `lastMessageId` and `lastStatus`; `?order=` and `?since=` on the message listing; `GET …/messages/{messageId}`.\n- `GET /runs/{runId}` and `GET /projects/{id}/runs/{runId}` accept `?fields=` (an unknown or blank key answers **400** `INVALID_QUERY`); a projected read is a `RunProjection`.\n- Search hits carry `matchedLegs`, `similarity` and `keywordScore`; the embedding settings take a nullable `minSimilarity`.\n- `Document.contentHash`; a `PATCH` of metadata is an RFC 7396 merge; `GET`/`HEAD …/documents/{id}/content`; knowledge entries carry `supersededAt`, filter by `?topic=`, and `GET /knowledge-entries/{id}/versions` lists their versions.\n- Triggers carry `id`, `lastRunId`, `lastSkippedAt` and `lastSkipReason`; a revoked webhook reads `revoked: 'webhook'`; a parked run carries `waitingFor`.\n- Skills: `etag` and `updatedAt` on every view, `ETag`/304 on `GET`, `If-Match` → **412** `SKILL_STALE`, `If-None-Match: *` → **412** `SKILL_EXISTS`, `GET`/`HEAD /skills/{slug}/files/{path}` (**404** `SKILL_FILE_NOT_FOUND`), **422** `SKILL_MALFORMED`, and `disableModelInvocation` is writable.\n- `GET /conversations/deliveries` peeks the delivery queue and `POST …/deliveries/{id}/retry` retries one delivery (`DELIVERY_RETRY_UNAVAILABLE`); `claim` already existed. `Me.capabilities.deploymentEditor`; `Me.key { id, name, expiresAt }`; `BrowserSession.createdAt`.\n- `PATCH /projects/{id}` edits `name`, `description` and `externalItemId` (nullable) beside `archived`; `{}` answers **400**. `ProjectFolder.parentId` (required), `?parentId=` and `GET …/folders/{folderId}`. `ProjectUploadHandoff.maxBytes` (required); a mint body `size` over it answers `FILE_TOO_LARGE` or `UPLOAD_POLICY_REJECTED` before any upload. `WebsitePatch.domain` is declared; `lifecycleStatus` and `translations` are read-only.\n- A key-less `OPTIONS` answers **204** with `Allow` and no CORS headers; `X-Organization-Slug` is matched case-insensitively; one trailing slash under `/api/v1/` routes; a HEAD of a JSON route carries `Content-Length`; 204, 304 and 416 carry no content headers; a 413 names the cap; every schema refusal reads as prose.\n- Every response declares its headers in the document, and a contract fingerprint test fails when the contract changes without a version bump.\n- Outside the document: `/status.json` answers `Access-Control-Allow-Origin: *`, and a WebDAV `OPTIONS` names in `Allow` the methods each target supports.\n\n## Known issues\n\n- 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.\n- **The egress proxy's shutdown fix was verified natively and in a nested guest whose AppArmor profile differs from production's**; the production-profile lifecycle check and the hosted container smoke are still owed.\n- **The app was not observed in a browser after the validated-read change**; two manual boxes, `PERF-P8` and `PERF-P9`, cover the idle-page silence and validated reads through the proxy.\n\n## Migration notes\n\n- **Four platform migrations and one knowledge-database migration apply on the first boot of the new images**, each written to run while the previous image is still serving:\n  - `0094_chat_send_idempotency` adds the table `app.chat_send_idempotency` and its expiry index. Rows live a day and are swept lazily per organization.\n  - `0095_documents_content_hash_backfill` moves `metadata.contentHash` into the `content_hash` column for knowledge-entry documents and removes the key from the metadata bag (a bag emptied by it reads as `null`). Set-based and idempotent.\n  - `0096_automation_triggers_fire_ledger` adds `last_due_at_ms`, `last_run_id`, `last_skipped_at_ms` and `last_skip_reason` to `app.automation_triggers` with a partial index on `last_run_id`; seeds the claim cursor from the old stamp so no past occurrence fires again; fills `last_run_id` from the newest run each trigger started; **nulls `last_fired_at_ms` on schedule and event bindings that never started a run**; and nulls `cron`/`timezone` on non-schedule and `event` on non-event bindings. During the roll, an occurrence the new image claims for an undeployed automation is also claimed by the old image, which starts nothing either but stamps `last_fired_at_ms` the old way; a fire stamp with no `last_run_id` beside it from that window is that claim.\n  - `0097_task_labels_case_insensitive_unique` merges labels that differ only in case onto the oldest row per project (every task's `label_ids` is repointed, order kept, and the other rows are deleted) and adds a unique index on `(project_id, lower(name))`. The old `UNIQUE (project_id, name)` constraint stays this release because the previous image writes against it; it is dropped in a later release.\n  - Knowledge database `public_web` migration 09 replaces the `(url, chunk_index)` unique key on `chunks` with `(domain, url, chunk_index)`. The database image applies it in its knowledge role at boot; it has no down migration by design.\n- **The proxy image changes**: its header budget is 64 KiB and an over-long API URL is answered at the edge. It rolls with `tale deploy`; an own-Compose deployment pulls the new `tale-proxy` tag.\n- **The sandbox egress service needs the `KILL` capability.** `tale deploy` renders it into the managed Compose file. An own-Compose deployment adds `KILL` to the `sandbox-egress` service's `cap_add` list next to `NET_BIND_SERVICE`; without it the new image still runs, but a stop times out instead of draining.\n- **No configuration-file change and no new environment variable**; `.env.example` is untouched. The sandbox, sandbox-runtime, sandbox-buildkitd and sandbox-llm-gateway images have no source change in this range.\n- **Managed deployments** whose last run ended in the 0.5.20 provisioning failure still follow the `supersedesPendingBundle` recipe in the 0.5.21 notes.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.21):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n  The migrations above apply on the first boot of the new images.\n\n- **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. 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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n  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.\n\n## What's Changed\n* feat(cli): build a baseline x64 executable for CPUs without AVX2 by @yannickmonney in https://github.com/tale-project/tale/pull/3327\n* perf(platform): close the 2026-09-12 efficiency report's findings by @larryro in https://github.com/tale-project/tale/pull/3331\n* fix(platform): show archived work in search, labelled as archived by @Israeltheminer in https://github.com/tale-project/tale/pull/3326\n* fix(sandbox): correct foreground egress lifecycle permissions by @yannickmonney in https://github.com/tale-project/tale/pull/3324\n* fix(platform): close the 2026-09-12 API evaluation's third-pass findings by @larryro in https://github.com/tale-project/tale/pull/3332\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.21...v0.5.22","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.22","publishedAt":"2026-09-12T14:46:10Z"},{"tag":"v0.5.21","version":"0.5.21","name":null,"body":"**0.5.21 is a fix release on the 0.5 line with one fix, in the CLI: managed deployments complete again.** Every `tale deploy --bundle` since 0.5.20's CLI started keeping its backend-local state under the backend's data volume ended after the runtime had already rolled, with the backend-local provisioning phase refused by its own ownership check. No migration, no config-file change, no platform image change beyond the version stamp. The Known issues from 0.5.20 are unchanged.\n\n## Highlights\n\n### Managed deployments complete again (#3330)\n\n`tale deploy --bundle` copies the CLI into the backend container and runs `deploy provision` there through `docker exec`, which uses the image's default user: root, kept so the entrypoint can fix volume ownership before it drops to the app user. The phase keeps its private state under `/app/data/ops/tale-deployments/<name>/` and refuses any path component the current account does not own, and the entrypoint hands `/app/data` to the app user on every boot. So as root the very first component was refused, the inner CLI exited non-zero, and the host saw only `The backend-local Tale CLI did not complete provisioning. Its previous receipts are retained for recovery.` — after the compose roll, so the instance served the new version while the deployment never reached `ready`. The credential export phase shared the fault. Reproduced inside the 0.5.20 platform image; the ops end-to-end shard on a fresh droplet failed the same way.\n\n- **Backend-local phases run as the backend's own user.** The CLI reads the owner of the backend's data directory from the container, hands the copied bundle to that account and runs `deploy provision` and `deploy export-client-native` as it.\n- **The failure names itself.** A failing backend-local phase now repeats the inner CLI's own JSON summary in the deploy result (`… It reported: <summary> (exit <code>)`); stderr still never surfaces.\n- **A stranded deployment can be taken over after review.** A failed deployment keeps its recovery point — the pre-deployment snapshot bound to the bundle it was applying — and refuses any other bundle on retry; a new CLI pin always changes the bundle, so a fault in this phase left a host stuck until someone with shell access removed the intent. The deployment specification takes `supersedesPendingBundle` with the pending bundle's sha256, which the refusal now names: the newly reviewed bundle takes over the same snapshot and the ready receipt lists it under `supersededBundles`.\n\n## Behaviour changes\n\n- **`deploy provision` and `deploy export-client-native` run inside the backend as the owner of its data directory**, on a copy of the bundle owned by that account.\n- **A failing backend-local phase reports the inner summary** in the deploy result.\n- **The pending-bundle refusal names the pending bundle's sha256** and points at `supersedesPendingBundle`.\n- **New optional deployment specification field `supersedesPendingBundle`**; new optional `supersededBundles` list in the pending intent and the ready receipt. Workspace `tale deploy` is unchanged.\n\n## Known issues\n\n- 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.\n\n## Migration notes\n\n- **No database migrations, no config-file changes, no new environment variables.** The platform, proxy, database and sandbox images change only in their version stamp.\n- **Managed deployments** whose last run ended in the provisioning failure keep a pending intent on the host. The first deploy with the fixed CLI refuses with `A different deployment bundle is pending (<sha256>)`; declare that sha256 as `supersedesPendingBundle` in the deployment specification, deploy again, and remove the declaration once the ready receipt lists it under `supersededBundles`. Nothing on the host needs manual repair.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.20):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n- **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.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n## What's Changed\n\n- fix(cli): run backend-local phases as the backend user by @larryro in https://github.com/tale-project/tale/pull/3330\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.20...v0.5.21","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.21","publishedAt":"2026-09-12T04:17:06Z"},{"tag":"v0.5.20","version":"0.5.20","name":null,"body":"**0.5.20 is a fix release on the 0.5 line that carries more than its fixes: four additive database migrations, a second set of REST contract corrections, an object-store registry move and one CLI feature.** The platform side closes the second-pass findings of the 2026-09-11 API evaluation, two of them severity 1; the object store image now comes from quay.io because Docker Hub stopped serving it; the CLI manages platform configuration declaratively and provisions fresh deployments end to end. Everything visible is named under Behaviour changes and API contract changes; the Known issues from 0.5.19 are carried and two are added.\n\n## Highlights\n\n### The REST and MCP doors after the second evaluation pass (#3329)\n\nThe 2026-09-11 external evaluation of platform.tale.dev on 0.5.19 reported 121 findings. Seven read-only verifiers second-confirmed every one against the source: 94 real code defects or contract gaps, 21 documentation gaps, 8 environmental observations, 25 by design, duplicate or refuted, 3 deliberately left. The real ones are fixed here, family by family, the two severity-1 findings first.\n\n- **Server-side request forgery.** Every outbound fetch the platform makes on a caller's behalf resolves the name, checks every returned address against the private-network and cloud-metadata rules, then dials through a connection pinned to the checked addresses. A public-looking name whose record points inside the network, or that flips between the check and the dial, is refused. The crawler is https-only and refuses such a host at registration (`400 WEBSITE_DOMAIN_NOT_CRAWLABLE`); the provider lanes share the policy.\n- **File download.** `GET …/files/{documentId}/content` streams the bytes with an RFC 6266 `Content-Disposition`. It used to answer a same-origin 302 to a presigned URL, which a bearer client followed into a 403 and `curl -L` wrote as an empty file with exit 0. When the object store is down the route answers `503 OBJECT_STORE_UNAVAILABLE` with `Retry-After`.\n- **Tenancy at the door.** A multi-organization key names its organization on **every** `/api/v1` call, reads included. The dashboard's last-active organization used to steer plain `GET`s, a hazard no client could defend against. The 400 lists the key's organizations under `data.organizations`.\n- **Strict input at the edge and the door.** The proxy answers **431** above 32 KiB of headers on HTTP/1.1 and HTTP/2 (an overflow used to surface as an HTTP/2 stream reset), advertises only HTTP/1.1 and HTTP/2 (no UDP port is published, so an advertised HTTP/3 only invited clients to race a QUIC connection that could not arrive), drops the `Via` header and puts a 64 MB body backstop on the `/api/v1` lane above the door's own caps. The door refuses an unknown, repeated or blank query parameter, a non-integer `limit`, invalid UTF-8, whole numbers beyond 2^53 − 1 and any query string on a write; bodies are capped by route (1 MiB by default, 4 MiB for a skill, 8 MiB for bulk and conversation sync, 30 MiB for an upload, 32 MiB for a document's inline content) and a declared length above the cap is refused before a byte is read. `OPTIONS` is listed in `Allow`, `X-Request-Id` is on raw responses, and the bare `/api` and `/.well-known/*` paths get the API's JSON 404 instead of the SPA shell. The error-code registry guard scans the domain modules the handlers import.\n- **Chat.** A message id is minted before the turn is queued and a real `queued` marker exists; a `cancelled` status; the poll carries text, reasoning, `cancelRequested` and `updatedAt`; `reasoningEffort` and `maxOutputTokens` on a message (400 above the model's ceiling); `costEstimateCents`; `archivedAt` on a thread (migration 0090); `PATCH` for the title; `model` is required on a message and `GET /models` lists what the key can send to.\n- **Documents and knowledge.** Retrieval admits a wider pool before dedupe, cache, rerank and the page cut (`limit` used to be the retrieval k, so a small page starved the rerank). Deleting an active knowledge entry releases its corpus references. `PATCH /documents/{id}` answers 200 with the document, honours `expectedUpdatedAt` (`409 DOCUMENT_STALE`) and refuses to rewrite or delete a knowledge entry's backing document (`409 DOCUMENT_HAS_KNOWLEDGE_ENTRY`). `DELETE` routes for project files and folders.\n- **Automations, runs and webhooks.** Run start honours `Idempotency-Key` through a ledger (migration 0091): a retry of a lost 202 reads the run the first attempt started, and a reused key with a different input is `409 IDEMPOTENCY_KEY_REUSED`. Refused inputs carry `data.issues`; emitted and reserved event names are distinguished; `?version=deployed`; `RunSummary` listings, `GET /runs` and per-project runs; `DELETE /runs/{id}` (`409 RUN_ACTIVE`). The webhook door collapses to `403 AUTOMATION_PROJECT_FORBIDDEN` for an unknown, archived or uninstalled project and is rate-limited per sender address (120 a minute) and per trigger (20 a minute), so a leaked URL starts a bounded number of runs. MCP dispatcher refusals carry `code` and `hint`, and the tools carry annotations.\n- **Skills, conversations and browser sessions.** A byte-measured skill body cap, merge semantics where `null` clears a field, `If-None-Match: *` answering `412 SKILL_EXISTS`, `team|org` visibility, a 404 for a malformed slug and typed `Skill` schemas. Conversation snapshots are deleted at or above the stored version, a claim distinguishes 404 from 403, delivery and attachment 404s are separate, nested bodies are strict. Browser sessions check the operator gate before reading the body, parse crawl-target domains, cap the TTL and gain `DELETE /browser-sessions/{id}`.\n- **Projects and tasks.** External keys are stored NFC-normalized and trimmed on both families (migration 0093 backfills), so a key handed over in NFD by a macOS filesystem finds the project a worker created in NFC from a CSV. Project folder names are unique among siblings regardless of case, enforced by the database (migration 0092). `GET /projects` lists, `PATCH {archived}` archives, `DELETE` (admin; cascade or detach; 409 for bound automations, protected records or legal holds). Agents take `expectedUpdatedAt` and refuse an unknown secret; tasks carry `runId` (`executionId` is deprecated) and answer `403 TASK_ARCHIVED`.\n- **Contacts, products and websites.** Free text is trimmed, a blank string is absent on create and clears on `PATCH`, `metadata` merges per RFC 7396, free-form JSON is bounded, `currency` is ISO 4217, `imageUrl` is http(s), an email local part is at most 64 characters, `CONTACT_STALE` and `PRODUCT_STALE` guard concurrent edits, duplicates are coded, and website registration checks that the domain resolves to a crawlable address.\n- **Docs and spec.** The OpenAPI post-pass stamps 403, 404, 405 and 500 (and 413 where a body exists) on every operation, every timestamp is an integer epoch-millisecond stamp, `Error.requestId` and `data.organizations` are declared, a Conversations tag and 14 missing descriptions are added, the preamble is rewritten and five new spec guards hold it. The api-reference, mcp-endpoint, rate-limits, webhooks and status-page pages are rewritten in English, German and French.\n\nDeliberately deferred: a lean assistant mode, an SSE stream for chat, a negative availability cache, the cached-input rate, catalog labels, `Idempotency-Key` on messages and creates, `ETag`/`If-Match` on skills, `DELETE` for conversation uploads, RFC 9728, jwks `use`, spec examples and task delete.\n\n### The CLI provisions deployments and manages platform configuration (#3321)\n\nNative platform configuration becomes part of the Tale CLI, and managed deployments reuse it.\n\n- **`tale config validate`, `plan`, `apply` and `read`** manage branding, governance policies, custom providers, environment-backed provider credentials, knowledge-embedding settings and instance deployment settings from one declaration file (`schemaVersion: 1`, a list of resources by `kind`) through the platform's authenticated APIs. `--url` and `--org` name the target and `TALE_CONFIG_COOKIE` carries the operator session. A plan names each resource's scope, current and desired hashes and native side effects; apply requires the exact declaration and plan and refuses to overwrite a conflicting native edit; `read` reports whether native state matches; undeclared resources are left alone; there is no delete and no arbitrary file write. Embedding changes check the organization's document and website counts and require paused ingestion; they do not migrate existing vectors.\n- **Managed deployments run the same engine** after identity provisioning and before configuration releases, with precondition checks, verified readback and a `native.configuration` receipt that binds the declaration and bundle hashes. Writes keep a pending receipt before the first change, so an interrupted apply can be inspected and retried with the same reviewed plan. A declared `deployment` resource is activated before the deployment reports ready: the sandbox spawner is drained for up to five minutes, the verified container is restarted once sessions have finished and ready requires fresh health checks; an unchanged replay does not restart it again.\n- **Fresh identity provisioning.** `identity.bootstrap: \"fresh\"` lets `tale deploy provision` create the initial local account and organization; `identity.emailVerification: \"operator-attested\"` records an administratively verified operator without sending mail; a symbolic `project: { key, name }` and `skillOwner: \"operator\"` let a configuration release target an instance that has no native IDs yet. A native client chooses an existing `clientId` or `managed: true`, and **`tale deploy export-client`** hands a managed client's credentials to a separate application as private `0600` JSON (`--env-prefix` yields a four-string map of issuer, client id, client secret and organization slug).\n- The native configuration schemas move into `@tale/shared`; the platform keeps authorization, persistence, history and domain effects. The `setup-cli` GitHub action also builds on macOS Apple Silicon for configuration jobs; managed Linux stack preparation still needs a Linux executable. The CLI install page gains a _Configure the platform_ section and the providers page is rewritten around the deployment declaration, in all three languages.\n\n### The object store image comes from quay.io (#3328)\n\nDocker Hub stopped serving `minio/minio` on 2026-09-11 (`pull access denied`), which stalled every smoke test and every fresh deployment at `object-store`. The same release, `RELEASE.2025-04-22T22-12-26Z`, is published on quay.io, so `compose.yml`, the CLI's third-party image list, the own-compose and contributing docs and the data-residency manual suite now pull `quay.io/minio/minio` (and `quay.io/minio/mc`).\n\n## Behaviour changes\n\n- **A multi-organization API key must name its organization on every call**, reads included; a call without `X-Organization-Slug` answers **400** `ORG_SLUG_REQUIRED` and lists the slugs.\n- **A project file download answers the bytes** with a `Content-Disposition`, never a redirect to the object store.\n- **Website registration refuses a hostname whose DNS answer points inside the network**, and the check repeats before every dial with the connection pinned to the checked address. `TALE_ALLOW_PRIVATE_CRAWL_HOSTS=1` still admits intranet targets; the cloud-metadata endpoints stay refused regardless.\n- **Project folder names are unique among siblings regardless of case.** Existing case twins are renamed once by migration 0092 (the oldest keeps its name).\n- **Project and task external keys are normalized** (Unicode NFC, trimmed) before every lookup and write; migration 0093 brings stored keys to that form.\n- **Knowledge retrieval admits a wider pool** before dedupe, cache, rerank and the page cut, so a small `limit` no longer starves the rerank.\n- **The edge answers 431 above 32 KiB of headers**, no longer advertises HTTP/3 and no longer stamps `Via`.\n- **The inbound webhook door is rate-limited** per sender address and per trigger, and answers 403 for an unknown, archived or uninstalled project.\n- **Run start honours `Idempotency-Key`**; a retried start returns the run the first attempt created.\n- **The object store pulls from quay.io.** The store sits in the stop-gated tier, so a default `tale deploy` leaves a running store untouched; it moves to the quay.io reference when it is next recreated (a first deploy, a store that is already stopped, or `tale deploy --stop`). Same MinIO release, same volume, no data change. A host with an egress allowlist must be able to reach quay.io before that.\n- **The CLI gains `tale config validate|plan|apply|read` and `tale deploy export-client`**, and `tale deploy provision` can bootstrap a fresh identity. The workspace `tale deploy` is unchanged.\n\n## API contract changes\n\nIntegrations against `/api/v1` should note:\n\n- A multi-organization key without `X-Organization-Slug` answers **400** `ORG_SLUG_REQUIRED` on every call, reads included; the body lists the slugs under `data.organizations`.\n- Query strings are strict: an unknown, repeated or blank parameter is **400** `INVALID_QUERY`; a write with any query string is refused; a non-integer `limit` is **400** `INVALID_LIMIT` (it used to be truncated).\n- Bodies above their cap answer **413** `BODY_TOO_LARGE`; invalid UTF-8 and whole numbers beyond 2^53 − 1 answer **400** `INVALID_BODY`.\n- `GET …/files/{documentId}/content` answers **200** with the bytes (was a 302). Drop `-L` from download recipes.\n- `PATCH /api/v1/documents/{id}` answers **200** with the document (was 204).\n- A chat message requires `model`; `GET /api/v1/models` lists what the key can send to.\n- Skills `visibility` is `team|org` (`private` is refused); the `PUT` body cap is 4 MiB.\n- Product `currency` must be ISO 4217 and `imageUrl` http(s); a blank string on `PATCH` clears the field.\n- Folder names are unique among siblings regardless of case; external keys are NFC-normalized and trimmed.\n- `DELETE /api/v1/projects/{id}` takes its `mode` in the body; the webhook door answers **403** `AUTOMATION_PROJECT_FORBIDDEN` for an unknown, archived or uninstalled project and is rate-limited per sender address and per trigger.\n- Run start honours `Idempotency-Key`; a reused key with a different input is **409** `IDEMPOTENCY_KEY_REUSED`.\n- Tasks carry `runId`; `executionId` is deprecated and still served.\n- The edge answers **431** above 32 KiB of headers and no longer advertises HTTP/3.\n- OpenAPI: every timestamp is an `integer`; every operation declares 403, 404, 405 and 500 (and 413 where it has a body); `Error.requestId` is declared. The error-code set is additive — treat a code you do not know as a generic refusal of the status you got.\n\n## Known issues\n\n- Unchanged from v0.5.14 and v0.5.15, where both are 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 (over-masking, not under-masking). The PII engine was not touched; only its policy schema moved into `@tale/shared` with the other native schemas.\n- **Thinking-block replay on the native Anthropic connector is still not done**, and the live check 0.5.19 left owed — a turn on the native connector with a Max reasoning pick and a tool call — has still not been observed.\n- **Embedding calls from `rag_search` inside a harness turn are still unmetered.**\n- **The product edit dialog in the app still cannot clear a field**; the REST `PATCH` can (a blank string or `null`).\n- **The app's skill editor still carries the retired `private` visibility** so it can re-send a saved value; the REST door accepts `team` and `org` only.\n\n## Migration notes\n\nFour additive migrations, applied automatically by the new image at boot inside the migrator's advisory lock. Each is safe mid-roll — the previous image keeps serving while the new one migrates — and idempotent.\n\n- **`0090`** adds a nullable `archived_at_ms` to `app.thread_metadata`. No backfill: a thread archived before this release has no honest value, and the `archived` flag stays the authority on the state.\n- **`0091`** creates `app.automation_run_idempotency` and its expiry index. Nothing in the previous image reads it; expired rows are swept lazily per organization.\n- **`0092`** renames existing case-twin project folders deterministically (the oldest row keeps its name; each newer twin gets its name truncated to 100 characters plus the first eight characters of its id in parentheses), then adds a unique index over organization, project, parent and the lower-cased name for project folders. No folder, document or sync binding is lost. During the roll, the previous image's racing duplicate insert surfaces as a unique-violation error on the loser instead of a silent duplicate.\n- **`0093`** rewrites stored project `external_item_id` and task `external_system`/`external_id` values to NFC, trimmed. A row whose canonical form another row of the same scope already holds is left untouched, so no unique index is violated and nothing is merged; only the lookup by key resolves to the canonical twin, and the other stays reachable by id. It uses PostgreSQL's `normalize()`, which needs **PostgreSQL 13 or newer with a UTF-8 server encoding**: the bundled `tale-db` is PostgreSQL 16, so check this only for an external `DATABASE_URL` database.\n- **No config-file changes and no new required environment variables.** `TALE_CONFIG_COOKIE` is read by the CLI process only and never belongs in the platform's `.env`.\n- **The object store** moves registry only when it is next recreated (see Behaviour changes).\n- **Managed deployments** keep the state directory, snapshots and native receipts; a pending configuration receipt is the recovery point for an interrupted apply.\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.19):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n- **Managed deployments** move by changing the pinned source commit, preparing a new bundle and applying it with the pinned CLI — see _Managed deployments_ on the CLI install page.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n## What's Changed\n\n- feat(cli): provision deployments and manage platform configuration by @yannickmonney in https://github.com/tale-project/tale/pull/3321\n- fix(platform): pull the object store image from quay.io by @yannickmonney in https://github.com/tale-project/tale/pull/3328\n- fix(platform): close the 2026-09-11 API evaluation's second-pass findings by @larryro in https://github.com/tale-project/tale/pull/3329\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.19...v0.5.20","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.20","publishedAt":"2026-09-12T02:26:40Z"},{"tag":"v0.5.19","version":"0.5.19","name":null,"body":"**0.5.19 is a fix release on the 0.5 line that carries more than its fixes: one additive database migration, a set of REST contract corrections, a gateway image bump and one CLI feature.** The sandbox side ends, retries and bills managed harness turns correctly and runs Claude Code on DeepSeek natively; the platform side closes the 2026-09-11 API evaluation and makes the composer's reasoning pick reach every connector; the CLI now owns source-pinned managed deployments and configuration releases. Everything visible is named under Behaviour changes and API contract changes; the Known issues from 0.5.15 are unchanged and two new ones are named.\n\n## Highlights\n\n### Managed harness turns end, retry and bill correctly (#3319)\n\nThe 2026-09-11 review of project agents on the sandbox harness matrix found ten defects; every one was re-confirmed in source and fixed with a regression lock.\n\n- **Turn lifecycle.** The Pi harness was cut at every drain window, so a tool stop mid-tool read as a completed turn and the process was reaped; the parser now ends only on the exec's real exit. Claude background tasks were cancelled early; the drain keeps the `task-started`/`task-settled` ledger and a `turn-ended` with open entries neither arms the cut nor reads as terminal. Hermes reported success on a failed SDK return and lost its history; the wrapper ends a `{completed:false, failed:true}` return as an error with `api_error_status`, persists messages through a `SessionDB`, and restores the stored conversation on `--resume`.\n- **Qwen Code is in the runtime image** (`@qwen-code/qwen-code@0.23.3`, API-key auth only). Its headless stream is the Claude stream-json dialect, not Gemini's, so the harness parses it as `claude-stream-json`; the image conformance test now walks the whole harness registry.\n- **Retry hygiene.** Both drive hosts cancel the exec before settling a crashed drain, and the kick plan carries the predecessor exec regardless of resume: the start cancels it and waits, bounded, for runnerd to report it gone before the new CLI launches.\n- **Billing is durable.** A managed turn mints a gateway virtual key whose spend is read back when the turn ends; that settle used to be one shot, so a gateway hiccup between the finalize claim and the spend read lost the figure forever, a failed remote delete was still marked revoked locally, and the teardown paths deleted the key without reading its spend. One recoverable settlement now sits behind the turn end, the teardown seam, a `sandbox.gateway_key_reconcile` job (30 s → 16 m backoff) and the sandbox watchdog sweep: spend is read before the key is deleted, a failed delete hands the token claim back, and the op row and `app.usage_ledger` are booked atomically, attributed to the run's starter and the agent (task runs) or the automation (agent nodes).\n- **The cap applies before the turn starts.** The turn's allowance is reserved under the org admission lock before the key is minted — min(deployment default, cap − ledger − in-flight reservations) — so concurrent turns cannot each read the same remaining balance and collectively overshoot it. A cap already reached refuses the start as `budget_exceeded` with no auto-retry, in both the task and the automation lane. A custom upstream whose pricing push fails is refused rather than billed at zero; standard providers stay fail-open.\n\n### Claude Code on DeepSeek runs natively; the gateway moves to Bifrost v1.6.11 (#3320)\n\nEvery task-agent run on DeepSeek with the Claude Code harness failed with `400 The reasoning_content in the thinking mode must be passed back to the API`: the gateway (Bifrost v1.5.13) provisioned DeepSeek as a generic OpenAI upstream and dropped the assistant's `reasoning_content` on tool-loop turns, which DeepSeek requires replayed once tools are present. Three changes, one PR:\n\n- **`tale-sandbox-llm-gateway` is Bifrost v1.6.11** (v1.6.9 fixes DeepSeek multi-turn thinking on the OpenAI-compatible surface, v1.6.10/.11 harden the replay; held below v2.0.0's request rewrite).\n- **The stored gateway admin password is preserved on every config apply.** Bifrost ≥ 1.6.9 enforces a password policy (12+ characters, upper, lower, digit, special) — only when the password is being changed. The platform re-sent the plaintext password on every apply, so a secret minted before the policy would have been refused with 400 and no sandbox session of any harness could have started after the bump. The apply now sends the preserve-stored marker once auth is enabled and a plaintext password only on first-time bootstrap; the CLI and dev generators produce policy-compliant secrets. No rotation, no volume wipe.\n- **A native Anthropic lane for anthropic-wire harnesses.** A provider may declare an optional `harnessEndpoint` (DeepSeek: `https://api.deepseek.com/anthropic`, format `anthropic`). Claude Code on such a connector rides a distinct `<org>__<slug>__<model>__anthropic` gateway record whose upstream is that endpoint, so the gateway forwards Anthropic through instead of down-converting it to OpenAI. The lane is decided once where the serving resolves and carried through routing, provisioning, the virtual-key mint and pricing; the automations lane keeps the now-fixed OpenAI path and the two records coexist.\n\n### The REST and MCP doors after the 2026-09-11 evaluation (#3322)\n\nA black-box evaluation of platform.tale.dev on 0.5.18 reported 111 findings. Every one was second-confirmed against the source: 81 are real and fixed here, 24 were by design or documentation gaps (now documented), 2 were false positives, 4 are deliberately deferred (list-envelope unification, test-verdict recording on a gate failure, jwks rotation policy, project/file/folder deletes). Ten further defects of the same classes the evaluation missed are fixed alongside.\n\n- **Edge and auth.** A dot-segment `/api/...` path answers the API's JSON 404 at the proxy, never the SPA shell. OAuth userinfo answers **401** `invalid_token` with a `WWW-Authenticate: Bearer` challenge (was 400 with no challenge, so every five-minute token expiry read as \"do not retry\"); token and authorize speak the RFC 6749 envelope; discovery lists the organization claim. This lives at the auth mount, because an auth hook cannot change a refusal's status.\n- **The `/api/v1` door.** NUL bytes are refused at the boundary (were Postgres 500s); a known path with the wrong verb answers **405** with `Allow`; page cursors are signed and a forged one is **400** `INVALID_CURSOR`; one error-code registry backs the OpenAPI `Error.code` enum, guarded against the handlers and the spec source; internal errors answer JSON **500** with a `requestId`; `/openapi.json` binds to the request origin.\n- **Knowledge and websites.** Deleting one knowledge entry no longer retires the whole topic chain. Search hits carry `documentId`. Crawl targets on loopback, private-network, link-local and cloud-metadata hosts are refused, because the crawler dials from inside the deployment's network; `TALE_ALLOW_PRIVATE_CRAWL_HOSTS=1` opts an intranet in and the metadata endpoints stay refused regardless. Websites get strict bodies and a camelCase view.\n- **CRM, conversations, projects, tasks, agents.** One strict shared shape per resource, IEEE-754 safe-range refusals, coded 404/409s, the projects rule for the org header, `GET /api/v1/me`. Derived project keys re-derive on collision, the presigned upload URL expires with its intent, a task title past the cap answers 400 instead of silent truncation, and agent model, provider and tool grants are validated against the organization's own listings.\n- **Automations, MCP and chat.** `projectIds` and `deployedVersion` on automation listings, DELETE routes, JSON webhook envelopes, `get_docs` serving an authoring reference instead of the builder system prompt, declared tool envelopes, `projectId` on runs. Chat gains model facts and the default marker on `GET /models`, door-side model resolution (`CHAT_MODEL_UNKNOWN` / `CHAT_MODEL_AMBIGUOUS`), message `status` and `usage`, archive, delete and cancel routes, trimmed content and a BCP 47 `locale`.\n\nThe OpenAPI document is regenerated and the api-reference, mcp-endpoint and environment-reference pages are updated in English, German and French. The backend integration lane runs to completion again (146 of 146 lanes) for the first time since the MCP endpoint landed.\n\n### The chat reasoning pick reaches every connector (#3323)\n\nThe composer's reasoning-effort pick never reached two shipped connector families, and both drops were silent: a wire dialect ignores a knob it cannot spell.\n\n- **Anthropic connector.** The Messages API takes `output_config.effort`, but the wire spelled only `thinking.budget_tokens`, so the `effort` knob every Claude entry declares was dropped; the picker was inert on every native Claude model. Both dialects now spell an effort level and fold the five steps to their own vocabulary: OpenAI `reasoning_effort` low/medium/high (Extra and Max land on high), Anthropic `output_config.effort` low/medium/high/xhigh/max. A knob the wire cannot spell warns instead of vanishing.\n- **OpenAI-format aggregators serving Claude** (OpenRouter, Vercel AI Gateway, custom) declared `budget-tokens` for any `claude-*` id, which an OpenAI body cannot spell. A live listing now always infers the `effort` knob, and the two static OpenRouter Claude entries flip to it.\n- **Temperature.** Models released after Claude Opus 4.6 reject any temperature other than 1.0 with a 400, and the Anthropic branch sent the platform's 0.7 unconditionally, so a chat turn on `claude-fable-5`, `claude-opus-4-8` or `claude-sonnet-5` over the native connector failed before any knob mattered. The Anthropic wire now sends a temperature only for a model known not to reason, the gate `openai-modern` already applied.\n- **Haiku 4.5 has no effort parameter**, so `claude-haiku-4-5` drops its knob: the picker shows no reasoning section for it (it was inert) and the turn samples at the model's default.\n\nTwo catalog guards hold this: every shipped entry declares a knob its connector's `apiFormat` can carry, and the anthropic catalog declares `effort` only for models the vendor documents it for. Everything here is proven at the wire-body level against the vendor docs; a live turn on the native Anthropic connector with a Max pick and a tool call was not observed before release (no key in the build environment) and is the check still owed.\n\n### The CLI owns source-pinned managed deployments and configuration releases (#3317, #3318)\n\nManaged deployment and generic configuration releases move into the existing Tale CLI. A client repository owns its Tale content; an operator selects full source commits and destination inputs, runs `tale deploy prepare` into one bundle, ships it, and applies it with the same pinned CLI (`tale deploy --bundle`). A new configuration release is identified by the full client commit with artifact digests naming the exact bytes, and historical releases stay verifiable without rewriting their archives. Managed deployment keeps the existing 0.5 Compose identities and volumes, verifies source, image and bundle provenance, snapshots before adoption, reconciles native identity, clients and automation through verified readback, and an interrupted deployment keeps its recovery point and requires the same bundle on retry. `tale config deploy` and `tale config verify-native` release and verify a configuration pack alone, reading the operator session from `TALE_CONFIG_COOKIE`; `TALE_SOURCE_SSH_KEY` lets `tale deploy prepare` fetch private client source, and its line endings are normalized at the private temp-file boundary (#3318) so a CRLF key from a Windows secret store parses. Full managed rollout runs on Linux; Windows answers an explicit managed-bundle precondition error before any filesystem or native work. The ordinary workspace `tale deploy` keeps its behaviour and refuses the managed-only flags. A `setup-cli` GitHub action builds the CLI from one exact commit for deployment jobs. The docs gain a configuration-release guide, a managed-deployments section on the CLI install page and a pointer from the upgrades page, in all three languages.\n\n## Behaviour changes\n\n- **A budget cap is applied before a managed agent run starts** — a project agent's task or an automation's agent node — with the rule's own reason, and a run that does start may spend only what remains under the cap, in-flight turns counted. The run fails rather than retries until the period rolls over or an admin raises the rule.\n- **Managed harness spend is settled durably** and lands in Usage analytics attributed to the person who started the run and the agent or automation; a gateway hiccup at turn end no longer loses the figure.\n- **Qwen Code** is available as a managed harness (API-key auth only).\n- **Claude Code on DeepSeek** runs through DeepSeek's native Anthropic endpoint instead of the OpenAI conversion.\n- **The composer's reasoning pick** reaches native Claude models as `output_config.effort` (Extra and Max as `xhigh` and `max`) and Claude behind an OpenAI-format aggregator as `reasoning_effort`. Haiku 4.5 no longer shows a reasoning-effort section. Chat turns on Claude 5-series models over the native connector no longer fail on temperature.\n- **Website crawl targets on loopback, private-network, link-local or cloud-metadata hosts are refused** with **400** `WEBSITE_DOMAIN_NOT_CRAWLABLE`. A deployment that crawls its own intranet sets `TALE_ALLOW_PRIVATE_CRAWL_HOSTS=1`; the metadata endpoints stay refused either way.\n- **OAuth userinfo** answers 401 with a bearer challenge on an expired or invalid token, so clients retry with a fresh token instead of giving up.\n- **Deleting one knowledge entry** retires that entry only, not its whole topic chain.\n- **The MCP `get_docs` tool** serves an authoring reference rather than the builder's system prompt.\n- **A dot-segment `/api/...` URL** gets the API's JSON 404 from the proxy, never the SPA shell.\n- **The sandbox LLM gateway image is Bifrost v1.6.11**; the platform preserves its stored admin password on every config apply.\n- **The CLI gains `tale deploy prepare`, `tale deploy --bundle`, `tale config deploy` and `tale config verify-native`**; the workspace `tale deploy` is unchanged.\n\n## API contract changes\n\nIntegrations against `/api/v1` should note:\n\n- A known path with the wrong verb answers **405** with `Allow` (was 404).\n- A task `title` past 200 characters answers **400** (was silently truncated).\n- Websites pages and search views are camelCase with epoch-millisecond timestamps.\n- Conversation sync codes are UPPER_CASE; a missing contact is **404** and an ambiguous one **409**.\n- Page cursors are signed: a cursor minted before this release is refused with **400** `INVALID_CURSOR`, so restart any listing that was paging across the upgrade.\n- The REST skills and documents refusals speak the door's `{ error, code }` envelope.\n- Webhook trigger 404 and 413 answer JSON.\n- Additions: `GET /api/v1/me`; `projectIds` and `deployedVersion` on automation listings and DELETE routes for automations; thread archive, delete and cancel routes; message `status` and `usage`; model facts and the default marker on `GET /models`; `documentId` on knowledge search hits. The error-code set is additive — treat a code you do not know as a generic refusal of the status you got.\n\n## Known issues\n\n- Unchanged from v0.5.14 and v0.5.15, where both are 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 (over-masking, not under-masking). The PII library was not touched by this release.\n- **Thinking-block replay on the native Anthropic connector is not done.** The Claude 5-series thinks by default and requires its signed thinking blocks back only within a tool-use turn; omitting them degrades to thinking silently disabled for that request rather than an error. Doing it right needs signature capture and in-turn replay, which is why Haiku 4.5's manual-budget mode is not offered yet.\n- **Embedding calls from `rag_search` inside a harness turn are still unmetered** (platform-wide; needs an attribution model).\n\n## Migration notes\n\n- **One additive migration, `0089`**, applied automatically by the new image at boot inside the migrator's advisory lock: three nullable columns on `app.sandbox_session_ops` (`budget_cents`, `spend_settled_at_ms`, `key_revoked_at_ms`), a backfill that marks already-finalized ops as settled so the reconcile sweep does not re-read months of deleted keys, and one partial index over ops whose settlement is still open. The previous image ignores the columns, so it is safe mid-roll; nothing is dropped or rewritten.\n- **The gateway rolls in place on `tale deploy`** (it is in the compute tier) and Bifrost migrates its own store forward on first start; provider records survived the bump on the development stack. An existing admin password that does not meet Bifrost's new policy needs no action: the platform preserves the stored one, and the policy applies only to a password being changed.\n- **No config-file changes.** New environment variables are all optional: `TALE_ALLOW_PRIVATE_CRAWL_HOSTS` on the platform; `TALE_CONFIG_COOKIE` and `TALE_SOURCE_SSH_KEY` are read by the CLI process only and never belong in the platform's `.env`.\n- **Signed page cursors** invalidate any cursor issued by 0.5.18 (see API contract changes).\n\n## Upgrading\n\n- **On the 0.5 line** (0.5.0 – 0.5.18):\n\n  ```bash\n  tale update\n  tale deploy\n  ```\n\n- **Managed deployments** move by changing the pinned source commit, preparing a new bundle and applying it with the pinned CLI — see _Managed deployments_ on the CLI install page.\n\n- **New install**:\n\n  ```bash\n  curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash\n  mkdir tale-05 && cd tale-05\n  tale init\n  tale deploy\n  ```\n\n## What's Changed\n\n- feat(cli): own source-pinned deployments and config releases by @yannickmonney in https://github.com/tale-project/tale/pull/3317\n- fix(cli): normalize source checkout SSH key line endings by @yannickmonney in https://github.com/tale-project/tale/pull/3318\n- fix(sandbox): end, retry and bill managed harness turns correctly by @larryro in https://github.com/tale-project/tale/pull/3319\n- fix(sandbox): route DeepSeek Claude Code natively, bump the gateway by @larryro in https://github.com/tale-project/tale/pull/3320\n- fix(platform): close the 2026-09-11 API evaluation findings by @larryro in https://github.com/tale-project/tale/pull/3322\n- fix(platform): wire the chat reasoning pick to each connector's dialect by @larryro in https://github.com/tale-project/tale/pull/3323\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.18...v0.5.19","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.19","publishedAt":"2026-09-11T08:12:10Z"},{"tag":"v0.5.18","version":"0.5.18","name":null,"body":"## What's Changed\n* chore(deps): remove unused AI SDK provider dependency by @larryro in https://github.com/tale-project/tale/pull/3308\n* fix(platform): unify search scopes and polish task chrome by @AdeolaAdekoya in https://github.com/tale-project/tale/pull/3307\n* fix(platform): map embedding provider failures to stable REST codes by @larryro in https://github.com/tale-project/tale/pull/3309\n* fix(platform): classify provider account refusals and check providerSlug by @larryro in https://github.com/tale-project/tale/pull/3310\n* fix(platform): bring the MCP endpoint in line with the protocol it speaks by @larryro in https://github.com/tale-project/tale/pull/3311\n* fix(platform): make REST refusals name what failed and refuse bad cursors by @larryro in https://github.com/tale-project/tale/pull/3312\n* fix(platform): restore Knip frontend entry discovery by @larryro in https://github.com/tale-project/tale/pull/3315\n* fix(sandbox): unify quotas and expose real runtime capacity by @larryro in https://github.com/tale-project/tale/pull/3314\n* fix(sandbox): reclaim idle sessions under capacity and bill spend by @larryro in https://github.com/tale-project/tale/pull/3316\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.17...v0.5.18","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.18","publishedAt":"2026-09-10T17:34:28Z"},{"tag":"v0.5.17","version":"0.5.17","name":null,"body":"Tale v0.5.17 repairs notification links, records provider embedding capabilities, and enforces project permissions across public APIs.\n\n**API upgrade required:** Update integrations that access project agents, tasks and comments, chat, automation runs, or project file search to use `/api/v1/projects/{id}/…`. Project automation webhooks use `/api/projects/{id}/automations/webhook/{token}`. Replaced flat endpoints have been removed, and retired `projectId` routing fields are rejected. Global chat and run endpoints serve only resources without a project. Follow the updated [API reference](https://github.com/tale-project/tale/blob/v0.5.17/docs/en/develop/api-reference.md) and [webhook guide](https://github.com/tale-project/tale/blob/v0.5.17/docs/en/develop/webhooks.md).\n\nTask notifications now carry the project context their links need, and notification URLs respect subpath deployments. Migration `0087` backfills that context for stored task notifications. Provider definitions can declare embedding support as `supported`, `unsupported`, or `unknown`; missing declarations remain `unknown`.\n\n## What's Changed\n\n* fix(platform): give every notification a link to what it names by @Israeltheminer in https://github.com/tale-project/tale/pull/3306\n* feat(platform): let a provider declare whether it can embed by @Israeltheminer in https://github.com/tale-project/tale/pull/3219\n* fix(platform): enforce project scope across public APIs by @larryro in https://github.com/tale-project/tale/pull/3305\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.16...v0.5.17","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.17","publishedAt":"2026-09-10T04:07:41Z"},{"tag":"v0.5.16","version":"0.5.16","name":null,"body":"Connected applications can sign existing users in with Tale using native OIDC and synchronize conversations with Inbox without an email connector. Organization administrators can register, rotate and disable exact-callback clients. Native password, passkey, MFA and SSO login remain available.\n\nConversation synchronization adds source ownership, versioned snapshots, durable reply receipts, claim leases, backoff and native Retry/Discard controls. Contact updates support an atomic revision precondition to preserve concurrent staff edits. The data migrations are additive. New interfaces and consent controls are documented in English, German and French.\n\nValidation includes all 47 PR checks, 580 native integration checks over real HTTP/Postgres/S3, 46 focused identity checks, and browser consent, MFA and Inbox delivery checks. Native amd64 and arm64 images are published after a separate container validation and startup gate.\n\nThe stable OAuth provider remains covered by [GHSA-p2fr-6hmx-4528](https://github.com/advisories/GHSA-p2fr-6hmx-4528), an unbound resource-indicator issue. This release applies the documented single-audience mitigation: only native userinfo access tokens are allowed, foreign and API-base resources are refused, and OAuth access tokens cannot authenticate REST endpoints. Applications use verified ID tokens for identity and native API keys for REST. The dependency version advisory remains visible; this is a tested configuration mitigation, not an upstream package fix.\n\nDeployments must register each relying party with its exact callback and configure its access policy before enabling sign-in. [Implementation and validation](https://github.com/tale-project/tale/pull/3304).\n\n## What's Changed\n* fix(platform): repair automation names, triggers, MCP slug, search by @larryro in https://github.com/tale-project/tale/pull/3302\n* feat(web): report web and docs errors to Sentry by @yannickmonney in https://github.com/tale-project/tale/pull/3303\n* feat(platform): add native identity and conversation APIs by @yannickmonney in https://github.com/tale-project/tale/pull/3304\n\n\n**Full Changelog**: https://github.com/tale-project/tale/compare/v0.5.15...v0.5.16","htmlUrl":"https://github.com/tale-project/tale/releases/tag/v0.5.16","publishedAt":"2026-09-09T13:28:08Z"}],"fetchedAt":"2026-10-08T17:13:15.044Z","source":"live"}