Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dc009b3
Fix Outputs sidebar not stretching to full height (#454)
jonesphillip Sep 7, 2026
a20168d
Support multi-tab Google Docs (#450)
ndisidore Sep 8, 2026
81f6a0e
add xlsx export suppport to workspace-sheets blueprint (#433)
maxwellpeterson Sep 8, 2026
54d5d8b
feat(gatekeeper-kit): replayable runs, declared action fences, and a …
ndisidore Sep 8, 2026
44f7950
Bind gatekeeper connect completion to the initiating browser (#464)
Maximo-Guk Sep 9, 2026
94459f2
integration-tests: check a deleted workspace's listing over a fresh s…
Maximo-Guk Sep 10, 2026
9c1d9c5
Redeem connect handoff tickets from the popup itself; drop the Broadc…
Maximo-Guk Sep 10, 2026
d0dfc09
Rename prohibitAllSharing -> containsRestrictedData (#381)
Maximo-Guk Sep 11, 2026
45ae8c2
Restricted data: govern restricted reads by observer verification (#382)
Maximo-Guk Sep 11, 2026
08afe05
Restricted data UI - share modal stays usable for restricted workspac…
Maximo-Guk Sep 11, 2026
5af356e
Bump vitest from 4.1.10 to 4.1.11 (#470)
dependabot[bot] Sep 14, 2026
98c3482
feat(fork): codify the upstream merge strategy
bashandbone Sep 14, 2026
ff04731
fix(fork): default PUBLIC_BASE_URL in dev-server
bashandbone Sep 14, 2026
340e3fb
Merge foundation/main into sync/foundation-2026-09-14 (11 commits)
bashandbone Sep 14, 2026
b23db94
fix(fork): handle fork deployment config in manifest, staging, and ha…
bashandbone Sep 14, 2026
a881583
fix(fork): drop dev-only browser.remote; keep backend assets out of n…
bashandbone Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/write-gatekeeper/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async getVerifier(): Promise<Fetcher<GatekeeperUserVerifier>> {

Strategy is chosen **per `Gatekeeper` DO class / binding**, not per package — one package may use several (e.g. Google: Gmail=A, Doc=B, BigQuery=C).

- **A — Private-only.** `addObserver()` always throws; `removeObserver()` is a no-op. `getVerifier()` must still exist (the overseer mints it) but is never consulted. Use when the resource is too sensitive to share and there is no per-observer access oracle (e.g. a personal Gmail mailbox).
- **A — Private-only.** `addObserver()` always throws; `removeObserver()` is a no-op. `getVerifier()` must still exist (the overseer mints it) but is never consulted. Use when the resource is too sensitive to share and there is no per-observer access oracle (e.g. a personal Gmail mailbox). For truly sensitive data, also mark each observation with `ObservationDescription.containsRestrictedData: true`: because nobody can open the workspace without passing `addObserver()`, and with strategy A nobody ever does, the first such observation makes the workspace effectively unshareable — and it latches into a restricted mode that blocks all actions and public web fetches, so the data cannot leak back out through other gatekeepers.
- **B — ACL check (single unit).** The binding is one atomic resource; sub-resources inherit its ACL. `addObserver()` calls a verifier method to confirm the observer can access it and throws otherwise; `removeObserver()` is a no-op; nothing is tracked and no `excludeObservers` is ever needed. Use for repo / document / page / team / single-project bindings.
- **C — Data-set tracking.** The binding spans sub-resources with **distinct ACLs**, and there is a **per-observer access oracle** for each. The DO logs the data sets actually observed and the current observers; `addObserver()` verifies the observer against **every** logged set (plus a coarse membership baseline) and **stores their verifier**; each later observation that first touches a **new** set re-checks all stored observers and sets `excludeObservers` for any who fail. Use for workspace / organization / dataset-spanning bindings.
- **D — Low-stakes.** `addObserver()` / `removeObserver()` are no-ops; `getVerifier()` returns a trivial verifier with a no-op public method such as `verify(): void {}` (an empty `WorkerEntrypoint` is not registered in `ctx.exports`). Use when any collaborator may observe (personal, low-stakes services).
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The project structure is:
* Gatekeeper configurator UI modules are compiled by `scripts/build-gatekeeper-configurator.ts` as part of package builds.
* packages/gatekeeper-*: Gatekeeper workers for external service integrations.
* Each gatekeeper runs as a separate Cloudflare Worker — with one exception the prefix does not capture: a `gatekeeper-*` package with **no `wrangler.jsonc` is a library, not a worker** (`gatekeeper-kit` here; `gatekeeper-shared` in the internal repo). Deployable discovery is config-gated, not name-gated — `readDeployablePackages` in `scripts/release/manifest-lib.ts` keys solely on the presence of `wrangler.jsonc`, and `run-dev-server.ts` requires it too — so adding one to a library package is what would make it deployable, at which point `workerKind` would classify it a gatekeeper by prefix and the deploy wizard would demand `CLIENT_ID`/`CLIENT_SECRET` for it. `manifest-lib.test.ts` fails first if that ever happens.
* Gatekeepers handle OAuth flows and provide sandboxed access to external APIs.
* Gatekeepers handle OAuth flows and provide sandboxed access to external APIs. A connect URL is a bearer capability, so every connect/reconnect flow ends on the kit's `connectHandoffPageHtml`, which sends the popup to the Workshop's `/connect/handoff` page, and that page redeems the single-use ticket over the popup's own session together with a per-flow nonce (see `docs/connect-handoff.md`); a reconnect stages its new credentials via `gatekeeper-kit/credential-stage` until the Workshop calls `GatekeeperUser.commitReconnect(stageId)` with the id that completion reported; completion is confirmed through the ticket, never through the URL alone.
* A gatekeeper may declare `VendorDescription.autoProvisionsAccount`: it can mint a connected account with no OAuth flow (via `GatekeeperVendor.createAccount()`, which takes no user identity). For such gatekeepers the deployment admin picks a per-vendor mode in the admin Gatekeepers panel — **disabled** / **optional** / **enabled** (default **optional**) — resolved in `provisioning-policy.ts`: `enabled` auto-provisions the account for every user (forced, and hidden from the Connectors list), `optional` lets each user opt in from the Connectors page, and `disabled` offers it to no one (existing accounts go dormant). The Workshop persists the account in the user DO like any connected account (the account capability — not an asserted identity — is the authority thereafter). The **account** (a `GatekeeperUser`) declares in its `AccountDescription` whether it provides an agent **singleton** (`singleton: { tsType }`) and/or a **management UI** (`providesUi`). The Workshop auto-provides the singleton to the owner's workspaces as an **ambient gatekeeper record**, folded into each chat's env as a **named chat binding** (named by the gatekeeper's `suggestedBindingName`; see `prepareChatBindings` in overseer.ts) that the agent reads in `executeCode` (`getSession`/`getAgentCatalog`), each read recorded as an observation. It is not bound to any gadget by default — most gadgets never call it programmatically — but the agent may wire it into a gadget's binding list with `setGadgetBinding` when the gadget's persistent code needs it. The UI is hosted at `/gatekeepers/$appId` (the gatekeeper's vendor id, e.g. `/gatekeepers/context`) via `startAppUi({ isAdmin })`. The two are orthogonal — an account can declare either, both, or neither.
* packages/mcp-shared: Shared implementation behind the two MCP gatekeepers — `gatekeeper-mcp` (endpoints a user pastes) and `gatekeeper-mcp-portal` (one admin-configured portal). Not a Worker; a library both import, holding the MCP client, the OAuth chain, the account DO base, the resource-URL scope grammar, and the queued-action store. See `packages/mcp-shared/README.md` and each connector's README.
* The trust boundary is `tools.ts`, and nothing outside it reads a tool's annotations: a tool the server declares `readOnlyHint: true` runs as an observation, everything else is queued for approval, and auto-*applying* a write additionally requires a `vetted` endpoint — which only the portal can produce, via `MCP_PORTAL_TRUST_ANNOTATIONS`.
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ afterwards.
`foundation` is the upstream remote (`cloudflare/cloudflare-os`); `origin` is ours. To sync:

```bash
git fetch foundation
git merge foundation/main
pnpm fork:audit # catches the failures git does NOT flag
pnpm fork:sync --dry-run # impact report, no branch, no merge
pnpm fork:sync # fetch, branch, merge; resolve Tier-2 files by hand
pnpm fork:sync --verify # exit 0 means commit
mise x node@24 -- pnpm lint && mise x node@24 -- pnpm test
```

`pnpm fork:audit` exists because the expensive problems in a sync are the silent ones. It reports
upstream changes that vanished without ever raising a conflict (a file resolved as "take ours" drops
every upstream hunk in it), and upstream-owned files whose entire diff is reformatting — churn that
buys nothing and conflicts forever.
`pnpm fork:sync` exists because the expensive problems in a sync are the silent ones: upstream
changes that vanish without ever raising a conflict (a file resolved as "take ours" drops every
upstream hunk in it), upstream-removed names the fork still uses (a rename surfaces here, with the
removing commit, instead of as confusing test failures), and upstream-owned files whose entire diff
is reformatting — churn that buys nothing and conflicts forever. `pnpm fork:audit` runs the
post-hoc checks standalone (and in CI on every PR).

Two environment notes that will otherwise cost you an afternoon:

Expand Down
Loading
Loading