Skip to content

Tale v0.5.46

Choose a tag to compare

@larryro larryro released this 21 Sep 18:25
7cb60c0

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.

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

Highlights

Entra ID app roles map to Tale roles (#3455)

An 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).

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

A gateway for pooled Claude and ChatGPT subscriptions, as a new standalone service (#3452)

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.

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

The gateway image builds, and the pipeline proves it before a tag does (#3456)

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

The egress proxy is probed with a request, not a connect (#3451)

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

ui.tale.dev counts a pageview for every guide a reader opens (#3450)

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

A managed deployment's configuration reader refuses out loud (#3453)

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

Behaviour changes

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • Component library: @tale/ui/icons/openai-icon exports OpenAIIcon, the same path the marketing site already draws.
  • 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.
  • 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.

API contract changes

  • 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.
  • MCP: unchanged.
  • 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.

Security

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

Known issues

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • A reply-language directive is a directive: a model may still answer in the prompt's language and nothing on the wire marks a slip.
  • No image input on the REST chat send. A vision model reads an image over REST only on a thread the app continued with an image attachment; the design of an attachments field on the send is recorded as contract debt.
  • No REST door authors or deploys an automation — POST /automations answers 405 by design. Build and deploy in the app, or over the MCP endpoint's save_automation and deploy_automation; the REST key lists, reads, runs, answers asks and wires triggers.
  • The x-tale-pagination extension is a declaration on the OpenAPI document; generated clients that do not read vendor extensions still branch on the two cursor names until cursor is retired.
  • The app's zip upload of a skill bundle rewrites the bundle and moves updatedAt even when the zip is byte-identical, where PUT /skills/{slug} writes nothing.
  • A tool call the reply cap cut keeps input: {} on the stored tool-call part; the raw text the model emitted is still not on the transcript.
  • Folder names written before 0.5.24 keep their bytes; a sync engine's hub-path lookup can create an NFC twin beside a legacy NFD folder. No backfill ships.
  • Two bounded document readers still filter after their cut; both report an honest truncated, so a caller can tell the answer was cut.
  • 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.
  • 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.

Migration notes

  • 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.
  • Rolling-deploy safe. No schema, contract, catalog or message change; the previous and the new platform image read and write the same tables.
  • 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.
  • 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.
  • 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.
  • 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.
  • @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.

Upgrading

  • On the 0.5 line (0.5.0 – 0.5.45; there is no 0.5.42 deployment to be on):

    tale update
    tale deploy

    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.

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

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

  • The AI gateway is deployed on its own, not by tale deploy:

    cp services/ai-gateway/.env.example services/ai-gateway/.env   # fill in the four secrets
    VERSION=0.5.46 docker compose -f compose.ai-gateway.yml pull
    VERSION=0.5.46 docker compose -f compose.ai-gateway.yml up -d

    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.

  • New install:

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

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

What's Changed

  • fix(sandbox): probe the egress proxy with a request, not a connect by @yannickmonney in #3451
  • feat(ui-docs): count a pageview for every guide a reader opens by @yannickmonney in #3450
  • fix(cli): make the config reader's refusals actually refuse by @yannickmonney in #3453
  • feat(ai-gateway): pool Claude and ChatGPT subscriptions by @yannickmonney in #3452
  • fix(platform): read Entra app roles from the ID token roles claim by @larryro in #3455
  • fix(ai-gateway): build the image from the whole workspace manifest set by @larryro in #3456

Full Changelog: v0.5.45...v0.5.46