@zackbart/connecta 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +3 -1
  3. package/bin/connecta.mjs +23 -6
  4. package/dist/auth/bearer.d.ts +2 -2
  5. package/dist/auth/bearer.js +2 -2
  6. package/dist/auth/clerk.js +1 -0
  7. package/dist/auth/cloudflare-access.d.ts +8 -0
  8. package/dist/auth/cloudflare-access.js +66 -0
  9. package/dist/execute.js +12 -6
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +2 -2
  12. package/dist/meta-tools.js +3 -3
  13. package/dist/operator-ui/generated.js +1 -1
  14. package/dist/operator-ui/model.d.ts +3 -3
  15. package/dist/operator-ui/view.d.ts +1 -1
  16. package/dist/operator-ui/view.js +6 -3
  17. package/dist/providers/cloudflare.js +233 -61
  18. package/dist/providers/linear.js +7 -0
  19. package/dist/providers/mixpanel.js +6 -4
  20. package/dist/providers/revenuecat.js +2 -1
  21. package/dist/providers/stripe.js +7 -6
  22. package/dist/routes/access-tokens.d.ts +1 -1
  23. package/dist/routes/access-tokens.js +2 -2
  24. package/dist/routes/activity.js +2 -2
  25. package/dist/routes/credentials.js +1 -1
  26. package/dist/routes/mcp.js +1 -1
  27. package/dist/routes/oauth.js +1 -1
  28. package/dist/routes/shared.d.ts +4 -4
  29. package/dist/routes/shared.js +10 -10
  30. package/dist/routes/ui.js +12 -9
  31. package/dist/skills.d.ts +1 -1
  32. package/dist/skills.js +7 -4
  33. package/dist/types.d.ts +37 -22
  34. package/dist/ui.d.ts +1 -1
  35. package/dist/ui.js +3 -3
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/documentation/architecture.md +7 -4
  39. package/documentation/auth.md +71 -7
  40. package/documentation/cloudflare.md +19 -1
  41. package/documentation/code-mode.md +4 -4
  42. package/documentation/linear.md +6 -4
  43. package/documentation/meta-tools.md +22 -20
  44. package/documentation/mixpanel.md +5 -3
  45. package/documentation/operations.md +28 -13
  46. package/documentation/operator-ui.md +21 -5
  47. package/documentation/provider-conventions.md +4 -2
  48. package/documentation/revenuecat.md +21 -21
  49. package/documentation/stripe.md +14 -13
  50. package/documentation/upgrading.md +77 -4
  51. package/ethos.md +2 -3
  52. package/examples/worker/README.md +52 -32
  53. package/examples/worker/src/index.ts +32 -38
  54. package/examples/worker/wrangler.jsonc +12 -4
  55. package/package.json +5 -1
  56. package/templates/node/package.json +1 -1
@@ -153,6 +153,18 @@ every constrained field, endpoint-specific pagination bounds, and a description
153
153
  on every property. `test/cloudflare-provider.test.ts` walks the surface and
154
154
  asserts those properties rather than leaving them as a claim.
155
155
 
156
+ The per-setting zone tools accept Cloudflare's current non-empty setting ids,
157
+ including `webmcp_enabled` and `webmcp_packs`. Cloudflare's
158
+ [rendered setting reference](https://developers.cloudflare.com/api/resources/zones/subresources/settings/)
159
+ and current OpenAPI now agree on both beta ids. Results preserve the value and
160
+ the actual `editable` flag Cloudflare returned; Connecta does not apply the
161
+ document's defaults locally.
162
+
163
+ KV namespace jurisdiction is creation-only. `create_kv_namespace` accepts
164
+ `eu`, `fedramp`, or `us`, and namespace reads preserve the returned value.
165
+ R2 uses a different enum: `default`, `eu`, `us`, or `fedramp`, sent through
166
+ `cf-r2-jurisdiction` by all eight named R2 operations.
167
+
156
168
  ### What the named surface deliberately leaves out
157
169
 
158
170
  A named tool is a permanent line item in every deployment's catalog, so the
@@ -206,7 +218,7 @@ connection *calls*, dropping the tool drops the row — so
206
218
  rather than by a recorded exception. A path reached only through a hatch is
207
219
  named by the caller, so it was never a touched endpoint.
208
220
 
209
- The surviving 47 named tools all refuse malformed arguments locally, which is
221
+ The surviving 48 named tools all refuse malformed arguments locally, which is
210
222
  the one thing no escape hatch can do: a hatch's path is an opaque string, so it
211
223
  can only check that a path is a path.
212
224
 
@@ -318,6 +330,12 @@ renamed to `camelCase`. A zone comes back as `id`, `name`, `status`, `paused`,
318
330
  `type`, `accountId`, `accountName`, `plan`, `nameServers`, and timestamps —
319
331
  not the forty-field object Cloudflare sends.
320
332
 
333
+ Every named tool declares useful top-level output keys. Zone settings, Worker
334
+ settings and deployments, KV namespaces and bulk operations, rulesets, R2 CORS,
335
+ and Pages resources use maintained result schemas instead of an open
336
+ "Cloudflare object" declaration. The three raw escape hatches remain open by
337
+ design because their result fields depend on the caller-supplied endpoint.
338
+
321
339
  Paginated lists add a `page` object derived from `result_info`:
322
340
  `{ page, perPage, count, totalCount, totalPages, hasMore }`. `hasMore` is the
323
341
  field to branch on.
@@ -889,11 +889,11 @@ the upstream `Executor` shape assignable.
889
889
  | `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
890
890
  | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
891
891
 
892
- The surface itself is checked by `test/server.test.ts` (the exact seven-tool
893
- list) and `test/code-first-surface.test.ts` (the fold's construction rules, the
892
+ The surface itself is checked by `test/server.test.ts` (the exact seven-tool list)
893
+ and `test/code-first-surface.test.ts` (the fold's construction rules, the
894
894
  required executor, the refusals a removed top-level tool now gets, copy, and
895
- measured size). There is one shape left to audit, so there is one audit:
895
+ measured size). The small whole-agent benchmark checks both read routes, provider semantics, and private pagination:
896
896
 
897
897
  ```sh
898
- npm --prefix eval/current-version run audit
898
+ npm --prefix eval/current-version run benchmark
899
899
  ```
@@ -110,9 +110,10 @@ the connector is present and reports `auth_required`. See
110
110
 
111
111
  The wrapper classifies Linear's documented `list_*`, `get_*`, and
112
112
  `search_documentation` tools as reads, and its `save_*`, `create_*`, `delete_*`,
113
- `resolve_*`, `submit_*`, and `merge_*` tools as writes. An unfamiliar tool the
114
- downstream leaves unannotated fails closed onto `call_destructive_tool` until a
115
- Connecta release reviews it.
113
+ `resolve_*`, `submit_*`, `merge_*`, `share_issue`, and `unshare_issue` tools as
114
+ writes. Sharing changes an existing issue's access, so both halves are
115
+ destructive. An unfamiliar tool the downstream leaves unannotated fails closed
116
+ onto `call_destructive_tool` until a Connecta release reviews it.
116
117
 
117
118
  That classification fills in downstream silence and otherwise preserves
118
119
  explicit annotations. A tool on the read allowlist arriving with
@@ -136,7 +137,8 @@ though some calls only create. The genuine creates are `create_issue_label` and
136
137
  ## The catalog is not a fixed set
137
138
 
138
139
  Linear's hosted `tools/list` varies by workspace plan and enabled features:
139
- customer requests, releases, and code review do not appear in every workspace.
140
+ customer requests, releases, code review, templates, and explicit issue sharing
141
+ do not appear in every workspace.
140
142
  The maintained allowlists are therefore a superset — a classified name a
141
143
  workspace never returns costs nothing, and a genuinely new tool fails closed.
142
144
  Agents should search this connector's catalog for what the workspace actually
@@ -12,19 +12,19 @@ Every deployment requires an executor and `tools/list` is exactly seven:
12
12
  batching live in `connecta.search`, `connecta.describe`, and `connecta.batch`
13
13
  inside a program ([#273](https://github.com/zackbart/connecta/issues/273)).
14
14
 
15
- Code-first is what a model sees. Four overlapping ways to reach one connector
16
- became two: `search_tools` then `call_tool` for a single cold read measurably
17
- cheaper direct than through a program and `execute_code` for everything wider.
18
- The consolidation removed overlapping routing choices while preserving the
19
- cheaper direct path for one cold call. The [guest API contract](./code-mode.md)
20
- is what a program is promised.
21
-
22
- The route is chosen before discovery. A result that will be reduced, a call
23
- whose arguments depend on an earlier result, or work with multiple operations
24
- starts with one `execute_code` call and keeps discovery, calls, and reduction
25
- inside it. Distinct operations get distinct short `connecta.search` queries in
26
- that program. Only one unknown-address read takes the cheaper top-level
27
- `search_tools` → `call_tool` path; a known address needs only `call_tool`.
15
+ Code-first is what a model sees. Read-only work has two routes: `call_tool` for
16
+ one known address, and `execute_code` when discovery or any wider work is
17
+ needed. Real hosted catalogs reversed the earlier synthetic result that made a
18
+ top-level cold search look cheaper. Keeping discovery inside the program avoids
19
+ returning every candidate schema to the model and removes a model round trip.
20
+ The [guest API contract](./code-mode.md) is what a program is promised.
21
+
22
+ The route is chosen before discovery. An unknown address, a result that will be
23
+ reduced, a call whose arguments depend on an earlier result, or work with
24
+ multiple operations starts with one `execute_code` call and keeps discovery,
25
+ calls, and reduction inside it. Distinct operations get distinct short
26
+ `connecta.search` queries in that program. A known address needs only
27
+ `call_tool`.
28
28
 
29
29
  That routing is about read-only work, because that is the only work a program
30
30
  can do. Anything unannotated, write-capable, or destructive is inadmissible
@@ -68,13 +68,15 @@ and a truncated line ends with the exact `+N more` count. This reads only the
68
68
  configured registry: it loads no catalog, probes no credential, grants no
69
69
  capability, and does not replace canonical discovery or addressing.
70
70
 
71
- Start an unknown-address lookup with two to four distinctive action/object
72
- terms, not the full request, and omit `limit` so the default eight-result page
73
- stays small. When the integration is obvious, set `connector` to its id: a
74
- scoped search loads that catalog alone, while an unscoped search must fan out
75
- across every configured connector. Leave the search unscoped when the right
76
- integration is genuinely ambiguous. Set `safety: "readOnly"` when the result is
77
- headed to `call_tool` or generated code; `safety: "approvalRequired"` finds the
71
+ Start a lookup with two to four distinctive action/object terms, not the full
72
+ request. Read-only lookup belongs in `connecta.search` inside the program.
73
+ Top-level `search_tools` remains available for explicit catalog inspection and
74
+ approval-required discovery. Omit `limit` initially so the default
75
+ eight-result page stays small. When the integration is obvious, set
76
+ `connector` to its id: a scoped search loads that catalog alone, while an
77
+ unscoped search must fan out across every configured connector. Leave the
78
+ search unscoped when the right integration is genuinely ambiguous. Set
79
+ `safety: "readOnly"` for generated code; `safety: "approvalRequired"` finds the
78
80
  complementary set that must cross `call_destructive_tool`. Omitting `safety`,
79
81
  or setting it to `"all"`, preserves the complete configured catalog. This is
80
82
  only a discovery filter: it neither grants authority nor changes invocation admission.
@@ -84,7 +84,7 @@ the maintained guide carries the missing call guidance:
84
84
  A read-only live audit on 2026-08-13 confirmed all three refusals against the
85
85
  US hosted endpoint. They are reported upstream as
86
86
  [`mixpanel/mixpanel-headless#202`](https://github.com/mixpanel/mixpanel-headless/issues/202).
87
- The vetted catalog records the same audit's schema digests for all 63 tools,
87
+ The vetted catalog records current schema digests for all 64 tools,
88
88
  so a later schema correction or regression appears by tool name in the
89
89
  maintainer drift check. The guide can then shrink when the downstream schema
90
90
  becomes complete; Connecta does not absorb the defect permanently.
@@ -110,8 +110,10 @@ rest) leave `destructiveHint` unset; `readOnlyHint: false` already routes them
110
110
  through the destructive path, and asserting destruction only inflates the
111
111
  approval copy the host shows a human.
112
112
 
113
- Experiments and Feature Flags 15 of the 63 classified tools — are Mixpanel
114
- beta surfaces. Expect their names and schemas to move faster than the rest.
113
+ Experiments and Feature Flags are Mixpanel beta surfaces. Their three changed
114
+ schemas were reviewed again on 2026-08-30. The same review added
115
+ `Fill-Event-Metadata` as a destructive write because it applies generated names
116
+ and descriptions to existing Lexicon events.
115
117
 
116
118
  ## Rate limits
117
119
 
@@ -45,6 +45,10 @@ one.
45
45
  npx @zackbart/connecta init my-deployment
46
46
  cd my-deployment && npm install && npm start
47
47
  CONNECTA_TOKEN=… npx connecta doctor --url http://localhost:8787
48
+
49
+ # A Worker protected by Cloudflare Access
50
+ CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
51
+ npx connecta doctor --url https://connecta.example.workers.dev
48
52
  ```
49
53
 
50
54
  `init` copies the template, pins the generated deployment to the CLI package's
@@ -59,12 +63,17 @@ on the way out: `QuickJS` on the Node template, `DynamicWorkerExecutor` on the
59
63
  Worker example, and `code executed` when an executor identifies as nothing —
60
64
  a checker that asserts a sandbox it never saw is worse than one that says it
61
65
  does not know ([#368](https://github.com/zackbart/connecta/issues/368)). It
62
- refuses to send a bearer token over remote plaintext HTTP, and it
66
+ refuses to send authentication credentials over remote plaintext HTTP, and it
63
67
  *reports* catalog drift without failing on it — an unclassified downstream tool
64
68
  already fails closed onto `call_destructive_tool`, so drift is a maintainer's
65
69
  next task rather than a broken deployment
66
70
  ([#343](https://github.com/zackbart/connecta/issues/343)).
67
71
 
72
+ For an Access-protected Worker, doctor sends the service-token pair to both
73
+ `/health` and `/mcp`; Access authenticates those requests before connecta runs.
74
+ `CONNECTA_TOKEN` remains the Node and legacy Worker path, and may be supplied
75
+ alongside the Access pair during rollback testing. A partial pair is refused.
76
+
68
77
  ### Configuration
69
78
 
70
79
  Structural seams stay top-level; tuning is grouped by subsystem. Every group is
@@ -74,7 +83,7 @@ optional.
74
83
  | --- | --- | --- |
75
84
  | `connectors` | — (required) | the connector set ([connectors](./connectors.md)) |
76
85
  | `executor` | — (required) | the sandbox `execute_code` runs in ([code mode](./code-mode.md#what-an-executor-must-implement)) |
77
- | `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before Clerk ([inbound auth](./auth.md)) |
86
+ | `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before interactive providers ([inbound auth](./auth.md)) |
78
87
  | `storage?` | `memoryStorage()` | the one state seam for catalogs, result paging, credentials, and access tokens ([storage](./storage-and-credentials.md)) |
79
88
  | `publicUrl?` | per-request origin | public base URL; an HTTPS value also redirects inbound HTTP |
80
89
  | `logger?` | `console`, prefixed `[connecta]` | `{ debug, info, warn, error }` |
@@ -83,7 +92,7 @@ optional.
83
92
  | `deploymentInfo?` | unset | arbitrary metadata exposed by `/health` |
84
93
  | `activity?` | unset | `{ store, readGate?, deploymentId? }` — payload-free activity storage, an optional operator-read gate, and a stable event label |
85
94
  | `credentials.encryptionKey?` | unset | base64 32-byte AES key for the connector vault. Without it, connectors declaring `credential` warn and their slots stay unmanageable |
86
- | `accessTokens?` | unset | `{ maxActive? }` (default 100) for operator-issued MCP bearer tokens. Requires a Clerk provider, or construction throws ([access tokens](./auth.md#operator-issued-access-tokens)) |
95
+ | `accessTokens?` | unset | `{ maxActive? }` (default 100) for operator-issued MCP bearer tokens. Requires an interactive operator provider, or construction throws ([access tokens](./auth.md#operator-issued-access-tokens)) |
87
96
  | `discovery.concurrency?` | 4 | connector catalogs/status probes in flight at once |
88
97
  | `discovery.catalogTtlSeconds?` | 300 | fresh TTL for cached tool lists |
89
98
  | `discovery.persistCatalog?` | true | persist serializable catalogs as a manifest plus revision-addressed chunks |
@@ -214,7 +223,7 @@ in.
214
223
 
215
224
  | Suite | Covers |
216
225
  | --- | --- |
217
- | `access-tokens.test.ts` | the `AccessTokenManager` — a one-time secret created, authenticated, renamed, and revoked, bounded names and active count, enumerable storage required, a deployment with no Clerk operator refused — and the Clerk-only routes, down to historical activity still resolving a revoked token's name |
226
+ | `access-tokens.test.ts` | the `AccessTokenManager` — a one-time secret created, authenticated, renamed, and revoked, bounded names and active count, enumerable storage required, a deployment with no interactive operator refused — and the operator-only routes, down to historical activity still resolving a revoked token's name |
218
227
  | `activity.test.ts` | payload-free delivery: a rejected async write attaches to `waitUntil` instead of throwing, approved destructive calls record under their real entry point, result-size friction records without retaining the result, and a hallucinated connector id or invented identity is clamped so the event still cannot carry a payload |
219
228
  | `api-connector.test.ts` | `api()` — kind, description, tool defs, dispatch, default args, unknown tools, handler throws, argument validation, and the construction contract |
220
229
  | `bearer.test.ts` | constant-time bearer compare, case-insensitive scheme, 401 challenges, and the retired audience options refusing rather than silently unbinding |
@@ -223,7 +232,8 @@ in.
223
232
  | `catalog-drift.test.ts` | `vettedCatalog()`, `detectCatalogDrift()`, and `withVettedCatalog()`; drift on the registry surface and on `/health`; the connector seam projected rather than echoed; and the drift types being public |
224
233
  | `catalog.test.ts` | lexical ranking and the compact schema renderer — `const`, `allOf` beside siblings, `$ref`, the depth limit, per-schema caching, and 2020-12 keyword compatibility |
225
234
  | `clerk.test.ts` | protected-resource metadata, the browser sign-in config, OAuth and session tokens, cached best-effort activity labels with their caps, the hand-applied `azp` rejection, and the `allowedDomains` allowlist including every lookalike that must not be repaired into a match |
226
- | `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, request building, projections, typed failures, and credential test |
235
+ | `cloudflare-access-auth.test.ts` | trusted `ctx.access` human and service identities, absent/error fail-closed behavior, service-token MCP admission without operator mutation, human same-origin mutation, and the Clerk-to-ambient shell switch |
236
+ | `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, current R2 and KV jurisdictions, useful output declarations, request building, projections including additive provider fields, typed failures, and credential test |
227
237
  | `code-first-surface.test.ts` | the seven-tool surface itself — an executor required, every removed option and top-level tool refused, compact always-loaded routing pinned below 1,000 characters, complete on-demand usage served, and `connecta.ui` findable before connector search |
228
238
  | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
229
239
  | `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and unknown own-properties rejected by their complete path before construction does work |
@@ -237,14 +247,14 @@ in.
237
247
  | `executor-admission.test.ts` | the portable bounded FIFO both pools use: active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown |
238
248
  | `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
239
249
  | `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, discovery, utility, batch, and budget failure codes; plus the real authority boundary — local `data:` fetch, denied egress, unresolved DNS, empty environment paths, unavailable filesystem/HTTP builtins, and present runtime globals |
240
- | `linear-provider.test.ts` | the Linear proxy's construction, classification, and guide |
250
+ | `linear-provider.test.ts` | the Linear proxy's construction, guide, plan-aware catalog superset, and current workspace, template, and issue-sharing classifications |
241
251
  | `meta-tools-call.test.ts` | registry-backed calls: structured errors, truncation and `get_result`, per-connector result bounds, JSON representation failures, MCP content bounds, and offset alignment |
242
252
  | `meta-tools-search.test.ts` | registry-backed discovery: bounded search with page and address maxima, compact and JSON schemas with constraints, typed describe recovery and suggestions, and structured-result compatibility |
243
253
  | `meta-tools.test.ts` | the remaining registry-backed meta-tools: the complete on-demand usage skill, connector-guide selection and summary bounds, stored-credential drift, catalog health, authorization, probe timeouts, and unavailable or unknown browse recovery |
244
- | `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide and complete reviewed schema-digest manifest |
254
+ | `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide, destructive metadata fill, and complete 64-tool schema-digest manifest |
245
255
  | `notion-provider.test.ts` | Notion's deliberate tool surface, including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes |
246
256
  | `operator-boundary.test.ts` | the operator row of the decisions table, after every mutation route: authentication material managed without moving a declared structure, and the one honest exception — a credential write making a remote catalog appear, which is discovery arriving, not an operator editing the deployment |
247
- | `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, `gate()`, the generation fence, and the request path |
257
+ | `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, ambient Access requests without a browser-readable token, `gate()`, the generation fence, and the request path |
248
258
  | `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
249
259
  | `provider-registry.test.ts` | all six maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
250
260
  | `registry.test.ts` | construction and id validation, startup warnings, address resolution, version 2 catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, and broken-connector isolation |
@@ -253,11 +263,11 @@ in.
253
263
  | `remote-mcp-pagination.test.ts` | the `tools/list` cursor chain in both directions — exact cursor handoff, first-wins dedup, a failed later page rejecting rather than returning its prefix, the runaway backstops, the tool-metadata re-prime across pages, and paginated catalogs reaching the discovery path |
254
264
  | `request-admission.test.ts` | `/mcp` bounded before auth, the stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, and the separate fallback code pool |
255
265
  | `result-shapes.test.ts` | passive output-shape learning: value-free bounded inference, merging, 256-entry LRU eviction, 24-hour expiry, runtime isolation, read-only admission, declared-schema precedence, definition-change invalidation, discovery provenance, and failure isolation |
256
- | `revenuecat-provider.test.ts` | the RevenueCat proxy's per-project key scoping and account-wide OAuth guides, its purpose-bearing summary, the argued borderline verdicts in its digest-free manifest, and the deliberately unclassified `render-paywall-screenshot` |
266
+ | `revenuecat-provider.test.ts` | the RevenueCat proxy's per-project key scoping and account-wide OAuth guides, its purpose-bearing summary, the refund-preference read and argued borderline verdicts in its digest-free manifest, and the deliberately unclassified `render-paywall-screenshot` |
257
267
  | `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory and exact model-only Apps metadata → complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, and deferred catalog reads through both discovery surfaces |
258
268
  | `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: every built-in answered ahead of connector routes inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
259
269
  | `startup-warnings.test.ts` | every construction-time `logger.warn` and, as importantly, the conditions that must *not* trigger one: open mode with a credential or OAuth connector, `publicUrl` unset beside OAuth, dropped branding and `uiAuth` URLs, a missing `verifyState`, a credential test-hook mismatch, and an unusable `calls.maxResultBytes` |
260
- | `stripe-provider.test.ts` | the Stripe proxy's mixed-mode OAuth and fixed-mode header contracts, admission, exact account selectors, and no-guess rule |
270
+ | `stripe-provider.test.ts` | the Stripe proxy's mixed-mode OAuth and fixed-mode header contracts, current eleven-tool classifications, admission, exact account selectors, and no-guess rule |
261
271
  | `operator-view.test.ts` | the app's pure state rules from `view.ts`: filtering, page routing, capability states, activity summaries, drift display, and identity reset |
262
272
  | `ui-credentials.test.ts` | credential-management routes: save, test, delete, validation, authentication, same-origin checks, and multi-field credential shapes |
263
273
  | `ui.test.ts` | the server shell and remaining `/ui/*` routes: gated `/ui/data` with broken-connector isolation and registry-owned catalog-observation containment, plus the URL safety gates |
@@ -272,13 +282,13 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
272
282
  | --- | --- | --- |
273
283
  | `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, one Node template that is also its own container, the same source running locally and in the container, the Node template's pinned esbuild install-script approval, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
274
284
  | `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
275
- | `doctor-cli.test.ts` | `connecta doctor`'s executor line end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, and a hostile name is bounded and stripped before it reaches a terminal | spawns the CLI against a Node HTTP deployment over real sockets |
285
+ | `doctor-cli.test.ts` | `connecta doctor`'s executor line and credentials end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, a hostile name is bounded, and a complete Cloudflare Access service-token pair is accepted while a partial pair is refused | spawns the CLI against a Node HTTP deployment over real sockets |
276
286
  | `drift-check.test.ts` | the maintainer drift checker — hosted-provider credential framing, recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
277
287
  | `file-storage.test.ts` | `fileStorage()` across instances, logical TTL plus physical pruning without clobbering a newer value, and corrupt-file quarantine | exercises the Node filesystem storage adapter |
278
288
  | `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes and inline describe recovery, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
279
289
  | `node.test.ts` | the `listen()` adapter propagating an HTTP client disconnect through the Web `Request` and the MCP handler into a program's connector call, releasing both admission permits | exercises the Node HTTP adapter over real TCP sockets |
280
290
  | `packed-links.test.ts` | the packed-link gate itself — shipped targets and repository URLs accepted, relative links into unshipped paths and directories rejected with the citation to write instead, reference definitions seen, fenced examples ignored, the changelog exempt | spawns the Node packed-link gate against filesystem fixtures |
281
- | `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, every provider independently importable, and the Cloudflare provider free of bare specifiers | walks the package tree with Node filesystem APIs |
291
+ | `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, dependency-free Cloudflare Access behind its Worker subpath, every provider independently importable, and the Cloudflare API provider free of bare specifiers | walks the package tree with Node filesystem APIs |
282
292
  | `purity.test.ts` | the import-graph guardrail ([architecture](./architecture.md#import-graph-purity)) — the core stays Workers-clean | walks the source import graph with Node filesystem APIs |
283
293
  | `quickjs-child-entry.test.ts` | a missing QuickJS child entry failing before `fork()`, with the expected path and the bundler-externalization constraint | mocks Node child-process and filesystem APIs |
284
294
  | `quickjs-child-stderr.test.ts` | abnormal child exits retaining only an 8 KiB stderr tail, included in the parent-side diagnostic | mocks Node child-process streams |
@@ -319,10 +329,15 @@ confinement does too.
319
329
  - **A connector with no `verifyState` refuses every callback.** That is the
320
330
  designed behavior, not a bug: handing an unverified code to `finishAuth` is
321
331
  the vulnerability. The startup warning names the connector.
322
- - **401 loops from a client that cannot discover auth.** The client must reach
332
+ - **401 loops from a Clerk client that cannot discover auth.** The client must reach
323
333
  the open `/.well-known/oauth-protected-resource` (and the `/mcp` variant);
324
334
  confirm CORS and the Clerk keys, and that DCR is enabled on the Clerk
325
335
  instance.
336
+ - **An Access-protected MCP client receives redirects or loops.** Enable Managed
337
+ OAuth on the Access application and use an RFC 8707-capable client. Access,
338
+ not connecta, must answer the unauthenticated challenge and `/.well-known/`
339
+ metadata. Do not bypass those routes. For unattended automation, use an
340
+ Access Service Auth policy and service-token headers instead.
326
341
  - **No sessions and no server push, by design.** The transport is stateless.
327
342
  Scope resolves per request, which is also where the MCP spec has arrived.
328
343
  - **A tool that should be callable from a program is not.** Only tools
@@ -39,10 +39,25 @@ their walkthrough, which is the honest version of the same page count.
39
39
  | `src/operator-ui/browser.css` | One stylesheet, inlined into the shell. |
40
40
  | `src/operator-ui/generated.ts` | The build output: the bundle and the stylesheet as two exported strings. |
41
41
 
42
- The server renders a mount point, not a page. Branding, the Clerk loader, and
43
- every operator-configured URL stay in `src/ui.ts`, where they are gated before
44
- they can become an attribute; the bundle renders everything that has a state.
45
- Two roots share one store: `#operatorNav` and `#operatorContent`.
42
+ The server renders a mount point, not a page. Branding, the optional Clerk
43
+ loader, and every operator-configured URL stay in `src/ui.ts`, where they are
44
+ gated before they can become an attribute; the bundle renders everything that
45
+ has a state. Two roots share one store: `#operatorNav` and `#operatorContent`.
46
+
47
+ Cloudflare Access is ambient browser auth. When the current Worker invocation
48
+ has `ctx.access`, the shell selects the `cloudflare-access` UI mode, emits no
49
+ Clerk loader, and sends no browser-readable token. Same-origin fetch includes
50
+ the HttpOnly `CF_Authorization` cookie, Access admits it at the edge, and the
51
+ server reads the resulting runtime identity. Sign out navigates to
52
+ `/cdn-cgi/access/logout`. Mutations still require an exact same-origin
53
+ `Origin`; an ambient cookie does not weaken the CSRF boundary.
54
+
55
+ This runtime selection is the Clerk migration seam. A deployment may contain
56
+ both providers: before Worker-level Access is attached, the data-free shell
57
+ selects Clerk; after Access supplies `ctx.access`, it selects ambient auth. That
58
+ is not two same-hostname gates running in parallel. Access is upstream and a
59
+ request it rejects never reaches Clerk. Keeping Clerk in the array preserves a
60
+ code-level rollback after Access is detached.
46
61
 
47
62
  The Clerk loader is intentionally blocking. The inline operator bundle calls
48
63
  `boot()` as soon as the parser reaches the end of the body, so a deferred Clerk
@@ -58,7 +73,8 @@ the same ordering.
58
73
  credential, token, and activity data arrives only through the authenticated
59
74
  `/ui/*` APIs, and the shell is identical whether or not a caller is signed in.
60
75
  - **One store, one identity.** `store.ts` is the only file that touches `fetch`,
61
- `localStorage`, or Clerk. Every request carries the current session's token,
76
+ `localStorage`, Clerk, or the ambient Access mode. Every token-bearing request carries the current session's token,
77
+ while Access requests deliberately carry none,
62
78
  and every response is dropped unless the identity that asked for it is still
63
79
  the one on screen. `resetIdentity` replaces all identity-scoped state at once
64
80
  and bumps a generation that work already in flight compares itself against.
@@ -693,8 +693,10 @@ Cloudflare keep/prune half of that judgment was made in
693
693
  [#350](https://github.com/zackbart/connecta/issues/350): 30 keep, 18 improve,
694
694
  3 prune, measured per tool in
695
695
  [`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md).
696
- Its eighteen `improve` rows are H8 and H9 misses on tools that clearly earn
697
- their place, so they are this audit's work, not a second removal argument.
696
+ Its eighteen `improve` rows were H8 and H9 misses on tools that clearly earned
697
+ their place. Issue #488 resolved them with useful output keys and maintained
698
+ projections; the measurements remain the reason those tools stayed rather than
699
+ a second removal argument.
698
700
 
699
701
  Each provider's own guide ([Cloudflare](./cloudflare.md),
700
702
  [Linear](./linear.md), [Mixpanel](./mixpanel.md), [Notion](./notion.md),
@@ -152,17 +152,18 @@ names the `authorize_connector` recovery. A permission gap, a plan restriction,
152
152
  or a rejected argument arrives as RevenueCat wrote it and is not an
153
153
  authorization problem.
154
154
 
155
- ## The ninety-five tools, and what they are classified as
155
+ ## The ninety-six tools, and what they are classified as
156
156
 
157
157
  RevenueCat's
158
158
  [tool reference](https://www.revenuecat.com/docs/tools/mcp/tools-reference),
159
- read on **2026-08-18**, documents ninety-five tools in a Read/Write table.
160
- Ninety-four carry an access column and are classified here: **50 read-only, 15
161
- additive writes, 29 destructive writes.**
159
+ read on **2026-08-30**, documents ninety-six tools. Ninety-five carry an access
160
+ column and are classified here: **51 read-only, 15 additive writes, 29
161
+ destructive writes.**
162
162
 
163
163
  Reads are every `Read` row, verbatim — the nine project and app reads, the four
164
164
  product reads, the entitlement, offering, targeting, paywall, customer, virtual
165
- currency, chart, webhook, and SDK reads, and `get-paywall-ai-task`.
165
+ currency, chart, webhook, and SDK reads, `get-paywall-ai-task`, and
166
+ `get-refund-request-preferences`.
166
167
 
167
168
  Writes follow the verb where the verb is honest: `archive-*` and `unarchive-*`
168
169
  flip an existing object's active state, `update-*`, `delete-*`, `publish-*`,
@@ -191,14 +192,15 @@ beside the row:
191
192
  `create-webhook-integration` deserves a sentence too. No existing integration
192
193
  changes, so the verb reads additive — but with filters omitted the new one
193
194
  "starts delivering" every customer event in the project to a URL the caller
194
- typed. Customer data leaving the account is the `create_refund` argument again:
195
- filed destructive on consequence, so the approval copy says what is at stake.
195
+ typed. Customer data leaving the account makes it destructive on consequence,
196
+ so the approval copy says what is at stake.
196
197
 
197
198
  **`render-paywall-screenshot` is deliberately unclassified.** RevenueCat's
198
- reference gives it no access column at all, so no release has reviewed what it
199
- does. It fails closed onto `call_destructive_tool` rather than being guessed
200
- into the read path because its name sounds harmless (P5). The guide names it,
201
- so an agent does not read the approval prompt as a bug.
199
+ reference gives it no access column. The current live server explicitly marks
200
+ it read-only, so that catalog keeps it callable from `execute_code`; if a later
201
+ catalog omits the annotation, it fails closed onto `call_destructive_tool`.
202
+ Connecta preserves the provider's current annotation without inventing a
203
+ release classification from the tool's harmless-sounding name (P5).
202
204
 
203
205
  That classification fills in downstream silence and otherwise preserves explicit
204
206
  annotations. A tool on the read allowlist arriving with `destructiveHint: true`
@@ -242,7 +244,7 @@ API v2 meters per minute and **per domain**
242
244
  | Charts & Metrics | 25 |
243
245
 
244
246
  A `ConnectorCallAdmissionPolicy` carries exactly one rule, so a connector-wide
245
- budget has to pick one of those six numbers for all ninety-five tools.
247
+ budget has to pick one of those six numbers for all ninety-six tools.
246
248
  Transcribing 25 would throttle a customer read loop to a nineteenth of its
247
249
  documented allowance; transcribing 480 would leave a chart sweep unprotected.
248
250
  Neither is the provider's limit, and both would look like RevenueCat being
@@ -281,15 +283,13 @@ still needs restrained use.
281
283
 
282
284
  ## What is not verified
283
285
 
284
- - **Every tool name is transcribed from RevenueCat's published reference on
285
- 2026-08-18, not read from a live catalog.** No maintainer ran this against a
286
- project with a real key before it shipped. A name the reference lists and the
287
- server does not serve costs nothing; a tool the server serves and the
288
- reference omits fails closed. The maintainer-run drift check with
289
- `CONNECTA_DRIFT_REVENUECAT_KEY` set is what turns that into a finding with a
290
- name attached.
291
- - **No input or output schema has been read**, which is why the manifest
292
- carries no digests.
286
+ - **The 2026-08-30 live review used a project-scoped catalog.** It proves the
287
+ additions that catalog serves, including `get-refund-request-preferences`,
288
+ but cannot prove a globally documented tool was removed. The manifest stays
289
+ a superset because plan, platform, and credential scope hide tools.
290
+ - **No complete schema set has been recorded**, which is why the manifest
291
+ carries no digests. The review read the new live schemas, but its scoped
292
+ catalog omitted many classified writes.
293
293
  - **Whether `render-paywall-screenshot` mutates anything.** It has no access
294
294
  column, and guessing is exactly what P5 exists to prevent.
295
295
 
@@ -139,38 +139,39 @@ boots but cannot list tools is usually a dashboard toggle, not a bad key.
139
139
 
140
140
  ## The eleven tools, and what they are classified as
141
141
 
142
- Stripe documents eleven tools on the hosted server. Seven are reads:
142
+ Stripe currently serves eleven tools. Seven are reads:
143
143
 
144
144
  `stripe_api_search`, `stripe_api_details`, `stripe_api_read`,
145
- `get_stripe_account_info`, `get_balance_summary`,
146
- `search_stripe_documentation`, `stripe_implementation_planner`.
145
+ `get_balance_summary`, `list_available_accounts_or_orgs`,
146
+ `manage_stripe_accounts`, and `search_stripe_documentation`.
147
147
 
148
148
  Four are writes:
149
149
 
150
- `stripe_api_write` and `create_refund` are classified destructive;
151
- `stripe_report` and `send_stripe_mcp_feedback` are additive.
150
+ `stripe_api_write` is destructive. `stripe_implementation_planner`,
151
+ `stripe_analytics`, and `send_stripe_mcp_feedback` are additive.
152
152
 
153
153
  Two of those deserve a sentence. `stripe_api_read` is a read because Stripe
154
154
  documents it as the `GET` half of a generic pair — the tool is the read
155
155
  boundary, not whichever endpoint an agent names inside it, and its sibling
156
156
  `stripe_api_write` carries every `POST`, `PATCH`, `PUT`, and `DELETE`.
157
- `create_refund` is filed destructive despite its name: it reverses a settled
158
- charge and moves money back out, which is a mutation of something that already
159
- exists rather than a fresh object appearing beside it. Additive writes
160
- (`stripe_report`, `send_stripe_mcp_feedback`) leave `destructiveHint` unset;
157
+ `stripe_implementation_planner` creates and continues provider-side guide
158
+ state. `stripe_analytics` combines retrieval with query-run creation behind one
159
+ tool. Stripe marks both not read-only and not destructive, so Connecta records
160
+ them as additive writes. They leave `destructiveHint` unset. Their explicit
161
161
  `readOnlyHint: false` already routes them through `call_destructive_tool`, and
162
- asserting destruction only inflates the approval copy the host shows a human.
162
+ asserting destruction would misstate their effect.
163
163
 
164
164
  That classification fills in downstream silence and otherwise preserves
165
- explicit annotations. It supplies the annotations Stripe leaves unset Stripe
166
- documents no MCP annotations at all. A tool on the read allowlist arriving with
165
+ explicit annotations. The current server annotates all eleven tools, while the
166
+ manifest records what this release reviewed and exposes later contradictions.
167
+ A tool on the read allowlist arriving with
167
168
  `destructiveHint: true` or `readOnlyHint: false` keeps exactly what the
168
169
  downstream said and stays behind `call_destructive_tool`. A tool on neither
169
170
  maintained list arriving with `readOnlyHint: true` keeps that too, and stays
170
171
  callable from `execute_code`. Both are the downstream telling you this release's
171
172
  allowlist is stale, and on a name no release has reviewed its word is the only
172
173
  evidence there is. One narrow fail-closed exception applies to a name this
173
- release reviewed and filed destructive: a `create_refund` claiming
174
+ release reviewed and filed destructive: `stripe_api_write` claiming
174
175
  `readOnlyHint: true` is a downstream bug rather than news, and stays on the
175
176
  approval path.
176
177
 
@@ -57,7 +57,7 @@ exist so far:
57
57
  | --- | --- | --- |
58
58
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
59
  | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
60
- | **B** | 0.16.0 – 0.20.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
60
+ | **B** | 0.16.0 – 0.21.1 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
61
61
 
62
62
  Generation A is a decade in template years and identifying it precisely does
63
63
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
106
106
  ### Bump the pin and install
107
107
 
108
108
  ```sh
109
- npm pkg set dependencies.@zackbart/connecta=0.20.0
109
+ npm pkg set dependencies.@zackbart/connecta=0.21.1
110
110
  npm install
111
111
  ```
112
112
 
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
130
130
  `$SCRATCH`:
131
131
 
132
132
  ```sh
133
- (cd "$SCRATCH" && npx @zackbart/connecta@0.20.0 init current)
133
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.21.1 init current)
134
134
  ```
135
135
 
136
136
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
186
  manufacture one. Instead:
187
187
 
188
188
  1. `SCRATCH=$(mktemp -d)`, then
189
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.20.0 init current)` — there is no
189
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.21.1 init current)` — there is no
190
190
  `base` leg here, only the current template to read from.
191
191
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
192
  `src/index.ts`**.
@@ -207,6 +207,79 @@ first, so cross them bottom-up: start at the oldest one still above this
207
207
  deployment's pin and work back up the page, because each boundary assumes the
208
208
  older ones are already done.
209
209
 
210
+ ### 0.20.0 → 0.21.1
211
+
212
+ 0.21.1 adds no deployment migration beyond 0.21.0. The boundary is additive
213
+ for Node and existing Clerk deployments. The new Worker path
214
+ uses Cloudflare Access identity directly and removes Clerk only after the edge
215
+ cutover has been verified. An agent can perform every repository edit; a human
216
+ must attach Access, choose its policy, create service credentials, and enable
217
+ Managed OAuth in the Cloudflare dashboard.
218
+
219
+ For a Worker currently using Clerk, keep rollback live through the cutover:
220
+
221
+ 1. Bump and install 0.21.1. Add the new provider **before** the existing Clerk
222
+ provider, but remove nothing:
223
+
224
+ ```ts
225
+ import { cloudflareAccessAuth } from
226
+ "@zackbart/connecta/auth/cloudflare-access";
227
+ import { clerkAuth } from "@zackbart/connecta/auth/clerk";
228
+
229
+ auth: [
230
+ cloudflareAccessAuth(),
231
+ // Keep the deployment's existing options and secrets unchanged.
232
+ clerkAuth({ /* existing configuration */ }),
233
+ ],
234
+ ```
235
+
236
+ Before Access is attached, the new provider fails closed and the operator
237
+ shell selects Clerk. Deploy this state and run doctor with the existing
238
+ `CONNECTA_TOKEN`. This separates the package/code change from the edge
239
+ change and proves the old path still works.
240
+
241
+ 2. In Cloudflare, attach Access to the Worker itself, apply the intended human
242
+ policy, and enable Managed OAuth. Through the API this is an Access
243
+ application destination of `{ "type": "worker", "worker_id": "<script
244
+ tag>" }`, not a hostname application for the `workers.dev` URL: the latter
245
+ gates traffic but does not provide `ctx.access`. Create an Access service
246
+ token and a **Service Auth** policy for doctor and fully unattended clients.
247
+ Do not create a bypass for `/.well-known/*`; Managed OAuth owns that
248
+ discovery surface.
249
+
250
+ 3. Reconnect interactive MCP clients to `<PUBLIC_URL>/mcp`. Their old Clerk
251
+ OAuth tokens are not Cloudflare credentials, so each client performs one new
252
+ browser authorization. An agent can edit client configuration and start the
253
+ flow; the user still completes the identity-provider prompt. Move CI, cron,
254
+ and server-to-server callers from connecta bearers to the two Access service
255
+ headers. The cutover warning is literal: once Access is attached, a static
256
+ bearer or `cta_…` token by itself is stopped at the edge before connecta can
257
+ inspect it.
258
+
259
+ 4. Verify the edge path:
260
+
261
+ ```sh
262
+ CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
263
+ npx connecta doctor --url https://connecta.example.workers.dev
264
+ ```
265
+
266
+ Open `/` as a human and exercise any enabled credential, token, and OAuth
267
+ controls. A service token may pass doctor and MCP but must receive 403 from
268
+ operator mutations.
269
+
270
+ 5. After an observation window, remove `clerkAuth`, its import,
271
+ `@clerk/backend`, and the Clerk variables/secrets. Until then they are inert
272
+ behind Access but preserve rollback. Rollback order matters: detach Access
273
+ first, then the untouched Clerk sessions and connecta bearers reach the
274
+ Worker again. Reverting code first cannot help a request the edge still
275
+ blocks. There is no storage migration and no token-format conversion.
276
+
277
+ If the Worker exposes an intentionally public connector route, create a
278
+ more-specific hostname/path Access application with a Bypass policy for that
279
+ route only. `/health`, downstream OAuth callbacks, operator shells, and MCP are
280
+ private under the canonical whole-Worker shape; doctor knows how to authenticate
281
+ its health request.
282
+
210
283
  ### 0.19.0 → 0.20.0
211
284
 
212
285
  Three intake paths become deliberately strict. None changes storage, the two