Skip to content

v0.24.0

Choose a tag to compare

@donalddellapietra donalddellapietra released this 21 Aug 05:02
· 22 commits to main since this release

Minor Changes

  • Admin direct gifts: admins can gift bonus credits straight to an existing user by email from the dashboard's admin Credits page (renamed from "Credit Requests"). Each gift is recorded as an already-approved kind='gift' row in credit_requests — it shows tagged in the admin list and as "Gift from the DeepSpace team" in the recipient's Billing history, and the grant shares the same expiring-bonus semantics as approvals and promo redemptions. The credits dialogs were also hardened: API errors always surface curated copy (raw server/transport messages never render), and Esc/backdrop can't dismiss a dialog while its request is in flight.

  • Managed knowledge base: an app can declare one [[ai_search]] binding with instance_name = "auto" and get an isolated Cloudflare AI Search instance, provisioned on deploy and torn down on undeploy like any other "auto" resource. The new knowledge(env) helper on deepspace/worker and deepspace/server exposes add / list / remove / search, plus scoped({ folder }) for a handle whose folder a caller cannot widen — safe to hand to an agent as a tool. Folders are recursive prefix scopes; oversized text files are split at UTF-8-safe boundaries and oversized binaries are refused rather than corrupted.

    The resolved binding is control-plane metadata only: it is stripped before the Workers-for-Platforms upload, so no Cloudflare token, instance id, or ai_search binding ever reaches the customer Worker, and ai_search_namespaces is refused outright. Every operation is authenticated per app and charged in the API worker, so calling the route directly is billed exactly like the helper. Search is charged up front, ingestion reserves and settles against the real indexed-token count, and storage is sampled daily — all at the existing 10% platform binding markup (third-party integrations are unchanged at 30%) and surfaced in the dashboard's Binding Usage card. Available on every plan.

  • Add --viewport WIDTHxHEIGHT to deepspace test screenshot. The helper normalizes the dimensions for Playwright's installed Chromium browser, enabling reliable mobile captures without selecting a device profile backed by an uninstalled browser.

  • Promo codes: admins (and Beacon, via app identity + owner JWT) can bulk-mint single-use codes that grant expiring bonus credits — idempotent per (campaign, email) when a campaign is given — and users redeem them from a new "Redeem Code" card on the dashboard Billing page. Includes an admin mint dialog on the Credits page and a rate-limited, race-safe redeem endpoint. Each code also carries a clickable claim link (dashboard /claim/<token>): signed-out recipients see the offer, sign in, and the credits auto-apply — the grant attaches to the claim, not to signup, so pre-existing accounts and any click/signup order work.

  • Remove the workspace:default shared scope — the one platform-wide Durable Object that held "cross-app shared business data" for every app. The WORKSPACE_SCHEMAS export (deepspace/schema), the workspace entry in GLOBAL_DO_TYPES, and the platform worker's acceptance of workspace:default as a global scope id are gone; the platform now answers 400 Invalid global scopeId for it. Cross-app data is an app's own dir:{appId} room or a conv:{convId} room, never a single DO shared by all apps. Team-scoped RBAC (teamField / a team_members collection) is unchanged and remains available to any app's own rooms.

Patch Changes

  • Stop gating source selection and transfers on unrelated local Git state. Initial GitHub claims and direct GitHub repository changes now require only remote reachability, matching deploys that intentionally ship dirty and unpushed bytes; provider transfers still verify the authoritative refs and release lineage before flipping authority.

  • Stop credit-request approvals from resurrecting a user's expired bonus credits: when the existing bonus allocation has already expired, approving a grant now resets the balance to just the newly granted credits instead of adding to the stale amount and refreshing its expiry.

  • deepspace app update now reports the secure-room-boundaries blocker wherever the legacy room proxy lives, not only at src/server/realtime-routes.ts. An app that had moved its WebSocket proxy and still forwarded identity in the URL passed app update clean, then reached the room as anonymous on every connection — RBAC filtered every row and signed-in users saw an empty app. The blocker scan is keyed on content (a file that forwards a request and sets userId/role… on its URL) instead of on the file path; the auto-rewrite is unchanged. Rooms also log a warning when a connection carries ?userId= with no x-user-id header, so the pre-0.19 proxy shape names itself in wrangler tail.

  • Return a stable duplicate result when an update violates a collection's uniqueOn constraint.

  • Fixes from the v0.23.2 production AX pass. Server: the user.list assistant tool now returns exactly what the socket roster gives the caller (admins full rows through the read policy; everyone else the public identity projection, honouring read: false and roster: 'read-policy'; app actions keep full rows) — it used to hand every member every user's email; the public identity projection now carries lastSeenAt, so usePresence() works for non-admins (it was always offline for everyone but the owner); timestampTrigger writes canonical epoch seconds on a storage: 'number' date/datetime column instead of an ISO string that stored the year or milliseconds inconsistent with explicit writes; an empty string written to a text column is stored as '' (it was folded into NULL, so the field read back absent and a default: '' never materialized); where on records.query/deleteWhere refuses arrays and non-objects at the one shared chokepoint; deleteWhere authorizes with delete permission independently of read permission; the client's writableFields refusal names the role and field again. CLI: deploy states on both surfaces that a rename does not carry the display name (src/constants.ts and wrangler.toml [vars] APP_NAME) and the --rename success envelope carries renamedFrom/staleDisplayName; collaborators add discloses that a collaborator can read and change every secret (grants in --json); the false "attributed to the app's owner" warning on collaborator deploys is gone (the ledger records the collaborator); app source github removes a stale space git remote (spaceRemote in --json), refuses stale import writes after an authority flip, rechecks GitHub after import, and preserves a successful flip when local remote reconciliation fails; push/pull/clone share one GitHub-source refusal with repository and appId as fields (apiFetch now keeps every server-provided field on ApiError.details); push's uninitialized-scaffold refusal is actionRequired (exit 2) like deploy's; releases/status/activity name a GitHub-source release's source instead of "(no source recorded)"; a GitHub-source deploy reports its branch and whether the tree was dirty (both surfaces); app update is now a successful read-only guide tied to the running CLI version: it reads only explicit app manifests and never rewrites source, stamps migrations, runs an installer, scans unrelated packages, imposes DeepSpace Git transport checks, or treats outstanding work as a command failure; local/file/workspace/VCS SDK specs report dependency_unverified instead of a false aligned state, and malformed migration ledgers refuse with invalid_migration_manifest; test run --json routes the Playwright dependency preflight to stderr through the same chokepoint as the suite and says up front when it will run apt-get; the unknown_suite refusal mentions --grep and single-spec paths; test accounts create no longer prints the password (it is saved locally; list --reveal shows it); ActionTools gains deleteWhere (template + upgrade guidance). Deploy locking no longer reclaims stale paths automatically, and release callbacks delete only the lock they own. Scaffolder: create-deepspace@X pins deepspace to exactly X (a caret let a pinned scaffolder install the newest SDK) and prints the SDK version it installed.

    Second round (wave 2 of the pass — recovery, upgrade, observability). CLI: a present-but-malformed DEEPSPACE_APP_ID is refused as invalid_app_id at the one id resolver (it was reported as "no app id" and the offered app init orphaned the app; app init now refuses to overwrite it without --new-id); the "no app dir" vs "app dir without an id" states get one code each (not_in_app_repo / app_not_initialized) across deploy, secrets, test, dev; secrets and auth login refusals go through the shared envelope (not_authenticated with its action, network_error naming the service and env var, file_not_found, invalid_credentials); not_authenticated names the selected plane vs the plane holding a session (DEEPSPACE_ENV/DEEPSPACE_AUTH_URL) and the headless login form; push/pull/deploy refuse merge_in_progress mid-merge instead of pushing the pre-merge commit; deploy takes a local .deepspace/deploy.lock (deploy_in_progress names the other run) so two deploys in one directory no longer race on dist/, release_in_progress explains itself and carries a retry action, forbidden names the signed-in account and the app, and after the edge confirms a release deploy wakes the worker once; undeploy's confirmation says what is destroyed (the app's Durable Objects: records, messages, canvas state, cron history — the old sentence said "data stays") and what stays (secrets, registration), names the app, and a second undeploy reports alreadyUndeployed; logs on a never-deployed app refuses app_not_deployed; app init --json reports the plane as env (the wrangler slot is wranglerEnv); test accounts list --json prints passwords only with --reveal; status --help names env/services; integrations invoke refuses a paid call outside a terminal or under --json without --yes (cost_confirmation_required; the interactive prompt defaults to No) and integrations info synthesizes an example body from the schema's required keys; app update reports guidance for the build-injected app id and action-route bearer guard without mutating the checkout; app init's app_not_registered refusal ships the --new-id action; workspace land pins its follow-up to the surviving primary checkout instead of the managed worktree it just removed. Server: a cron schedule arms on the worker's first request (armCronRoom, wired in the template) instead of waiting for its Durable Object to be fetched by a visitor, and rejected or non-2xx wake attempts remain retryable; cron and job runs log one structured line each; the template's app.onError logs a thrown error's message (Hono's default rendered only the stack frames, so deepspace logs never carried it). Platform (ships with the next worker deploy): a created-never-written secrets config answers {} instead of "Project DEK not found"; non-request log invocations (alarms) are labelled with their entrypoint; the integrations catalog discloses the customer price after markup and currency conversion (null for metered per_actual_cost endpoints, variesWithInput where multipliers apply), and integrations list/info/the consent prompt label an input-dependent figure as a base rate that can move lower or higher instead of falsely calling it a floor. Template: the action route logs [action] <name> caller=<userId> (the platform request log carries no user), with upgrade guidance for existing apps.