Skip to content

Bump Cloudflare OS to a679442 - #7

Merged
prepor merged 3 commits into
mainfrom
prepor/bump-cloudflare-os
Oct 21, 2026
Merged

prepor merged 3 commits into
mainfrom
prepor/bump-cloudflare-os

Conversation

@prepor

@prepor prepor commented Oct 21, 2026 •

Copy link
Copy Markdown

Moves the pinned cloudflare-os submodule to upstream main HEAD, with only the wrapper changes the bump requires.

Warning

Merge = production deploy. deploy.yml deploys main to https://os.stelaxis.org on push. This bump changes stored data in ways the old version cannot read: the Workshop's per-workspace storage, Context collections, and GitHub credentials. It also adds Durable Object migrations to three Workers, which Cloudflare will not roll back across. The sequential deploy is not compatible across versions in either direction, so it has to run as a short announced maintenance window. Read Migration plan, Rollout plan and Before merging first. Nothing was deployed from this branch.

Gitlink

Commit Date
Old 6478a1448a11524e2f7c2575ad66fab0bc47c433 2026-08-19
New a6794421279057cb8813d3e5d0710754f5d41618 2026-10-20

Compare: cloudflare/cloudflare-os@6478a14...a679442 (290 ahead, 0 behind; 1458 files).

Provenance: the commit is upstream's squash merge of cloudflare/cloudflare-os#843 into main, GitHub-signed (verification.verified: true, reason valid). 6478a14 is an ancestor. Upstream has no tags and no releases, so main HEAD is the latest version. Upstream main was still a6794421 when this PR was opened, so there's no newer head to report. .gitmodules is unchanged.

cloudflare/cloudflare-os-starter bumped to the same a679442 in three steps (its cloudflare#17/cloudflare#22). Its changes served as a reference, but it has since restructured (cf-os CLI, link: dependencies), so nothing was copied wholesale. Every change below was made and verified against this repository's own layout.

Wrapper changes, and why each is required

File(s) Change Why
pnpm-workspace.yaml Catalog re-synced verbatim with the submodule's; cloudflare-os/scripts added as a member; overrides vite → vite@*, plus @cloudflare/vitest-plugin>miniflare / >wrangler → catalog: Two submodule packages here resolve catalog: against this file (drift means two capnweb copies). @gadgets/error-reporting now declares @gadgets/scripts: workspace:*, which resolves only against a member of the installing workspace, so without it pnpm install fails. vite-plus 1.x depends on vite as an alias that a bare vite override would clobber. See Catalog re-sync.
pnpm-lock.yaml Regenerated Follows from the above, seeded so it resolves the same versions as cloudflare-os/pnpm-lock.yaml (see below).
package.json, packages/{custom-gatekeeper,gatekeeper-book}/package.json, both vitest.config.ts @cloudflare/vitest-pool-workers → @cloudflare/vitest-plugin Upstream renamed it with the move to Vitest 5 (cloudflare-os#810). The re-synced catalog has no vitest-pool-workers and puts vitest at ^5.0.5.
package.json test Adds --filter '!@gadgets/scripts' Now that it's a member, the root vp run … test would also run @gadgets/scripts's own test task: upstream's workspace-wide guards over its own packages (cwd: '..'). These can't run from this workspace (gadgets-with-timeout not found) and they belong to the submodule's CI.
packages/{custom-gatekeeper,gatekeeper-book,error-reporter}/vite.config.ts Task input/output moved under cache; vitest runs get --no-fsModuleCache vite-plus 1.x reads cache settings under cache (voidzero-dev/vite-task#749). Vitest 5's module cache is one directory under the root node_modules, shared by every package's vitest, so parallel tasks race on it (ENOTEMPTY). Upstream's shared task passes the same flag. Without it, test tasks also stopped caching.
packages/custom-gatekeeper/src/custom.ts, packages/gatekeeper-book/src/book.ts commitReconnect() on the account entrypoint; getAgentCatalog(): null on the Gatekeeper GatekeeperUser.commitReconnect is now required (connect handoff, cloudflare-os#464); without it tsc fails. getAgentCatalog is optional in the type, but the Workshop now calls it on every ambient Gatekeeper every turn and only stops after a null answer. Both of ours are ambient singletons, so leaving it out would mean a handled overseer.catalog-fallback issue report on every chat turn. Neither has a connect flow or a catalog.
packages/error-reporter/package.json @cloudflare/workers-types → catalog: The catalog now carries it (workshop-shared uses catalog:), so one version is resolved across the workspace.
packages/gatekeeper-book/src/mirror.ts, __tests__/book.test.ts [...kv.list()] → Array.from(kv.list()); .sort → .toSorted Two vp lint errors. Pre-existing: a clean export of ce7c13e with its own lockfile fails vp lint on these exact two lines. Fixed only so the pnpm lint gate is green. Array.from keeps the materialize-before-delete behavior.
docs/customization.md, deployment.jsonc, scripts/*.ts (comments only), operator skill references/upgrade-and-rollback.md Google section now lists eight resource types, new scopes and APIs, and the Chat app config step. The Slack table now has the current Conversation and Thread scopes, which both need team:read. Upgrade steps name three workspace members and the version-alignment check, and say when restoring the previous gitlink is safe. Behavior changed upstream (see the Gatekeepers row in Compatibility review). No logic change in scripts/deploy.ts.

Not changed, deliberately:

  • Upstream source: untouched.
  • Web Search Gatekeeper: not deployed. Upstream now preinstalls gatekeeper-websearch (#685) on hosted installs, but it's a new capability (web search didn't exist at 6478a14) with its own Worker and Workers AI search spend. The Workshop discovers it by binding and works without it. It's an opt-in follow-up, not a compatibility fix.
  • Compat dates of our own Workers (custom, book, error-reporter): left at 2026-08-04, since nothing requires moving them.

Catalog re-sync

  • The catalog: block in pnpm-workspace.yaml is now byte-identical to cloudflare-os/pnpm-workspace.yaml's, comments included. diff of the two blocks is empty.
  • Key moves: capnweb ^0.11.1 → ^0.12.0, capnweb-validate 0.2.4 → 0.3.0, vitest ^4.1.10 → ^5.0.5, vite-plus ^0.2.8 → ^1.2.0, wrangler ^4.119.0 → ^4.147.0, @cloudflare/vitest-pool-workers → @cloudflare/vitest-plugin ^1.4.3. Added: @cloudflare/workers-types, miniflare 5.20261001.0-alpha, typescript6, zod, diff, @vitest/browser-playwright, playwright.
  • Matching ranges isn't matching versions. A fresh resolve would float wrangler to 4.151.3, whose own miniflare (5.20261019.1-beta) differs from the catalog's exact alpha that the vitest-plugin override pins, giving two miniflare/workerd stacks. So the lockfile was seeded to resolve what upstream's lock does, and it records the ranges at: capnweb 0.12.0, capnweb-validate 0.3.0, wrangler 4.147.0, miniflare 5.20261001.0-alpha, workerd 1.20261001.1, vite-plus 1.2.0, vitest 5.0.5, @cloudflare/vitest-plugin 1.4.3, @cloudflare/workers-types 5.20261005.1, zod 4.6.5. Result: exactly one capnweb, one wrangler, one miniflare, one workerd in pnpm-lock.yaml. (vitest 5.0.4 and typescript 5.9.3 also appear: the first is bundled by vite-plus 1.2.0, the same as upstream's lock; the second belongs to capnweb-validate, as before.)
  • Vite+ tasks: no upstream package that buildCommands() names gained a nested cached vp run. Context and Scheduler still nest vp run --cache build:app, which is already pre-run uncached. GitHub, Google, Slack and MCP Portal keep build:configurator → build. Workshop backend's build is cache: false with uncached codegen dependsOn (build:bundled-blueprints, build:browser-runtime). The frontend is a task with env: ['VITE_*'], and its VITE_CF_ACCESS_MODE is passed explicitly. buildCommands() is unchanged (19 steps, all --no-cache) and still rebuilds from source; scripts/deploy.test.ts passes unmodified apart from a comment.

Compatibility review

Area Finding
Auth / Access Access mode is unchanged: same CF_ACCESS_ISS/CF_ACCESS_AUD/ADMINS, same JWT path. New: native-app device-session handoff (#712, #780), not reachable without that client. Gatekeeper connects are now bound to the initiating browser (#464): complete() returns a single-use ticket the opener redeems over the user's own session, so PUBLIC_BASE_URL is now required for connects (we already set it). The Router now stamps Cloudflare-OS-Router-Version (a display label, overwritten on every request). No change to the route, Preview URLs or workers_dev.
Shared RPC / Gatekeeper contract (workshop-shared/src/gatekeeper.ts) Required: GatekeeperUser.commitReconnect() (added to Custom and Book). getAgentCatalog() dropped its authorizer parameter and is now polled every turn (added as null). Optional/kernel-side: reconnectComplete, getAcceptedResourceUrlPatterns, user-selection tickets, git-pull transfers, createResource/applyCreation, versionTag(), ActionFields; prohibitAllSharing → containsRestrictedData. applyAction gains a GitCache argument our implementations can ignore. Not backward-compatible with the old Workshop: a new Gatekeeper refuses some of the old Workshop's calls (applyAction without GitCache, startAppUi without userSelectionExchange, connect completion); see Rollout plan.
Frontend ↔ Workshop API The Router serves the frontend bundle (assets.directory: ../workshop-frontend/dist). The old frontend loads and saves editor contents through Overseer.subscribeToCode()/updateCode() (workshop-frontend/src/GadgetCodeInterface.tsx@6478a14:591,648; workshop-shared/src/api.ts@6478a14:1672,1680). The new API removes both, in favour of submitCodeChange() and commit-based reads (api.ts@a6794421:2797; overseer-storage.ts@a6794421:983). An old frontend against the new Workshop cannot load or save code. That covers the deploy window, a rolled-back Router, and every browser tab left open across the deploy.
Error Reporter (@gadgets/error-reporting) Source unchanged; ErrorReporterProps/ErrorEventV1 are identical. It only gains the @gadgets/scripts devDependency (hence the workspace member). The Workshop's ERROR_REPORTER binding (entrypoint and props) is unchanged in the derived config.
Upstream Gatekeepers (GitHub, Google, Slack, MCP Portal) Secrets unchanged (CLIENT_ID/CLIENT_SECRET ×3; MCP Portal under auth: "oauth" needs none). New env reads are all optional and unset here: GitHub WEBHOOK_ORIGIN (webhooks off when unset); Google PUBSUB_* (push off), CF_AI_GATEWAY (PDF conversion then calls Workers AI toMarkdown() directly, with no gateway); MCP Portal MCP_PORTAL_HIDDEN_SERVER_IDS. Google gains an ai: AI binding (Drive PDFs, #843), which deploy.ts carries over from the base config. Google authority grows: new Drive (drive.readonly), Slides and Chat resources, Chat with send/edit/react behind approval, and Sheets goes from spreadsheets.readonly to read-write spreadsheets, which retracts every existing Sheets grant: owners re-consent on next connect. /admin is a deny-list (disabledResources), so all new resource types are offered to every user by default once deployed. Actions queued before the upgrade: some can no longer be approved after it — Gmail send/reply/forward, any Gmail action through a search- or label-restricted binding, every pending Google Docs edit, and GitHub merges queued without a bound head (see Migration plan). Slack narrow grants: Conversation grants now also need team:read, and Thread grants need team:read plus the conversation :read scopes, because every read first checks workspace membership through team.info. Workspace grants are unaffected. The Slack app's scope set is unchanged.
Durable Objects Additive class migrations only: Workshop v3, GitHub v1, Google v4–v8. No deleted, renamed or transferred classes. In-code storage changes that the old version can't read: the Workshop's Overseer DO (v1 → v5, irreversible); Context (lazy ACL migration aclVersion 0 → 1, byte-encoded document bodies, { id }-only library rows); GitHub (credentials move into the kit's layout and the legacy keys are deleted; legacy unbound merge actions refused); Google (Gmail's and Docs' pending actions keep their keyspace but legacy entries are refused or partly refused). Additive only: Workshop User DO backfills, the rest of Google, MCP Portal and Slack. None: Scheduler (schemaVersion still 1), Custom, Book, Error Reporter. Full inventory in Migration plan.
Base Wrangler configs Upstream's wrangler.jsonc files are now generated from cloudflare.config.ts but still committed, so deploy.ts reads them as before. Every upstream Worker's compatibility_date goes 2026-02-02 → 2026-09-04. The Workshop drops enhanced_error_serialization, a no-op: it has been default-on since 2026-04-21 in workerd's compatibility-date.capnp. $schema keys removed.
Sections deploy.ts replaces or reconstructs Checked each one against the new bases. vars: no upstream Worker gained a required var (MCP Portal's base still only sets MCP_ALLOW_INSECURE, which we pin). services: the base Workshop declares none, and the Router's base still only WORKSHOP_BACKEND. kv_namespaces/r2_buckets: same bindings, with base preview_ids (dev-only) dropped as before. assets: still deleted on the Workshop and kept on the Router. artifacts: still disabled. secrets.required: unchanged. ai: kept from Google's base and set on the Workshop. observability: merged as before. migrations, compatibility_*, build, rules, browser, worker_loaders: inherited from base. Nothing new is silently dropped; see the derived diff below.
Vars / secrets / bindings / KV / R2 / assets No new required var, secret, service binding, KV, R2 or assets change. New optional Workshop vars (CLOUDFLARE_OS_DEPLOYMENT, CF_VERSION_METADATA, NOTIFICATION_SERVICE_URL/CFOS_INSTALL_*) come from upstream's hosted deploy service. Left unset, update checks and push notifications stay off.
AI transport (AI Gateway) AiGatewayConfig's deploy-facing contract is unchanged: same CF_AI_GATEWAY* vars and the same constructor refusals that deploy.ts mirrors. The derived Workshop vars are identical (default gateway, cloudflare,anthropic, binding transport, no token). Admins can now add gateway models in /admin (#616).
Deps / lockfiles / build See Catalog re-sync. New upstream workspace member @gadgets/bundled-blueprints is reached only via the Workshop build inside the submodule's own install. Both lockfiles install --frozen-lockfile.

Compat flags newly defaulted by 2026-02-02 → 2026-09-04 (upstream Workers only)

fetch_iterable_type_support, delete_all_deletes_alarm, text_decoder_replace_surrogates, text_decoder_cjk_decoder, unhandled_rejection_after_microtask_checkpoint, websocket_close_reason_byte_limit, queue_expose_error_codes, websocket_standard_binary_type, encoder_stream_spec_compliant_backpressure, writable_stream_spec_compliant_writer, web_socket_auto_reply_to_close, enhanced_error_serialization, diagnostics_channel_has_subscribers_getter, throw_on_not_implemented_tls_options, nodejs_compat + nodejs_compat_v2 (2026-08-04: now implicit on the Router and every upstream Gatekeeper), plus Python/Workflows/Containers-only flags. This is the date upstream tests at.

Derived-config diff summary

Generated by calling each tree's own generateConfigs() with its own base configs (old: a clean export of ce7c13e with 6478a14; new: this branch), normalized to sorted keys, with the account ID, Access issuer/audience and admins redacted. Nothing generated was committed.

Worker Change
stelaxis-os-router compatibility_date 2026-02-02 → 2026-09-04 ($schema dropped)
stelaxis-os-workshop date → 2026-09-04; flags drop enhanced_error_serialization (default-on); migrations + v3 UserDirectoryDurableObject. Bindings and vars are identical: all 9 GATEKEEPER_* + ERROR_REPORTER services with the same entrypoints/props, WORKERS_AI, BROWSER, LOADER, KV/R2, ADMINS, CF_ACCESS_*, PUBLIC_BASE_URL, CF_AI_GATEWAY*.
stelaxis-os-context, stelaxis-os-scheduler, stelaxis-os-slack, stelaxis-os-mcp-portal date → 2026-09-04 only
stelaxis-os-github date → 2026-09-04; migrations + v1 GitHubHookDriver
stelaxis-os-google date → 2026-09-04; + ai.binding = "AI"; migrations + v4 GoogleDriveGatekeeperImpl, GoogleDocGatekeeperImpl; v5 GoogleChatGatekeeperImpl; v6 ChatHookDriver; v7 GmailHookDriver; v8 GoogleSlidesGatekeeperImpl
stelaxis-os-gatekeeper, stelaxis-os-book, stelaxis-os-error-reporter no change
buildCommands() no change

pnpm check's dry-run binding tables confirm env.AI on Google and the unchanged Workshop binding set. Every non-Router Worker is still workers_dev: false, preview_urls: false, with no route.

Migration plan

Durable Object and storage changes exist. They need specialist review before merge (operator skill: hard stop on migration or irreversible data change). Inventory re-audited across every storage owner this deployment runs: the Workshop's DOs plus BLUEPRINTS/AVATARS KV and BLUEPRINT_CONTENT R2; Context's DOs and KV; Scheduler, GitHub, Google, Slack and MCP Portal DOs; Custom, Book and Error Reporter.

old and new migration tags/classes:
  stelaxis-os-workshop  v0,v1,v2 -> + v3 new_sqlite_classes [UserDirectoryDurableObject]
  stelaxis-os-github    v0       -> + v1 [GitHubHookDriver]
  stelaxis-os-google    v0..v3   -> + v4 [GoogleDriveGatekeeperImpl, GoogleDocGatekeeperImpl]
                                    + v5 [GoogleChatGatekeeperImpl] + v6 [ChatHookDriver]
                                    + v7 [GmailHookDriver] + v8 [GoogleSlidesGatekeeperImpl]
  No deleted/renamed/transferred classes. Context, Scheduler, Slack, MCP Portal, Custom, Book: no tag change.

  In-code storage changes (no wrangler tag), by owner (paths under cloudflare-os/packages/ @a6794421):
  - Workshop OverseerDurableObject: storage version 1 -> 5 on first wake
    (OVERSEER_STORAGE_VERSION, workshop-backend/src/storage-schema/overseer-migrations.ts):
      1->2 migrateToGitStorage: legacy Yjs code log -> synthesized git commits; rewrites gadget heads
           (commitId), merge messages, blueprint records (codeVersion deleted -> commitId); converts
           every live chat to the commit-pinned model; deletes outstanding chat drafts
      2->3 action-index backfill; 3->4 workpiece `type` stamp; 4->5 blueprint-upstream backfill
           (bounded to 1000 chat messages per workspace)
  - Workshop blueprints: newly published blueprints are git release packs in R2 BLUEPRINT_CONTENT,
    not Yjs snapshots (#659). BLUEPRINTS KV has no migration step (values must stay readable); new
    code installs bundled blueprints (workspace-docs/sheets/slides) into it.
  - Workshop UserDurableObject (storage-schema/user-storage.ts): additive lazy backfills -- the
    outputs index (outputsBackfilled / outputsBackfillCursor, user.ts #backfillOutputs) and the
    deployment-wide user directory (directoryRev -1 backfills the new UserDirectoryDurableObject);
    device-session records (#712).
  - Context ContextCollectionDurableObject (gatekeeper-context/src/context-collection.ts):
      ACL: #ensureGrants() (:300), lazily on the first access check, gives a private collection's
           creator an explicit owner grant and stamps aclVersion 0 -> 1 (ACL_VERSION = 1, :144);
           sharing state is new (grants, everyoneCanView, deletionPending, git-token issuer records)
      bodies: every document written after the upgrade is stored as bytes (contextRecord ->
           encodeStoredContextBody, :104-108); 6478a14 types body as a string and returns
           record.body verbatim (context-collection.ts@6478a14:348)
  - Context UserLibraryDurableObject (gatekeeper-context/src/user-library.ts): owned-collection rows
    written after the upgrade are { id } only (:58-60). 6478a14's rows carried denormalized
    title/description/icon/lastUpdated, which 6478a14 lists from; existing rows are left in place
    but no longer refreshed. New sharedCollections index and `disconnected` flag.
  - GitHub UserAccount (gatekeeper-github/src/github.ts:1387-1399): on first use, credentials move
    from the legacy accessToken/scopes keys into gatekeeper-kit's CredentialCoordinator record, and
    the coordinator deletes the legacy keys after committing (gatekeeper-kit/src/credentials.ts:182).
    Pending actions stay under `action:<id>` and every type is unchanged except
    MergePullRequestAction, which gains required expectedHeadSha/headBranch: a merge queued before
    the upgrade without a bound head is refused ("Discard it and queue the merge again",
    github.ts:4214) -- that refusal makes no API call, but it cannot tell whether an earlier
    attempt already merged. 6478a14 called GitHub first and recorded success only afterwards
    (github.ts@6478a14:3254ff, #markActionApproved after each call), so a lost response leaves an
    applied action `pending`. The new code's only guard is its own `approved` record
    (github.ts:3999-4005). A merge cannot happen twice, but the other legacy types it still
    applies (create issue/PR, comment, review, diff reply) would be sent again.
  - Google Gmail pending actions (GmailGatekeeperImpl): new code reuses the `pending:action:<id>`
    keyspace of 6478a14's PendingActionStore (gmail.ts:350; google.ts@6478a14:1184) with new action
    shapes. Legacy entries (isLegacyGmailAction, gmail.ts:960) are partly refused, permanently --
    not just while versions are mixed:
      send / reply / forward: always refused, "cannot be retried safely. Inspect Sent mail..."
           (gmail.ts:4924-4928) -- the stored state "cannot prove whether a previous
           implementation already attempted delivery" (gmail.ts:313-314)
      any legacy action through a restricted binding (search query or label): refused
           (gmail.ts:4930-4933)
      legacy archive / trash / markRead / markUnread through an unrestricted binding: still apply
           (gmail.ts:5327-5330)
    Reject still works for legacy entries. Conversely, 6478a14 cannot execute the new shapes
    ("unknown action type", google.ts@6478a14:1942).
  - Google Docs pending edits (GoogleDocGatekeeperImpl): same `pending:action:` keyspace; the
    action base drops documentId, makes baseRevisionId optional and adds tabId/writeId/
    markdownVersion. Every edit queued before the upgrade lacks markdownVersion and is refused,
    "queued under an earlier Markdown format ... Reject it and retry" (google.ts:1613-1619).
    That refusal writes nothing, but it does NOT prove the action never wrote: 6478a14 awaited
    Google's batchUpdate() (google.ts@6478a14:2273) before removing the pending action (:2275), so
    an accepted write whose response was lost stays pending. Legacy actions carry no write marker,
    so nothing detects this automatically -- the lost-response hazard upstream's new write markers
    exist to close (google.ts:1351-1356). Re-queuing an already-applied append duplicates its
    content.
    Gmail's legacy outbound actions have the same shape (send, then remove,
    google.ts@6478a14:1924-1945: sendRawMessage, then remove), hence their refusal; legacy archive/trash/mark read/unread set
    state rather than add content, so a retry cannot duplicate anything.
  - Google, the rest: additive and read-compatible -- the new per-resource DOs above;
    `grantedResources` recorded on new consents while existing accounts' `grantedScopes` still
    derive their resources (gatekeeper-google/src/resources.ts:512-520); Calendar/BigQuery/Drive
    observer state through the kit ObserverTracker (the legacy `true` reads as "observed");
    Sheets/Slides action journals in new namespaces. Calendar's pending actions have identical
    shapes and still apply. Sheets had no actions at 6478a14.
  - MCP Portal (mcp-shared/src/user.ts): reconnects stage credentials under new keys
    (reconnectTokens, reconnectOauthClient, reconnectOauthDiscovery) until commitReconnect;
    existing connections' keys unchanged.
  - Slack: legacy facets gain an additive `boundTeamId` (gatekeeper-slack/src/slack.ts:588-612).
    Not storage, but stored authorization state: the recorded `grantedScopes` of a Conversation
    or Thread grant made at 6478a14 no longer cover the resource -- Conversation now needs
    team:read; Thread needs team:read plus channels/groups/im/mpim:read (slack.ts:155-163;
    6478a14:142-148). Reads retract a resource whose scopes are not all held (slack.ts:190-196),
    and every bound read first checks workspace membership through team.info (slack-api.ts:505 ->
    :376-380 -> :537-540), which needs team:read. The deploy cannot widen a user's grant; the
    user re-consents through the reconnect / ensureResources flow (slack.ts:749). Workspace grants
    already hold every scope. MCP Portal actions: unchanged schema (mcp_actions).
  - Scheduler (schemaVersion still 1), Custom, Book, Error Reporter, Context KV snapshots
    (registry-do.ts unchanged): none.

additive, destructive, or irreversible:
  DO class migrations: additive. Overseer v1->v5: irreversible in practice -- records rewritten in
  place, drafts deleted (legacy Yjs bytes kept on disk "as rollback insurance", but nothing in
  6478a14 can reconcile them with post-upgrade state). Context: the owner grant and ACL stamp are
  additive, but byte bodies and { id } library rows are a format 6478a14 cannot read. GitHub:
  destructive for the legacy credential layout. Gmail/Docs pending actions (and GitHub unbound
  merges) queued before the upgrade: stranded -- refused by the new code, and the new shapes are
  unreadable by the old. User DO, the rest of Google, MCP Portal, Slack: additive.

old code reading new state:
  UNSAFE for four owners.
  - Workshop: 6478a14 sees version 5, skips its own 0->1 step, and treats the workspace as v1: it
    reads the stale Yjs log (every edit made after the upgrade is invisible), finds blueprint
    records without codeVersion, and cannot read git release-pack blueprints.
  - Context: 6478a14 hands back a Uint8Array where it expects a string body for every document
    written after the upgrade; it lists post-upgrade owned collections with no title, description
    or lastUpdated; and it ignores grants, so shares made after the upgrade disappear.
  - GitHub: 6478a14 reads accessToken, which is gone once an account has been used, so every
    GitHub connection used after the upgrade looks disconnected.
  - Google: 6478a14 cannot execute Gmail actions queued in the new shapes ("unknown action
    type"). The Google Worker is also platform-blocked from rolling back.
  MCP Portal, Slack and the User DO backfills: old code ignores the new keys.

new code reading old state:
  Designed for it. Overseer migrations run in the constructor (git step under
  blockConcurrencyWhile) before anything touches storage; version is stamped last / inside
  transactionSync, so a crash re-runs the step; legacy snapshot blueprints and archives still load.
  Context still reads legacy string bodies (literal text or base64, context-collection.ts:99), and
  its ACL migration runs before any access decision. GitHub's kit `upgrade` reads the legacy
  layout once. Google derives resources from the old grantedScopes. Exceptions, stranded by
  design rather than misread: legacy Gmail outbound / restricted-binding actions, legacy Docs
  edits and unbound GitHub merges are refused with an explanation, never applied differently from
  what was previewed. A refusal is not a statement about the past, though: an earlier attempt may
  already have written (see the lost-response notes above). Slack narrow grants read nothing until re-consented. Upstream tests
  git-migration.test.ts and git-migration-do.test.ts pass in the run below.

deployment order and mixed-version window:
  scripts/deploy.ts order: error-reporter, context, scheduler, github, google, slack, custom, book,
  mcp-portal, workshop, router. The versions are NOT compatible in that window, in either
  direction; it must run as a controlled interruption. See "Rollout plan" below for what breaks,
  the operator steps, and forward completion of a deploy that stops partway. Overseer migrations
  then run lazily, per workspace, on first wake after the Workshop upload; a large workspace's
  first request may be slow.

production-shaped rehearsal:
  NOT performed. There is no isolated evaluation deployment for this repo, and Wrangler is not
  authenticated in this environment. Evidence is limited to upstream's unit/DO tests and
  `pnpm check` dry-runs. Production-only uncertainty: size/age of real workspaces' Yjs logs and
  chats (migration time and memory inside blockConcurrencyWhile), and real Context collections.

backup/export feasibility:
  KV (BLUEPRINTS, AVATARS, CONTEXT_COLLECTIONS) and R2 (BLUEPRINT_CONTENT) can be exported
  read-only with wrangler before merge. DOs (Overseer, User, Context collections/libraries, GitHub
  accounts): no export tooling here. SQLite-backed DOs support point-in-time recovery (30 days) via
  ctx.storage.getBookmarkForTime / onNextSessionRestoreBookmark, but only from code shipped inside
  the DO -- a last-resort repair path, not rehearsed.

post-migration validation:
  Open several existing workspaces (oldest first): gadget code, history and chats present; edit,
  save and accept a change; publish and instantiate a blueprint; Workers Logs show
  storage.migration.completed / storage.migration.git.completed and no
  "failed to load agent catalog" for Custom/Book.
  Context: open existing collections (private and public) and check their document contents and
  titles read back unchanged, owners still own them, and access grants and visibility (private /
  everyone-can-view) match what they were; add a document and read it back; the Context
  management app opens.
  GitHub: an existing connection still reads a repository (its credentials have migrated).
  Pending actions: none left from before the upgrade (see the reconciliation step in "Rollout
  plan"); a new Gmail draft approval proves only the new path, not any old queued action.
  Slack: one Workspace-grant binding still reads; one Conversation- or Thread-only user
  reconnects, consents to the missing scopes, and their binding reads again.
  Schedules still listed and one fires; the Scheduled Tasks and Book management apps open;
  existing Google/Slack/MCP connections still work and a fresh connect completes; an action
  approval (e.g. a Gmail draft or GitHub comment) applies; the Error Reporter receives no new
  storage.migration or catalog-fallback issues.

forward-repair plan:
  Fix forward: bump to an upstream commit carrying the fix, or a reviewed wrapper patch; for a
  single broken workspace or collection, a reviewed repair via PITR bookmark as above. Never delete
  or recreate DO namespaces.

rollback restrictions:
  Platform-blocked (DO class lifecycle change): stelaxis-os-workshop, -github, -google.
  Redeploying the old gitlink would present a migrations list missing the applied tags -- do not
  attempt. Storage-unsafe even where the platform would allow it: Workshop, Context, GitHub.
  Interface-incompatible with the new Workshop: Router (old frontend), Context, Slack, MCP Portal.
  Degraded but functional: Scheduler, Custom, Book (per-turn catalog error). Clean: Error
  Reporter. Details in the matrix below.

Rollout plan

The deploy uploads the Gatekeepers, then the Workshop, then the Router, one at a time. Old and new versions are not compatible across that boundary, in either direction, so treat the deploy as a controlled interruption: a short announced maintenance window, not a zero-downtime rollout. No compatibility bridge was built, and the deploy order is unchanged.

What breaks while versions are mixed (each confirmed in source; not claimed exhaustive):

Window Breakage Evidence
New Gatekeepers, old Workshop (first Gatekeeper upload → Workshop upload: Context 09:15:27 → Workshop 09:16:14, ~47 s in run 33158419430) Action approvals fail on GitHub, Gmail, Google Docs and Google Sheets connections (and the new Slides). The old Workshop calls applyAction(record.action). The new validated Gatekeepers require a second RpcStub<GitCache>, and capnweb-validate refuses a missing required parameter. Calendar, Chat, Drive, BigQuery, Slack, MCP Portal, Context and Scheduler still take one argument. workshop-backend/src/overseer.ts@6478a14:2676; gatekeeper-github/src/github.ts:3997, gatekeeper-google/src/gmail.ts:4910, google.ts:1943, sheets.ts:253, slides.ts:193 (all @validateRpc() classes); capnweb-validate README "Missing required parameter / property: Refused"
same Management apps fail for Context, Scheduled Tasks and Book. The old Workshop passes AppUiContext { isAdmin }; the new type requires userSelectionExchange, which the validated account entrypoints check. workshop-backend/src/user.ts@6478a14:1364; workshop-shared/src/gatekeeper.ts:174-184; gatekeeper-context/src/library-gatekeeper.ts:150, gatekeeper-scheduler/src/scheduler.ts:340, packages/gatekeeper-book/src/book.ts:325
same OAuth connects and reconnects fail (GitHub, Google, Slack, MCP Portal): complete() now returns a ConnectHandoff, and reconnectComplete() doesn't exist on the old Workshop's callback. cloudflare-os#464
New Workshop, old Router (Workshop upload → Router upload, ~5 s), plus every browser tab opened before the deploy, until it is reloaded The editor can't load or save code. The Router serves the old frontend, which calls the removed subscribeToCode()/updateCode(). Unsaved edits in an open editor are lost. see the Frontend ↔ Workshop API row of Compatibility review
A deploy that stops partway (any failure after the first Gatekeeper upload and before the Router upload) Production stays in the mixed state above until the deploy is completed forward. Rolling back the uploaded Workers is not a way out: GitHub/Google are platform-blocked once uploaded, and the others either break against the old Workshop or have written new-format state. —

What stays broken after the deploy completes unless someone acts (not a window effect):

Affected Breakage Recovery
Actions queued before the upgrade Approving a legacy Gmail send/reply/forward, a legacy Gmail action through a search/label-restricted binding, any legacy Google Docs edit, or a GitHub merge queued without a bound head fails with an explanatory error, indefinitely. Drain the queue before the window; reconcile any leftovers after the deploy, before reopening, checking the target before anything is resubmitted (steps below).
Slack Conversation/Thread-only grants Those bindings read nothing: the user's token lacks team:read (and, for threads, the :read scopes). The user reconnects Slack from the connection, consenting to the missing scopes.
Browser tabs open across the deploy The old frontend can't load or save code. Reload.

Operator checklist:

Before the window

  1. Confirm the Slack app's User Token Scopes at api.slack.com → the app → OAuth & Permissions. All of team:read, channels:read, channels:history, groups:read, groups:history, im:read, im:history, mpim:read, mpim:history, search:read and users:read must be present, or users cannot consent to what narrow grants now need. An app set up per docs/customization.md#slack already has them, since the scope union is unchanged; add any that are missing.
  2. Announce a maintenance window of about 30 minutes: ~4 min for the deploy (the last run took 09:12:38 → 09:16:19 from push to Router upload), plus validation and the /admin changes below. Pick a time when the deployment is idle.
  3. Ask every user to drain their approval queue: approve or reject every pending action in every connection — at minimum Gmail, Google Docs and GitHub, the ones the new version refuses or could re-send. An action whose earlier approval failed or timed out may have been applied anyway, because 6478a14 writes to the provider before it records success. Before approving it again, check the target: Sent mail for Gmail; the document's content and revision history for Docs; the issue/PR (comments, reviews, merged state, or a duplicate already created) for GitHub. If the change is already there, reject the action. Approve only what is confirmed missing. Then save their work and close every Cloudflare OS tab.
  4. Tell Slack users who connected individual channels, DMs or threads (rather than a whole workspace) that they will have to reconnect Slack afterwards, and Google Sheets users that they will be asked to re-consent.

During the window

  1. No action approvals, management apps (Context, Scheduled Tasks, Book) or account connects.
  2. Merge and watch the Deploy run to completion: all 11 "Current Version ID" lines, Router last.
  3. If the run fails before or at the Router upload: do not roll back any Worker. Read the failed run's log to see which Workers were uploaded, and fix the cause if it is outside the repository (an expired CLOUDFLARE_API_TOKEN, a missing secret, a transient API error). Then re-run that same run: gh run rerun <run-id> --repo stelaxis/company-os, or "Re-run jobs" in the Actions UI. A re-run reuses the original commit, rebuilds all 11 Workers and re-uploads them, and Wrangler skips Durable Object migrations already applied. Check the deployed commit with gh run view <run-id> --json headSha against the merge commit. Don't use "Run workflow" (workflow_dispatch) for this: deploy.yml checks out without a pinned ref, so a dispatch deploys whatever main points to now. That's only right for a reviewed forward-fix commit merged to main because the failure was in the code. The deploy-production concurrency group queues a run behind one still in progress rather than overlapping it.

After the deploy, before reopening (with the owners of any leftover actions and one designated Slack Conversation/Thread-only user taking part)

  1. Apply the /admin Google decisions (Chat, Drive, Slides; Sheets is now read-write) from Before merging.
  2. Reconcile leftover pre-upgrade actions, each with its owner. One is identifiable by an approval that fails with "legacy outbound Gmail action", "legacy Gmail action cannot be applied through a restricted binding", "queued under an earlier Markdown format" or "queued without a bound head commit". The refusal itself writes nothing, but the action may have been applied by an earlier attempt whose response was lost. So check the target first, reject what is already applied without resubmitting, and re-create only what is confirmed missing:
    • Gmail send/reply/forward: check the owner's Sent mail. If it was delivered, reject it and don't resubmit. If not, reject it and have the agent queue it again.
    • Google Docs edits: check the document's current content and revision history (File → Version history). If the edit is there (an append present, or a replacement already made), reject it and don't resubmit: re-queuing an applied append duplicates its content. If it is missing, reject it and have the agent queue it again.
    • GitHub merges queued without a bound head: check the PR. If it is already merged, reject the action. Otherwise reject it and queue the merge again, which binds it to the head you see now.
    • Gmail archive/trash/mark read/unread through a search- or label-restricted binding: refused for every legacy action on such a binding (gmail.ts:4930-4933; the source states no further reason). These set state rather than add content, so check the thread's current state, reject, and re-queue or do it by hand only if it is still wanted.
    • A successful new approval doesn't clear old actions: each has to be resolved individually.
  3. Run the post-migration validation in Migration plan, now that no pre-upgrade action is left. That includes the designated Slack user reconnecting and their Conversation/Thread binding reading again.
  4. Only then close the window and tell everyone to reopen or reload. Other Slack Conversation/Thread-only users reconnect at this point, the same way.

Before merging

Required for the deploy to succeed: none. No new required secret (the three CLIENT_ID/CLIENT_SECRET pairs, for GitHub, Google and Slack, and the Book's GITHUB_TOKEN are unchanged). No new var, KV, R2 or queue, no Access or DNS change, and no AI Gateway token (providers stay cloudflare,anthropic in-account). The CI token needs no new scope: the Google Worker's new AI binding is the same binding type the Workshop already deploys.

Decisions and actions an operator should take before merging, because merge = deploy:

  1. Approve the storage changes the old version can't read (specialist review per the operator skill): the Workshop's Overseer migration, Context's body/library/ACL changes, and GitHub's credential migration. Consider exporting BLUEPRINTS/AVATARS/CONTEXT_COLLECTIONS KV and the BLUEPRINT_CONTENT R2 bucket first, read-only.
  2. Confirm the Slack app's User Token Scopes include team:read and the four :read conversation scopes (checklist step 1 in Rollout plan). This is the only pre-deploy change outside this repository that may be needed, and only if the app was set up with fewer scopes than our docs list.
  3. Schedule the maintenance window in Rollout plan and announce it with the user asks: drain every pending approval (Gmail, Docs and GitHub at minimum), first checking the target of any whose earlier approval failed (it may already be applied); save and close every tab; no approvals, management apps or connects during it; Slack channel/thread-only users reconnect afterwards. Line up the owners of any actions that may be left over, and one Slack channel/thread-only user, to take part inside the window.
  4. Google Cloud project, for the new resources to work (not deploy-blocking): enable the Google Slides, Google Chat and People APIs. On the Chat API's Configuration tab, set an app name, avatar URL and description, turn off Interactive features and save. Without that, Chat sends/edits/reactions fail. Drive was already enabled. The Internal consent screen needs no scope verification.
  5. Decide the new Google authority, to apply in /admin right after the deploy, before the window closes (the new resource patterns exist only once the new Worker is live): keep or disable Google Chat (send/edit/react with approval, and starting DMs), Drive (drive.readonly), Slides, and note that Sheets is now read-write. Also tell users that existing Google Sheets connections will ask for re-consent.
  6. Confirm the version IDs below are still current (wrangler deployments list --name <worker>): any wrangler secret put since 2026-08-28 created a newer version that this PR could not see.

Rollback matrix

Previous wrapper commit ce7c13e with gitlink 6478a14. Version IDs are the "Current Version ID" each Worker reported in the last production deploy, run 33158419430 of ce7c13e on 2026-08-28. They could not be re-read live: Wrangler's OAuth token here has expired and no CLOUDFLARE_API_TOKEN is set.

Summary. Only the Error Reporter rolls back cleanly. Scheduler, Custom and Book can be rolled back but stay degraded against the new Workshop. Router, Context, Slack and MCP Portal are allowed by the platform but unsafe. Workshop, GitHub and Google are refused by Cloudflare (Durable Object class lifecycle change). Recovery for the unsafe and refused Workers is forward-repair.

Worker Previous version Depends on / depended on by Binding/config change Secrets Migration/storage Data written by new version Rollback or forward-repair Order
stelaxis-os-error-reporter 80017a6c-2c9c-4c03-869d-4da4136d6463 ← Workshop none none none logs only clean: wrangler rollback; contract unchanged any
stelaxis-os-context 4acda9c5-5d11-494a-a122-2d7c63d8d505 ← Workshop, Router compat date none ACL aclVersion 0→1 + owner grants; byte bodies; { id } library rows collections, documents, grants unsafe, two independent reasons. Storage: 6478a14 can't read byte-encoded bodies or { id } library rows, and ignores grants. RPC: the old validated getAgentCatalog(authorizer) declares a required parameter the new Workshop no longer passes, so it is refused every turn. → forward-repair —
stelaxis-os-scheduler 979863e6-f3b6-4e1e-a47d-c9a148c5dbe2 ← Workshop, Router compat date none none (schemaVersion 1) schedules (same schema) degraded: storage is fine and schedules keep running, but the old @validateRpc() SchedulerGatekeeper.getAgentCatalog(_authorizer) declares a required parameter, so the new Workshop's no-argument call is refused, with a handled overseer.catalog-fallback report every chat turn. Short-term measure only. any
stelaxis-os-github c69c7391-c26c-43e3-a2cb-911fbb7860e7 ← Workshop, Router compat date unchanged +v1 GitHubHookDriver; credential layout migrated, legacy keys deleted migrated credentials, staged connects refused by Cloudflare (DO lifecycle change), and storage-unsafe as well (old code finds no accessToken) → forward-repair only —
stelaxis-os-google 8f0fb8b1-9862-4d19-91c1-7fa991799f33 ← Workshop, Router compat date, +AI binding unchanged +v4…v8; Gmail/Docs pending actions in new shapes; otherwise additive Drive/Docs/Chat/Slides accounts, Sheets re-grants, new-shape Gmail/Docs actions refused by Cloudflare, and storage-incompatible as well (6478a14 can't execute the new Gmail action shapes) → forward-repair only; reduce exposure via /admin disabledResources —
stelaxis-os-slack f819a180-575a-4099-9150-e6d8bbe00878 ← Workshop, Router compat date unchanged additive (boundTeamId); narrow grants need wider scopes staged connects, re-consented grants unsafe: an old Slack Gatekeeper never posts the connect ticket to the new Workshop, so new connects silently fail → forward-repair —
stelaxis-os-gatekeeper (custom) a43e6369-3145-46e0-a4cb-92fd24ca445d ← Workshop, Router none none none none degraded: the old version lacks getAgentCatalog → a handled issue report every chat turn any
stelaxis-os-book c10ce875-1bcc-4917-baad-5a770f8544a2 ← Workshop, Router; cron none unchanged none mirror (same schema) degraded, as custom (its management app still opens: extra context properties are allowed) any
stelaxis-os-mcp-portal 5c3482ab-ea03-40d9-8666-04219a32354c ← Workshop, Router compat date none additive (staged reconnect keys) staged connects unsafe, as Slack: connects break against the new Workshop → forward-repair —
stelaxis-os-workshop 4188d9e5-fcbe-44c8-9a98-bcb634b742fc → all Gatekeepers, Error Reporter; ← Router compat date, flag, +v3 unchanged +v3; Overseer v1→v5 irreversible; User DO backfills migrated workspaces, git blueprints, bundled blueprints in KV refused (v3) and data-unsafe → forward-repair only —
stelaxis-os-router 614b9b0b-1f45-4759-9c0d-de706449043c → Workshop + 9 Gatekeepers compat date none none none unsafe: the old Router serves the old frontend bundle, which calls the removed subscribeToCode()/updateCode(), so the editor can't load or save against the new Workshop → forward-repair —

A Worker version is its code, static assets, bindings and compatibility settings, so a rollback restores that version's binding configuration. It does not restore KV, R2, D1 or Durable Object contents ("not tracked with versions"), or anything outside the Worker such as Access policies and DNS. wrangler secret put creates a new version, so check secret names after any rollback rather than assuming either way. After any recovery, repeat the full Access, /admin, storage, Gatekeeper, AI, Reporter and logs verification (operator skill §11).

Verification

Run in company-os-worktrees/bump-cloudflare-os after deleting every node_modules in the worktree, in CI's order. Node 26.9.0, pnpm 11.17.0.

# Command Result
1 pnpm install --frozen-lockfile ✅ pass
2 pnpm --dir cloudflare-os install --frozen-lockfile ✅ pass; submodule worktree clean
3 pnpm lint ✅ pass. vp lint: 0 errors, 2 warnings (pre-existing, configured warn); tsc -p scripts/tsconfig.json: pass; vp run -r --cache build: 5/5 tasks (tsc in workshop-shared, error-reporting, error-reporter, custom-gatekeeper, gatekeeper-book)
4 pnpm test ✅ pass. node --test 'scripts/**/*.test.ts': 37 test cases, 37 passed (file set: scripts/deploy.test.ts). vp run … test (vitest test cases): @gadgets/error-reporting 23 (1 file), custom-gatekeeper 2 (1 file), error-reporter 2 (1 file), gatekeeper-book 28 (2 files)
5 pnpm check ✅ pass. The same tests again; 19 vp run --no-cache builds; wrangler deploy --dry-run for all 11 Workers (11 × "--dry-run: exiting now", nothing uploaded); generated wrangler.prod.jsonc files removed (0 left); pnpm-lock.yaml unchanged (sha256)
6 pnpm --dir cloudflare-os exec vp run -F @gadgets/workshop-shared -F @gadgets/error-reporting --no-cache build ✅ pass (upstream contract packages' tsc)
7 pnpm --dir cloudflare-os exec vp run -F @gadgets/error-reporting -F @gadgets/router -F @gadgets/workshop-backend --no-cache test ✅ pass. error-reporting 23; router 17; workshop-backend unit 1965 test cases in 81 files (including git-migration*.test.ts); workshop-backend integration 3 passed, 4 skipped (hard-coded describe.skip upstream, open-gadget-rpc.test.ts:87)
— Catalog diff (this repo vs submodule) empty
— Baseline: vp lint on a clean export of ce7c13e + 6478a14, its own frozen lockfile ❌ the same 2 errors fixed here, so they are pre-existing
8 pnpm lint, re-run after each docs-only commit (860e04c, bb78db7) ✅ pass both times: 0 errors, the same 2 warnings; tsc -p scripts/tsconfig.json pass; 5/5 builds

Not run: any mutating Cloudflare command, any deploy, and live wrangler deployments list / versions list (Wrangler is not authenticated: "Your auth token has expired and could not be refreshed, and the environment is non-interactive").

Top risks for the production upgrade

  1. Irreversible storage changes with no production-shaped rehearsal: every workspace's Overseer storage (code log → git) on first wake, Context collections (byte bodies, { id } library rows, ACL grants), and GitHub credentials. Rollback is blocked by Cloudflare (DO lifecycle) and by data. Mitigation: specialist review, KV/R2 export, post-migration validation on the oldest workspaces and on existing Context collections first.
  2. The deploy is a controlled interruption, not a rolling upgrade. While versions are mixed, action approvals, management apps, OAuth connects and the code editor break. A deploy that stops partway leaves production broken until it is completed forward (gh run rerun on the failed run). Stale browser tabs stay broken until reloaded.
  3. Some breakage persists after the deploy: pre-upgrade Gmail outbound / restricted-binding actions, Docs edits and unbound GitHub merges can never be approved, and Slack Conversation/Thread-only grants read nothing until their users reconnect. Because 6478a14 wrote to the provider before recording success, any action whose earlier approval failed may already be applied. Draining and reconciling must check the target first (Sent mail, document revision history, the issue/PR); re-queuing an applied Docs append duplicates content, and re-approving a legacy GitHub comment, review or create sends it again.
  4. Google authority expansion by default: Chat (send/edit/react), Drive (drive.readonly), Slides, Sheets read-write, all offered to every user until disabled in /admin; plus Sheets re-consent for existing users.
  5. Rollback options are narrow: only the Error Reporter is clean; Scheduler, Custom and Book are degraded; Router, Context, Slack and MCP Portal are unsafe; Workshop, GitHub and Google are refused.
  6. Compat date jump (2026-02-02 → 2026-09-04) for all upstream Workers, including implicit nodejs_compat and delete_all_deletes_alarm. That's upstream's tested baseline, but these defaults are new to this deployment.
  7. Rollback inventory reflects the 2026-08-28 deploy, not a live query.

Moves the pinned submodule 6478a14 -> a679442 (upstream main, 290
commits ahead, 0 behind; upstream has no tags or releases), with only the
wrapper changes the bump requires:

- pnpm-workspace.yaml: the catalog is re-synced verbatim with the
  submodule's (capnweb ^0.12.0, capnweb-validate 0.3.0, vitest ^5.0.5,
  vite-plus ^1.2.0, wrangler ^4.147.0, @cloudflare/vitest-plugin, ...),
  and the lockfile resolves the shared toolchain to the versions
  cloudflare-os/pnpm-lock.yaml does: one capnweb, one wrangler/miniflare/
  workerd stack. cloudflare-os/scripts (@gadgets/scripts) becomes a
  member, because @gadgets/error-reporting now declares it workspace:*.
  Overrides follow upstream's vite-plus 1.x form (vite@*) and pin the
  vitest plugin's miniflare and wrangler to the catalog.
- @cloudflare/vitest-pool-workers is renamed @cloudflare/vitest-plugin
  upstream (Vitest 5); manifests and both vitest configs follow.
- vite-plus 1.x reads task input/output under `cache`; the three wrapper
  packages' task configs move there. Their vitest runs pass
  --no-fsModuleCache, as upstream's shared task does: Vitest 5's module
  cache is shared under the root node_modules and parallel tasks race on it.
- The root test script excludes @gadgets/scripts, whose test task is
  upstream's workspace-wide guards over its own packages.
- Gatekeeper contract: GatekeeperUser gains a required commitReconnect()
  (connect handoff, cloudflare-os#464), and the Workshop now asks every
  ambient Gatekeeper for getAgentCatalog() each turn. The Custom and Book
  Gatekeepers implement both; neither has a connect flow or a catalog.
- error-reporter takes @cloudflare/workers-types from the catalog.
- gatekeeper-book: two lint findings (no-useless-spread, no-array-sort)
  that also fail `pnpm lint` on main with the old toolchain.
- Docs and comments: the Google Gatekeeper now carries eight resource
  types (Drive, Slides and Chat added; Sheets read-write), and the
  upgrade checklist names three workspace members.

scripts/deploy.ts needs no code change: the derived configs pick up the
new base configs as they are (compatibility_date 2026-09-04, additive
Durable Object migrations on the Workshop, GitHub and Google Workers, and
Google's new AI binding), and buildCommands() still rebuilds every
package from source.
Restoring the old gitlink or rolling a Worker back is only safe while the
old code can still read what the new version wrote, and Cloudflare
refuses a rollback across a Durable Object migration. This bump does
both, so the upgrade checklist says to repair forward otherwise.
At the pinned Cloudflare OS commit, every Slack read first confirms the
user still belongs to the bound workspace through team.info, so
Conversation grants need team:read and Thread grants need team:read
plus the conversation :read scopes. The table still listed the old,
narrower sets, and a grant made with them reads nothing until the user
reconnects.
@prepor
prepor merged commit 190ddd9 into main Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant