@zackbart/connecta 0.34.1 → 0.36.0
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.
- package/CHANGELOG.md +418 -0
- package/README.md +65 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2044 -1161
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +751 -281
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +29 -7
- package/dist/connectors/api-connector.js +145 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +227 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +379 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/contract.d.ts +8 -3
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +427 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/kit.js +146 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +801 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1023 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/quickbooks/api.js +299 -0
- package/dist/providers/quickbooks/index.d.ts +13 -0
- package/dist/providers/quickbooks/index.js +93 -0
- package/dist/providers/quickbooks/lib/query.js +118 -0
- package/dist/providers/quickbooks/lib/writes.js +168 -0
- package/dist/providers/quickbooks/safety.js +222 -0
- package/dist/providers/quickbooks/skill.generated.js +5 -0
- package/dist/providers/quickbooks/tools.js +117 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +733 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +65 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +6 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +552 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -223
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +9 -109
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +28 -7
- package/documentation/architecture.md +2409 -255
- package/documentation/auth.md +867 -114
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +84 -48
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +1347 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +30 -50
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/operator-ui/agents.d.ts +0 -74
- package/dist/operator-ui/agents.js +0 -104
- package/dist/operator-ui/config-summary.js +0 -565
- package/dist/operator-ui/connection-map.js +0 -376
- package/dist/operator-ui/endpoints.js +0 -221
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -87
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -123
- package/dist/operator-ui/setup-commands.js +0 -51
- package/dist/operator-ui/view.d.ts +0 -401
- package/dist/operator-ui/view.js +0 -705
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/dispatch.js +0 -41
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/tools.js +0 -569
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -50,12 +50,40 @@ deployment shapes construct it outside the request handler.
|
|
|
50
50
|
An OAuth connector, `remoteMcp()` or `api()` with a static grant, owns a
|
|
51
51
|
runtime-local refresh completion gate in `src/auth/downstream-oauth.ts`.
|
|
52
52
|
Joined scopes share the outcome while keeping their own request I/O and
|
|
53
|
-
cancellation.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
cancellation. Grant use and the caller's wait belong to the call. Renewal
|
|
54
|
+
preparation is call-owned only until the first durable coordinator write
|
|
55
|
+
(the claim CAS) is submitted. Closing the call or request before
|
|
56
|
+
submission promptly refuses with "This call has ended…": no CAS, no token
|
|
57
|
+
request and no state change. Late read results cannot restart preparation.
|
|
58
|
+
Claim submission transfers renewal synchronously to the storage coordinator,
|
|
59
|
+
before CAS completion or token dispatch. The coordinator retains its own
|
|
60
|
+
35-second preparation deadline and then a 20-second deadline for the token
|
|
61
|
+
request and response body, followed by the fenced rotation commit. Neither
|
|
62
|
+
the call nor request signal controls this work after submission. A winning
|
|
63
|
+
claim proceeds to token exchange and commit; a losing claim follows existing
|
|
64
|
+
contention/adoption rules. The runtime passes completion to Workers
|
|
65
|
+
`waitUntil`. Cancellation cannot abandon a fenced claim or strand a spent
|
|
66
|
+
refresh token, and introduces no rollback or re-consent exception. A shared
|
|
67
|
+
storage CAS lease prevents independent isolates from redeeming the same
|
|
68
|
+
refresh token concurrently. The holder commits an accepted rotation by CAS
|
|
69
|
+
before releasing waiters, even after caller closure. Closing a call or request
|
|
70
|
+
still promptly ends that caller's wait.
|
|
71
|
+
For a shared remote MCP connection, the challenged response identifies the
|
|
72
|
+
call that owns renewal preparation. The connection and OAuth provider remain
|
|
73
|
+
request-owned; their longer lifetime cannot keep pre-claim renewal alive
|
|
74
|
+
after its initiating call closes.
|
|
75
|
+
Authorization-code exchange transfers no refresh ownership: its auxiliary
|
|
76
|
+
token POST and pending wait abort on explicit request revocation even when
|
|
77
|
+
the operation has only a `requestScope` and no external signal. A response
|
|
78
|
+
that arrives afterward cannot persist tokens. The callback route keeps its
|
|
79
|
+
existing uninterruptible ownership and does not revoke its own scope during
|
|
80
|
+
exchange; explicit operation revocation is a separate boundary. An aborted
|
|
81
|
+
code exchange can be authorized again without stranding a durable refresh
|
|
82
|
+
claim.
|
|
83
|
+
The closed call consumes no renewal result, marks no ledger entry renewed,
|
|
84
|
+
and sends or resends no resource request. This preserves a rotation after its
|
|
85
|
+
refresh token has been spent while keeping call capabilities revoked.
|
|
86
|
+
Contenders read the committed tokens or typed
|
|
59
87
|
verdict. An unsent claim can be taken over after expiry. Dispatch uses a lease CAS
|
|
60
88
|
transition and a CAS of the token fingerprint into permanent spent storage
|
|
61
89
|
before sending. Fingerprint records have no TTL and carry outstanding,
|
|
@@ -79,7 +107,7 @@ request that created them. INV-7 requires this lifetime: a client retained
|
|
|
79
107
|
across requests on Workers is a cross-request capability leak, and a promise awaited after the response is
|
|
80
108
|
work the runtime may already have torn down. Deferred work has one sanctioned
|
|
81
109
|
channel, `ctx.waitUntil`, threaded through `fetch(request, env, ctx)` — activity
|
|
82
|
-
writes and
|
|
110
|
+
writes and OAuth renewals from claim submission use it. Catalog listing and SQL cache
|
|
83
111
|
publication finish in the requesting scope. A fresh
|
|
84
112
|
`McpServer` per request is what makes the deployment stateless: no sessions, no
|
|
85
113
|
server push, no stream resumability, scope resolved rather than remembered.
|
|
@@ -119,7 +147,7 @@ reordering reads like a harmless refactor.
|
|
|
119
147
|
An admitted non-preflight `/mcp` request then takes five steps in
|
|
120
148
|
`src/routes/mcp.ts`:
|
|
121
149
|
|
|
122
|
-
1. **Admit.**
|
|
150
|
+
1. **Admit.** The fetch-native MCP route reads no body before auth on any endpoint. A request
|
|
123
151
|
whose `Mcp-Method` header names `subscriptions/listen` takes no permit
|
|
124
152
|
before auth. After auth and endpoint admission connecta reads at most
|
|
125
153
|
64 KiB (`MAX_LISTEN_CLASSIFICATION_BYTES`) of it: past that it answers
|
|
@@ -128,10 +156,44 @@ An admitted non-preflight `/mcp` request then takes five steps in
|
|
|
128
156
|
`-32601`; anything else takes a permit then, so the header alone bypasses
|
|
129
157
|
nothing. Auth and classification share the configured request lifetime.
|
|
130
158
|
Other requests take one permit from the deployment-wide pool, taken before
|
|
131
|
-
auth so an unauthenticated flood costs a permit
|
|
159
|
+
auth so an unauthenticated flood costs a request permit before an auth lookup,
|
|
132
160
|
and held until the response _body_ completes, the caller leaves, or its
|
|
133
161
|
configured lifetime ends, not until the handler returns.
|
|
134
|
-
2. **Authorize.**
|
|
162
|
+
2. **Authorize.** Every adapter invocation passes the independent fixed
|
|
163
|
+
16-active-authorization bound in `src/auth-admission.ts`, including header-named
|
|
164
|
+
listens, SDK-owned auth, agent-token storage, and operator/connection routes
|
|
165
|
+
outside the MCP pool. Shared state is counters and bounded plain charge
|
|
166
|
+
records, never request I/O; there is no queue or shared promise. Saturation
|
|
167
|
+
returns retryable 503 `authentication_busy` with `Retry-After: 1` and a
|
|
168
|
+
checked payload-free log. Each request releases exactly once after the
|
|
169
|
+
adapter and its cancellation cleanup settle; abort alone does not release
|
|
170
|
+
capacity. At its hard deadline plus one second of grace, unfinished work
|
|
171
|
+
moves to a separate allowance of 16 abandoned operations, or stays active
|
|
172
|
+
while that allowance is full. A full allowance refuses all new work, logged
|
|
173
|
+
as `authentication_abandoned_saturated`; only a charge's end decrements it
|
|
174
|
+
and promotes an overdue active charge. A charge ends exactly once, through a
|
|
175
|
+
released state in its plain record: on settlement, or when the collector
|
|
176
|
+
frees its token. That token is an object held only by the closures
|
|
177
|
+
attached to the adapter promise and the registered native operations (plus
|
|
178
|
+
the deadline timer and the request's lifetime entry, which refers to the
|
|
179
|
+
lifetime through a `WeakRef`, because workerd keeps a `Request` and its
|
|
180
|
+
WeakMap entries alive past its invocation). A module-level
|
|
181
|
+
`FinalizationRegistry` holds the record, never the token, so a freed token
|
|
182
|
+
proves nothing can settle the charge; the next admission logs
|
|
183
|
+
`authentication_unreachable`. Without `FinalizationRegistry` (workerd
|
|
184
|
+
without `enable_weak_ref` before 2025-05-05) only settlement releases.
|
|
185
|
+
Health exposes both counters and `unreachableRelease`. Access
|
|
186
|
+
and agent verification race five-second deadlines while registering their
|
|
187
|
+
uncancellable native work with the invocation's charge. The request's
|
|
188
|
+
`ctx.waitUntil` keeps that same settlement continuation runnable on Worker
|
|
189
|
+
disconnects. No other request touches its promise, signal or I/O. Clerk
|
|
190
|
+
uses a client per authorization, with its signal and a ten-second deadline
|
|
191
|
+
across verification, JWKS, handshakes, session refresh, domain and gate
|
|
192
|
+
lookups. The SDK transport buffers at most 1 MiB, closes every upstream body,
|
|
193
|
+
and makes no JWKS retries; no live response escapes to an SDK clone. No current adapter exposes a cheap,
|
|
194
|
+
fully authenticated cache hit, so the entire adapter invocation counts.
|
|
195
|
+
[Authentication capacity](./auth.md#authentication-capacity-and-ingress-limiting)
|
|
196
|
+
lists exemptions and ingress requirements. `/agent/<pool>` consults only the built-in `cta_` adapter
|
|
135
197
|
(`authorizeAgent`), never with the runtime context that carries
|
|
136
198
|
`ctx.access`. Human endpoints refuse a `cta_` token before recognition or
|
|
137
199
|
storage, and never ask the `cta_` adapter. A provider's
|
|
@@ -284,6 +346,34 @@ permit has its own deadline even if the recipient never resumes. Lease identitie
|
|
|
284
346
|
make an old late release harmless to a newer permit. Set the bound above the
|
|
285
347
|
longest admitted request the deployment expects to serve.
|
|
286
348
|
|
|
349
|
+
`admission.requests.maxBodyBytes` bounds each MCP request body at `/mcp`,
|
|
350
|
+
`/mcp/<pool>` and `/agent/<pool>`. It defaults to 4 MiB (4,194,304 bytes).
|
|
351
|
+
This restricts bodies that previously reached the host's larger platform cap;
|
|
352
|
+
deployments needing larger MCP envelopes must configure the bound explicitly.
|
|
353
|
+
A configured value must be a positive safe whole number; invalid values fail
|
|
354
|
+
construction with their configuration path (INV-11). After authentication and
|
|
355
|
+
endpoint admission, core counts streamed bytes before any SDK or native tool
|
|
356
|
+
body reader. A body exactly at the cap proceeds through normal protocol
|
|
357
|
+
validation. Past the cap, core stops reading, cancels both stream branches and
|
|
358
|
+
returns HTTP 413 with JSON-RPC code `-33006`, `"MCP request body too large."`.
|
|
359
|
+
The response retains the request's existing admission lifetime and releases
|
|
360
|
+
the permit when delivery ends. Caller cancellation and the existing request
|
|
361
|
+
deadline also cancel stalled reads. `describeConfig().limits.requests.maxBodyBytes`
|
|
362
|
+
reports the resolved value and its `default` or `config` source.
|
|
363
|
+
|
|
364
|
+
The pre-dispatch `subscriptions/listen` classification keeps its 64 KiB ceiling;
|
|
365
|
+
a smaller deployment body cap wins. Authentication failures and private endpoint
|
|
366
|
+
refusals still read no body. This envelope bound does not change program size,
|
|
367
|
+
tool argument validation, write accounting or result limits.
|
|
368
|
+
|
|
369
|
+
The Node `listen()` adapter separately buffers requests before calling the core,
|
|
370
|
+
with its own `maxBodyBytes` option. Its default is also 4 MiB. It stops at the
|
|
371
|
+
first chunk crossing that bound and returns a plain HTTP 413 before auth;
|
|
372
|
+
core's authenticated refusal uses the JSON-RPC envelope above. The smaller bound
|
|
373
|
+
wins. To admit larger bodies on Node, raise both options deliberately. Workers
|
|
374
|
+
and hosts calling `connecta.fetch` apply core's bound after authentication,
|
|
375
|
+
subject to any smaller platform or proxy limit.
|
|
376
|
+
|
|
287
377
|
Call admission (`src/call-admission.ts`) answers what the envelope cannot see —
|
|
288
378
|
a connector's optional policy over its own `Connector.callTool` attempts,
|
|
289
379
|
partitioned by an optional `partitionKey` and bounded by concurrency, a
|
|
@@ -336,7 +426,7 @@ client. A raw Worker stream established the runtime behavior; a custom auth
|
|
|
336
426
|
provider returning a synthetic streaming 401 exercised Connecta's real admission
|
|
337
427
|
wrapper. Each case had its own Connecta instance with one request permit and no
|
|
338
428
|
queue. Health checks and subsequent MCP requests confirmed the same isolate ID.
|
|
339
|
-
The [recorded observations](https://github.com/zackbart/connecta/blob/
|
|
429
|
+
The [recorded observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-2026-09-25.json)
|
|
340
430
|
contain all twelve cases, including served flags and client-end timing.
|
|
341
431
|
|
|
342
432
|
| Response and flag | Raw stream abort / cancel | Connecta source abort / cancel | Admission after response; next request |
|
|
@@ -364,29 +454,55 @@ the same hard deadline and is aborted in its own context.
|
|
|
364
454
|
Cloudflare documents the flag in its
|
|
365
455
|
[Request API](https://developers.cloudflare.com/workers/runtime-apis/request/).
|
|
366
456
|
|
|
457
|
+
### Authentication release by collection on workerd
|
|
458
|
+
|
|
459
|
+
Measured on 2026-10-20 with workerd 1.20260722.1 (Miniflare 4.20260722.0),
|
|
460
|
+
compatibility date `2025-01-01`:
|
|
461
|
+
|
|
462
|
+
- `FinalizationRegistry` and `WeakRef` are undefined without
|
|
463
|
+
`enable_weak_ref`; the flag's default-on date is 2025-05-05.
|
|
464
|
+
- With `enable_weak_ref` and `enable_request_signal`, an object reachable only
|
|
465
|
+
through a fetch continuation is finalized once its invocation ends and
|
|
466
|
+
workerd cancels the fetch; while the invocation runs it is not. Pending
|
|
467
|
+
timers and listeners on the request's signal do not keep it.
|
|
468
|
+
- Strong values in a WeakMap keyed by a `Request` (even one the handler
|
|
469
|
+
created), properties set on a `Request`, and WeakMap values keyed by its
|
|
470
|
+
`Headers`, `ctx` or `env` stay reachable after the invocation ends. The
|
|
471
|
+
lifetime registry in `src/auth-admission.ts` therefore stores `WeakRef`s.
|
|
472
|
+
- No `gc()` is exposed. Allocating large arrays (32 × 1 MiB per request)
|
|
473
|
+
triggers collections, but the most recent unreachable object often survives
|
|
474
|
+
them; V8 in this build scans the native stack conservatively. The inspector's
|
|
475
|
+
precise `HeapProfiler.collectGarbage` freed it.
|
|
476
|
+
- In the vitest-pool-workers harness, an isolate that had already collected
|
|
477
|
+
one scenario's cancelled work sometimes kept a later scenario's cancelled
|
|
478
|
+
work reachable through 400 rounds of pressure; standalone Miniflare never
|
|
479
|
+
did. `test/auth-admission-binding.test.ts` gives each collection scenario a
|
|
480
|
+
fresh isolate and requires two of four sentinels collected before asserting
|
|
481
|
+
that live charges stayed counted.
|
|
482
|
+
|
|
367
483
|
A second production run on the same date tested the request bound at 2,000 ms.
|
|
368
484
|
All twelve raw-stream and MCP cases passed with both flag configurations. Live
|
|
369
485
|
MCP requests still rejected a competing request with 503 before the deadline;
|
|
370
486
|
the first MCP request after the deadline returned all eight tools with 200 in
|
|
371
487
|
the original isolate. Admission then reported zero active requests. Without the
|
|
372
488
|
flag, the source still recorded no abort or cancellation, so recovery did not
|
|
373
|
-
rely on runtime cleanup. The [recovery observations](https://github.com/zackbart/connecta/blob/
|
|
489
|
+
rely on runtime cleanup. The [recovery observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-recovered-2026-09-25.json)
|
|
374
490
|
also retain two inconclusive queue-handoff cases: concurrent client connections
|
|
375
491
|
reached different isolates and could not establish an abandoned queued waiter.
|
|
376
|
-
A [second trial using HTTP/2](https://github.com/zackbart/connecta/blob/
|
|
492
|
+
A [second trial using HTTP/2](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-h2-2026-09-25.json)
|
|
377
493
|
repeated the twelve passing core cases and verified queued cancellation with
|
|
378
494
|
request signals enabled. Even on one HTTP/2 connection, the no-signal queue case
|
|
379
495
|
crossed isolates and remained inconclusive. The Node and workerd regression
|
|
380
496
|
suites cover expired waiters, orphaned handoffs, and identity-safe late release
|
|
381
497
|
deterministically.
|
|
382
498
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
499
|
+
The [historical probe source](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect.mjs)
|
|
500
|
+
and observations are retained in Git history, outside the current checkout and
|
|
501
|
+
npm package. The probe deployed a disposable Worker containing only synthetic
|
|
502
|
+
data, verified the served configuration, saved observations, and deleted the
|
|
503
|
+
Worker in `finally`. It used no `waitUntil`, which would change the lifetime
|
|
504
|
+
being measured. The production runtime build number was not exposed by the
|
|
505
|
+
deployment; these findings are tied to the date and flags above.
|
|
390
506
|
|
|
391
507
|
### Production Access bypass for agent endpoints
|
|
392
508
|
|
|
@@ -555,14 +671,62 @@ that refuses deletion does not block another Worker's deletion. It runs no real
|
|
|
555
671
|
hardening and the joint convergence are verified offline only and have not run
|
|
556
672
|
against Cloudflare.
|
|
557
673
|
|
|
674
|
+
### Alchemy Worker deployment
|
|
675
|
+
|
|
676
|
+
Two throwaway deployments on 2026-10-20 verified the Worker example's
|
|
677
|
+
Alchemy path with alchemy 2.0.0-beta.83, effect 4.0.5, and Alchemy local
|
|
678
|
+
state, then deleted every resource and confirmed by re-listing. Alchemy
|
|
679
|
+
uploads the full Worker with `PUT /workers/scripts/<name>`, the endpoint an
|
|
680
|
+
earlier full deploy had been suspected of losing `ctx.access` through.
|
|
681
|
+
|
|
682
|
+
The first deployment ran a near copy of the stack with a temporary diagnostic
|
|
683
|
+
route. With an Access service token in a non-identity policy, the Worker saw
|
|
684
|
+
`ctx.access` carrying the Worker-level application's AUD and no identity, as
|
|
685
|
+
expected for a service token. D1, the Worker Loader, R2, and secret bindings
|
|
686
|
+
worked; `/agent/*` reached connecta through the bypass; `/ui/api/config`
|
|
687
|
+
returned 403 `Cloudflare Access human identity required`; and
|
|
688
|
+
`connecta doctor` passed with a `cta_` token issued inside the Worker. A code
|
|
689
|
+
update through `alchemy deploy` kept the script tag, `ctx.access`, and D1 rows.
|
|
690
|
+
Three consecutive probe rounds agreed.
|
|
691
|
+
|
|
692
|
+
The second deployment ran the shipped `alchemy.run.ts` from the packed package
|
|
693
|
+
in a separate project on Node 26, changing only resource names and the Access policy
|
|
694
|
+
(a service token). The edge checks in the example's "Verify after deploying"
|
|
695
|
+
held for three or more rounds after each deploy. `alchemy destroy` deleted the
|
|
696
|
+
Worker and both Access applications and orphaned the retained D1 database; the
|
|
697
|
+
next deploy adopted it by name with its rows. After deleting local state, a
|
|
698
|
+
deploy reused the Worker with the same script tag, and `applicationId` took
|
|
699
|
+
both Access applications over with unchanged AUDs and no duplicates. Removing
|
|
700
|
+
`applicationId` afterwards planned an in-place update, and a further deploy
|
|
701
|
+
planned no changes. The account API token needed only Workers Scripts Write,
|
|
702
|
+
D1 Write, Access: Apps and Policies Write, and Access: Organizations, Identity
|
|
703
|
+
Providers, and Groups Read.
|
|
704
|
+
|
|
705
|
+
Four findings shape the operator rules. Alchemy local state under
|
|
706
|
+
`.alchemy/state/` records secret binding values in plaintext. Alchemy sends no
|
|
707
|
+
`keep_bindings`, so each deploy deletes secrets set outside the stack.
|
|
708
|
+
`alchemy dev` does not provide `ctx.access` to a plain fetch Worker, so local
|
|
709
|
+
development stays on `wrangler dev` and its `access.dev` identity. D1 needs
|
|
710
|
+
`RemovalPolicy.retain()` so `alchemy destroy` cannot delete a deployment's
|
|
711
|
+
state. Not verified: `Cloudflare.state()` remote state, custom domains, and
|
|
712
|
+
taking over a Worker first deployed with Wrangler.
|
|
713
|
+
|
|
714
|
+
npm strips `.gitignore` from the package, so the copied Worker example's
|
|
715
|
+
ignore rules ship as `examples/worker/gitignore`, renamed by the README's copy
|
|
716
|
+
step. `test/deployment-shapes.node.test.ts` checks a copy of the source tree,
|
|
717
|
+
and `scripts/check-package.mjs` extracts the packed example with the README's
|
|
718
|
+
`tar` command; both confirm git leaves `.alchemy/`, `.env*`, `.dev.vars*`,
|
|
719
|
+
`.wrangler/`, and `node_modules/` untracked.
|
|
720
|
+
|
|
558
721
|
## Storage, credentials, and connectors
|
|
559
722
|
|
|
560
723
|
A deployment has one store. On Workers it is one D1 database
|
|
561
724
|
(`d1Storage(env.CONNECTA_DB)` from `@zackbart/connecta/d1`); on Node it is one
|
|
562
725
|
SQLite file (`sqliteStorage(path)` from `@zackbart/connecta/sqlite`, over the
|
|
563
726
|
built-in `node:sqlite`). Both are the same SQL key-value store
|
|
564
|
-
(`src/storage/sql.ts`) over a
|
|
565
|
-
unchanged on each, and the same module carries the
|
|
727
|
+
(`src/storage/sql-native.ts`) over a native Effect SQL execution service, so
|
|
728
|
+
every statement runs unchanged on each, and the same module carries the
|
|
729
|
+
activity table beside it. `src/storage/sql.ts` owns the Promise edges.
|
|
566
730
|
Each store creates its tables on first use. `memoryStorage()` is the default
|
|
567
731
|
and the test double. Workers KV and the 0.28 JSON file store are gone: KV is
|
|
568
732
|
eventually consistent and cannot compare-and-set, and the file store rewrote
|
|
@@ -596,7 +760,7 @@ is one statement, which SQLite executes atomically and D1 serializes on its
|
|
|
596
760
|
primary. Everything that must claim a key exactly once relies on it with no
|
|
597
761
|
read-then-write fallback: agent-token slot swaps and
|
|
598
762
|
adoption, OAuth handoff ownership, single-use connect links, every downstream
|
|
599
|
-
OAuth grant write and consent claim, and the
|
|
763
|
+
OAuth grant write and consent claim, the result stash, and initialization of the cursor signing key.
|
|
600
764
|
|
|
601
765
|
Every key is built in `src/storage/keys.ts`, which lists each family with its
|
|
602
766
|
scope, version, codec, and TTL policy; `test/storage-keys.node.test.ts` fails when
|
|
@@ -782,17 +946,186 @@ auth headers, raw confidential OAuth client IDs and secrets, and outbound
|
|
|
782
946
|
bearer tokens join the set, including tokens rotated during a call. Token and
|
|
783
947
|
revocation requests register client credentials before dispatch, decoding OAuth
|
|
784
948
|
Basic form components so a raw echo is covered too. Public client IDs remain
|
|
785
|
-
visible.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
949
|
+
visible. Redaction replaces these values and their auth prefixes, mixed JSON
|
|
950
|
+
escapes, URL encodings, and base64/base64url forms with `[redacted]`.
|
|
951
|
+
|
|
952
|
+
Every redaction is one pass over a text projection of the whole value: string
|
|
953
|
+
and number leaves in serialization order, nested JSON strings parsed into their
|
|
954
|
+
own leaves, and errors' own fields (non-enumerable diagnostics and causes
|
|
955
|
+
included). The joined projection is matched once, raw and through each JSON
|
|
956
|
+
decoding layer, so a form split across adjacent leaves is found; each match is
|
|
957
|
+
replaced only in the leaves it touches, so public runs, links, annotations and
|
|
958
|
+
container shapes stay. The walk reads every field once, from its descriptor, and
|
|
959
|
+
returns what it checked. A caller's container stands only as verified plain
|
|
960
|
+
data: prototype `Object.prototype`, `Array.prototype` or `null`, no `toJSON` on
|
|
961
|
+
its chain, no symbol-keyed field, every field a data property it read (an
|
|
962
|
+
array's holes read through its prototypes), and nothing in it changed. Anything
|
|
963
|
+
else is a copy of writable, configurable data properties, so a frozen input
|
|
964
|
+
never throws, and every value returned is JSON data, a fresh built-in
|
|
965
|
+
`Uint8Array`, or an error copy: an error always, as its own class, with its
|
|
966
|
+
stack and every accessor its class defines read once into checked fields, its
|
|
967
|
+
`toJSON` shadowed unread and no function in a field; an object of another class
|
|
968
|
+
as plain data; an array that is not plain as a plain array of its indices; a
|
|
969
|
+
`String`, `Number` or `Boolean` wrapper as its primitive; a `toJSON` result,
|
|
970
|
+
called once and checked; a function or symbol as the placeholder; a typed array,
|
|
971
|
+
`DataView` or `ArrayBuffer` as a fresh `Uint8Array` of its bytes, which are
|
|
972
|
+
checked as bytes and, with the elements it held, as the numbers JSON shows for
|
|
973
|
+
them. The type table is in [auth.md](./auth.md). An own accessor is
|
|
974
|
+
never invoked: an object's copy leaves it out, an array index holds the
|
|
975
|
+
placeholder. A change copies the containers on its path on first write, so a
|
|
976
|
+
caller's value is never mutated. An array's non-index fields are never read:
|
|
977
|
+
JSON, which every exit and executor marshals with, never serializes them.
|
|
978
|
+
Facts kept beside a value by identity (failure facts, classifications, statuses,
|
|
979
|
+
Google's reasons and outcomes) register with `redaction-identity.ts` and follow
|
|
980
|
+
its copies, arrays' included.
|
|
981
|
+
One checker, `checked()`, matches every output-bound string under the whole
|
|
982
|
+
lattice: the projection's leaves as one lane, and alone each object key,
|
|
983
|
+
scalar, cursor claim, encoding Connecta produces and structural field;
|
|
984
|
+
provider lanes and bounded reads call it too. Its matching steps run nowhere
|
|
985
|
+
else. Only fixed
|
|
986
|
+
words are kept whatever was sent: envelope keys, MCP and JSON-RPC member names
|
|
987
|
+
and tag values, booleans under outcome names, and on an error its
|
|
988
|
+
classification code and a name its class is registered with in
|
|
989
|
+
`redaction-identity.ts` (the built-in errors, `DOMException`, and Connecta's
|
|
990
|
+
and the pinned MCP SDK's own classes, each with literals beside the class); a
|
|
991
|
+
number or string under any name is matched like any other, and any other error
|
|
992
|
+
name is a projection leaf like any field, joined with its neighbours, and
|
|
993
|
+
withheld whole as `[redacted]` when the lane changes any part of it, keeping
|
|
994
|
+
the copy's class. An accessor an error's chain defines is held on its copy under its
|
|
995
|
+
checked name. Exact relay states and connect links
|
|
996
|
+
Connecta mints and authenticates are kept whole: they hold only sealed state or
|
|
997
|
+
Connecta's own facts. Serialized JSON whose keys, syntax and leaves together
|
|
998
|
+
hold a run of a form that no single leaf holds is withheld whole.
|
|
999
|
+
|
|
1000
|
+
The guarantee: **no caller-visible text holds a registered form whole, nor any
|
|
1001
|
+
run of K = 8 consecutive UTF-16 units of a registered form of a value at least K
|
|
1002
|
+
long, in the raw text or any JSON-decoded layer; text cut where the cut can end
|
|
1003
|
+
inside a form loses that unfinished tail, however short.** Registered forms are
|
|
1004
|
+
the raw value, its URL/form encodings (either percent hex case), base64 and
|
|
1005
|
+
base64url, and the `Bearer `/`token ` framings. The framing words are public, so
|
|
1006
|
+
no indexed run starts inside them; a run reaching K units of the value is
|
|
1007
|
+
indexed from the value's own forms, and an echo carrying the framing is
|
|
1008
|
+
replaced with it. A private value of one to three units is matched only as a
|
|
1009
|
+
whole field value (a leaf, key, number, boolean or decoded view equal to one of
|
|
1010
|
+
its forms); one of four to seven units also as a substring of payload text,
|
|
1011
|
+
numbers, keys and decoded views; one of K or more also by every K-unit run.
|
|
1012
|
+
Pieces shorter than K are found only where they are adjacent: in the joined
|
|
1013
|
+
projection or one provider lane (one Slides shape's text runs, one Docs document). Non-adjacent pieces shorter than K, such as
|
|
1014
|
+
two seven-unit halves of a value in two Slides shapes, survive: they are
|
|
1015
|
+
indistinguishable from ordinary text without redacting ordinary words. Runs of
|
|
1016
|
+
K-1 units survive away from a cut. Values derived by an unregistered encoding
|
|
1017
|
+
(a date from a timestamp, say) are outside the guarantee, as is a later read of
|
|
1018
|
+
a Proxy, which only in-process code can build.
|
|
1019
|
+
|
|
1020
|
+
Payload passes run where downstream or guest data enters: the connector's
|
|
1021
|
+
result and errors, the invocation's raw reply, executor outcomes and logs,
|
|
1022
|
+
emits, and resource and skill reads. The agent boundary (`redactAgentOutput`,
|
|
1023
|
+
every meta-tool exit and the MCP route) repeats every check except short
|
|
1024
|
+
private values, which would rewrite Connecta's own ids, counts and addresses;
|
|
1025
|
+
payload passes already removed them. Identifiers sent beside a credential — a
|
|
1026
|
+
confidential client's ID, a Basic username beside a password of at least eight
|
|
1027
|
+
characters, and credential-slot fields named as identifiers (`…Id`, `user`,
|
|
1028
|
+
`username`, `login`, `email`, `public…`) — are matched whole only: consent URLs
|
|
1029
|
+
and vendor metadata carry their parts by design. Continuation cursors sent as
|
|
1030
|
+
query parameters (`pageToken`, `next_token`, `continuationToken`, `syncToken`)
|
|
1031
|
+
are public result state and are not registered. Header values a connector
|
|
1032
|
+
adds to token requests are registered under any name, except values equal to
|
|
1033
|
+
Connecta's own public ones (`Accept: application/json` and maintained
|
|
1034
|
+
providers' media types, in `PUBLIC_TOKEN_REQUEST_HEADERS`).
|
|
1035
|
+
|
|
1036
|
+
K = 8 equals the credential floor, so exactly the short private values are
|
|
1037
|
+
whole-only. It keeps ordinary words, dates and counts out of the index: a public
|
|
1038
|
+
run must repeat eight consecutive units of one form to match, so high-entropy
|
|
1039
|
+
credentials and private values produce no measured false positives (a 200 KB
|
|
1040
|
+
corpus of documents, logs, JSON and URLs is unchanged). Values that embed
|
|
1041
|
+
ordinary text do suppress it wherever it recurs within the same request: a
|
|
1042
|
+
password holding the word `resource`, a token holding a vendor's name, a
|
|
1043
|
+
credential that is a URL, or a PEM key's armor. Base64 forms reach K at six
|
|
1044
|
+
bytes of the value. The index stores two independent 32-bit polynomial hashes
|
|
1045
|
+
per run (at most 524,288 distinct runs, a table at most half full), so a false
|
|
1046
|
+
match is vanishingly rare and only redacts more; index and scan are linear in
|
|
1047
|
+
the registered forms and the scanned text, whatever the number of values. A
|
|
1048
|
+
64 Ki-bit bitmap of the runs' first hashes rejects nearly every text position
|
|
1049
|
+
in one load, so only a candidate computes its second hash. A pass over a large
|
|
1050
|
+
body costs about what the per-leaf matcher it replaced did
|
|
1051
|
+
(`scripts/bench-sent-secrets.mjs` compares builds); declared binary content
|
|
1052
|
+
costs more, since it is decoded and checked as bytes as well as its base64 text.
|
|
1053
|
+
|
|
1054
|
+
Unchecked content is withheld rather than weakening the guarantee: past the
|
|
1055
|
+
index's capacity, or under a private value too large to register, every leaf of
|
|
1056
|
+
K units or more and every run of adjacent shorter leaves reaching K; a cycle, an
|
|
1057
|
+
accessor, a container past depth 512, and a container past the node budget,
|
|
1058
|
+
withheld whole and never serialized. Leaves too large to match joined within the call's work budget are
|
|
1059
|
+
matched alone, in order, withholding one that does not fit, and every seam
|
|
1060
|
+
between kept leaves is matched 512 units deep on each side. Text that still
|
|
1061
|
+
holds escapes after eight decoding layers withholds the leaves holding them.
|
|
1062
|
+
|
|
1063
|
+
Declared binary content (Drive `format: "base64"` content, MCP image/audio
|
|
1064
|
+
`data`, resource `blob`, and a REST body's `base64` beside its `contentType`)
|
|
1065
|
+
is decoded and checked as bytes, as UTF-8 when a registered form is not ASCII,
|
|
1066
|
+
as its base64 text, and through any base64 the bytes carry, so an echo at any
|
|
1067
|
+
byte alignment is found. Decoded views take payload text's short-value rules. A
|
|
1068
|
+
match withholds the whole field as the base64 of `[redacted]`, never partially
|
|
1069
|
+
scrubbed bytes; content that is not valid base64 as it stands still gets every
|
|
1070
|
+
text view. Base64 or base64url in any string, key or declared field is decoded
|
|
1071
|
+
in each of its text views: raw, one layer of JSON escapes decoded, percent
|
|
1072
|
+
escapes decoded, and the form decoding (a `+` as a space, generated whenever
|
|
1073
|
+
a `+` appears). A view that
|
|
1074
|
+
is wholly base64 is decoded from six units; a run inside one from six units
|
|
1075
|
+
when payload rules apply and a four-to-seven-unit private value is registered,
|
|
1076
|
+
otherwise from eleven. A run of L units decodes to floor(3L/4) bytes, so a piece of
|
|
1077
|
+
p bytes at alignment a needs L >= ceil(4(a + p)/3), least at a = 0: the smallest
|
|
1078
|
+
piece matched is a short value's four bytes (6) or a K-unit run's eight (11).
|
|
1079
|
+
Decoded content is searched for further base64 three encodings deep. The
|
|
1080
|
+
decoded views are matched together as one lane, each run decoded once per call
|
|
1081
|
+
however many leaves repeat it, and a match only decoding reveals withholds the
|
|
1082
|
+
leaf as `[redacted]`. Every decode is charged to the call's work budget, and a
|
|
1083
|
+
leaf it cannot check is withheld. Two text decodes chained, base64 glued to
|
|
1084
|
+
preceding alphabet units with no separator, HTML entities, compression and
|
|
1085
|
+
custom alphabets are outside the guarantee (`auth.md`, INV-5 output redaction
|
|
1086
|
+
limits). Content found clean at least 4,096 units long is remembered for the
|
|
1087
|
+
request until its set changes, so later passes do not decode it again.
|
|
1088
|
+
|
|
1089
|
+
Every base64 or base64url encoding Connecta returns goes through
|
|
1090
|
+
`encodeForAgent`: Drive and Gmail downloads, REST bodies, and every page
|
|
1091
|
+
cursor. It checks the content before encoding and encodes `[redacted]` instead
|
|
1092
|
+
on a match. Cursors are built from already-redacted values, not sealed: a
|
|
1093
|
+
provider has no deployment key, and a sealed vendor token would still be the
|
|
1094
|
+
vendor's text. A vendor page token is redacted where it is placed, so one that
|
|
1095
|
+
echoes a sent value fails on the next page instead of carrying it; a revision or
|
|
1096
|
+
id the cursor only compares becomes its digest when it holds a sent value
|
|
1097
|
+
(Slides states, Forms revisions and anchors), and a vendor page id a cursor
|
|
1098
|
+
names (Slides slides and notes pages) is redacted. Before encoding, every
|
|
1099
|
+
string in a cursor's claims is checked under payload rules, since each
|
|
1100
|
+
downstream value a cursor holds is a string, and its whole text and encoding
|
|
1101
|
+
under the boundary's, so Connecta's own keys and counts never meet short
|
|
1102
|
+
private values. A cursor checked so is remembered as clean; one that holds a
|
|
1103
|
+
match encodes `[redacted]`.
|
|
1104
|
+
|
|
1105
|
+
Text Connecta or a provider cuts short, trims, normalizes or extracts from is
|
|
1106
|
+
redacted whole against the request's set, private arguments included, first
|
|
1107
|
+
(`redactBeforeCut`, and `clampText` in the Workspace products): a cut leaves a
|
|
1108
|
+
remnant shorter than K that no later pass can see, and a normalized number may
|
|
1109
|
+
no longer hold the form at all (Drive size strings, Breeze amounts and dates).
|
|
1110
|
+
Provider text lanes that the projection does not see as adjacent are matched
|
|
1111
|
+
joined (`SentSecrets.parts`): Docs text runs across links and paragraphs, with
|
|
1112
|
+
index previews checked again after whitespace collapses; and Slides raw
|
|
1113
|
+
text runs, which carry style between them. Text read only up to a bound, or that
|
|
1114
|
+
a vendor stopped listing, also loses any suffix that begins a registered form
|
|
1115
|
+
(`redactBoundedRead`). Complete forms and open-tail prefixes are matched against
|
|
1116
|
+
the original text and merged before replacement. Each decoded layer sets aside
|
|
1117
|
+
its own incomplete trailing JSON escape, so a later layer cannot conceal an open
|
|
1118
|
+
tail. Only the uncertain suffix changes; checked earlier rich-text runs retain
|
|
1119
|
+
their links and annotations. Text-offset cursors bind the redacted
|
|
1120
|
+
representation as well as the vendor revision; changed secret sets that change
|
|
1121
|
+
that representation refuse continuation with a restart instruction.
|
|
1122
|
+
|
|
1123
|
+
Final transports register sensitive headers and query values after assembling
|
|
1124
|
+
the request; custom API handlers use `ctx.fetch`. Values shorter than eight
|
|
790
1125
|
characters do not enter the matcher, including Basic usernames, passwords
|
|
791
1126
|
and explicitly secret fields such as `client_secret`. Configured short
|
|
792
1127
|
credentials produce one payload-free warning per connector; use secrets of
|
|
793
|
-
at least eight characters.
|
|
794
|
-
credentials follow the same redaction rules. Connecta's own messages
|
|
795
|
-
never quote a credential.
|
|
1128
|
+
at least eight characters. Connecta's own messages never quote a credential.
|
|
796
1129
|
Core uncertainty and retry envelopes share one schema-filtered argument snapshot,
|
|
797
1130
|
captured before dispatch and byte budgeting. `writeOnly: true` omits values of
|
|
798
1131
|
every length, including nested properties, array items and prefixes, local
|
|
@@ -832,7 +1165,11 @@ including titles, descriptions, schemas and annotations. If a tool name would
|
|
|
832
1165
|
change, the complete catalog is refused. Rewriting a name changes dispatch;
|
|
833
1166
|
dropping only that tool would publish a partial catalog, against INV-8. Later
|
|
834
1167
|
cached reads need no credential set because the stored facts are already clean.
|
|
835
|
-
Echoed sensitive header lines are also withheld.
|
|
1168
|
+
Echoed sensitive header lines are also withheld. They are read on the text
|
|
1169
|
+
before the echo is replaced and merged into its spans, so a private value that
|
|
1170
|
+
takes part of a header's name, its `:` or a credential prefix (`Bearer `) cannot
|
|
1171
|
+
hide the header: the whole value goes, every cookie pair and any short tail
|
|
1172
|
+
with it (#703). A line that is not a sensitive header keeps its text. The set is never persisted
|
|
836
1173
|
or logged. Anything else (a transport, parser, stream, validator, or
|
|
837
1174
|
runtime error) reaches it in connecta's words: step, origin, HTTP status, and
|
|
838
1175
|
class, classified as the original would have been ([auth](./auth.md#what-a-servers-errors-may-say)).
|
|
@@ -855,7 +1192,10 @@ operator page may show catalog metadata the operator loaded (descriptions,
|
|
|
855
1192
|
schemas), but withholds a tool name outside the grammar as it does in records.
|
|
856
1193
|
`test/operator-record-sources.node.test.ts` is a secondary lint over every other
|
|
857
1194
|
log call in `src/`. Fix the sink, not the source: a filter at each source missed
|
|
858
|
-
the next one.
|
|
1195
|
+
the next one. Recorded HTTP status facts survive the bare write-401 fallback,
|
|
1196
|
+
Google's uncertain-write wording, and oversized-response mapping; see
|
|
1197
|
+
[Read failure records](./operating.md#read-failure-records) for their operator
|
|
1198
|
+
meaning. These facts do not change agent error fields or write outcomes.
|
|
859
1199
|
|
|
860
1200
|
## Configuration
|
|
861
1201
|
|
|
@@ -888,10 +1228,9 @@ factory) walk their own options the same way, against shapes the compiler
|
|
|
888
1228
|
checks against their option types (`optionsOf<T>()`). Provider definitions
|
|
889
1229
|
declare their closed `options` shape; `defineProvider()` validates it by the
|
|
890
1230
|
same descriptor walk before `create` reads a value and stamps the definition
|
|
891
|
-
name onto `describe().source.provider`.
|
|
892
|
-
|
|
893
|
-
connectors remain opaque
|
|
894
|
-
registry validates. Checks
|
|
1231
|
+
name onto `describe().source.provider`. Hosted MCP uses generic runtime discovery;
|
|
1232
|
+
`classify` is rejected at construction in favor of `ConnectaConfig.classification`.
|
|
1233
|
+
Custom connectors remain opaque and classify from annotations. Checks
|
|
895
1234
|
that need the connector set — pool members, exact classification overrides, a
|
|
896
1235
|
connector's own `maxResultBytes` — throw from `src/index.ts` and the registry
|
|
897
1236
|
under the same policy. `storage` stays opaque, but its check requires `list`
|
|
@@ -917,91 +1256,175 @@ switched by the environment, and keep their entries under 30 lines.
|
|
|
917
1256
|
|
|
918
1257
|
### Providers and reviewed classification
|
|
919
1258
|
|
|
920
|
-
Infisical separates explicit value reads from identifier results. `get_secret`
|
|
921
|
-
and `list_secrets` with `includeValues: true` return secret values and comments.
|
|
922
|
-
All other results, including imported secrets and pending approvals, allow only
|
|
923
|
-
checked IDs, keys, environment and tag slugs, paths, timestamps, positive versions
|
|
924
|
-
and enum fields. They omit project slugs derived from free-text names, comments,
|
|
925
|
-
descriptions, free-form names, reminder notes and arbitrary nested objects.
|
|
926
|
-
`metadataOmitted: true` reports withheld or unavailable metadata.
|
|
927
|
-
This assumes an honest server and protects against careless human entry in free
|
|
928
|
-
text. A malicious server encoding values into valid identifiers is out of scope.
|
|
929
|
-
Known, accepted residual risk: environment slugs are returned as required
|
|
930
|
-
routing identifiers. Infisical's UI derives them from environment names by
|
|
931
|
-
default, so secrets entered in those names can appear in returned slugs.
|
|
932
|
-
Operators must not put secrets in environment names or slugs. Secrets placed in
|
|
933
|
-
identifiers or key names are out of scope.
|
|
934
|
-
Write input schemas mark secret values, secret comments and folder descriptions
|
|
935
|
-
as `writeOnly`, so recovery argument echoes omit them without changing dispatch.
|
|
936
|
-
Submitted values never join the request's sent-credential set, preserving later
|
|
937
|
-
program arguments. Client credentials and tokens still join that set on every
|
|
938
|
-
call. The [provider guide](https://github.com/zackbart/connecta/blob/main/src/providers/infisical/SKILL.md) owns routing and
|
|
939
|
-
value-read conventions.
|
|
940
|
-
|
|
941
1259
|
A maintained provider is one `defineProvider()` call (`src/provider.ts`): a
|
|
942
|
-
name, title, kind
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
`
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1260
|
+
name, title, `kind: "api"`, maintained skill, closed `options` shape and a
|
|
1261
|
+
synchronous `create`. Common values are checked before construction and the
|
|
1262
|
+
factory stamps its provider name onto `describe().source.provider`. API
|
|
1263
|
+
implementations and their contract evidence remain vendored. Hosted MCP servers
|
|
1264
|
+
use generic `remoteMcp()` entries; provider reviews and hosted defaults are
|
|
1265
|
+
removed. Legacy hosted selectors fail at construction with the generic migration
|
|
1266
|
+
path. GitHub retains its scope-enforced App identity and exposes a bounded repository-read and release-write hatch plus local scope discovery, scoped data search and guarded merge.
|
|
1267
|
+
|
|
1268
|
+
[Decision 0011](https://github.com/zackbart/connecta/blob/main/decisions/0011-provider-standard.md)
|
|
1269
|
+
records the provider standard now implemented by all 15 maintained providers. It fixes
|
|
1270
|
+
one folder layout per kind and fixed evidence names. `drift.json` gets a
|
|
1271
|
+
generated block and the guide a generated Conventions section, and every
|
|
1272
|
+
`api()` surface gets one agent-facing shape. Providers completed two migration
|
|
1273
|
+
steps: onto the kit with unchanged output, then onto the standard surface.
|
|
1274
|
+
Both `PENDING.step1` and `PENDING.step2` in `test/provider-standard.ts` are empty;
|
|
1275
|
+
every maintained provider now meets the standard without a migration exemption.
|
|
1276
|
+
`test/provider-standard.node.test.ts` holds every maintained provider folder to the
|
|
1277
|
+
standard, and `test/provider-conventions.test.ts` checks the surface rules on
|
|
1278
|
+
Node. `providers:generate` writes the standard's derived parts and
|
|
1279
|
+
refuses a provider past step 1 that breaks the `drift.json` rules, or one past
|
|
1280
|
+
step 2 that breaks the guide rules: `drift.json` version 2's `generated` block
|
|
1281
|
+
(operations from the kit's `ProviderKitFacts` and evidence digests), and a guide's `## Conventions`, rendered
|
|
1282
|
+
from the definition's declarations into `skill.generated.ts`.
|
|
1283
|
+
The generator requires `fixtures.ts` to export `createAuthored()` with a
|
|
1284
|
+
nonempty offline `staticTools` catalog. Its input/output schemas derive one
|
|
1285
|
+
paging convention, including `get_*`, hatch search and details, excluding
|
|
1286
|
+
tools whose `page.hasMore` is always false. The convention distinguishes
|
|
1287
|
+
top-level `cursor` from `query[page.param]` or `body[page.param]` continuation
|
|
1288
|
+
and tells agents to return `page.nextCursor` before the call that would exceed
|
|
1289
|
+
the per-program call limit, then continue in the next program. The Hatch line
|
|
1290
|
+
reports a nonzero explicit refusal count with singular/plural wording and
|
|
1291
|
+
points to the actual prefixed details tool with method/path lookup; it never
|
|
1292
|
+
embeds the refusal table. Unreviewed operations remain refused.
|
|
1293
|
+
`ProviderContext.usageGuide` selects a guide's `<!-- when: <key> -->` blocks
|
|
1294
|
+
with the definition's `blocks`. `providers:check` reads both record versions,
|
|
1295
|
+
reports a manifest operation no endpoint row records at the provider's level,
|
|
1296
|
+
and lists manual checks last verified more than 180 days ago. Its offline
|
|
1297
|
+
record validator, `scripts/drift-validate.mjs`, also gates `providers:generate`
|
|
1298
|
+
and the standard suite, so a record they accept is one `providers:check` reads.
|
|
1299
|
+
|
|
1300
|
+
Supplemental vendored contracts use a simple `<slug>.json` pin and a sibling
|
|
1301
|
+
`<slug>.document.json` in the provider's `evidence/` directory. An `endpoints`
|
|
1302
|
+
check names the pin with `specification.pin`; both files are hashed in
|
|
1303
|
+
`generated.evidence`. Slugs use lowercase letters, digits and hyphens, start
|
|
1304
|
+
with a letter, and cannot be `openapi` or `value-safety`; paths, dots and
|
|
1305
|
+
reserved primary evidence names are refused. The pin requires https `url`
|
|
1306
|
+
and `documentation`, `vendored: true`, and the exact document `digest`.
|
|
1307
|
+
The checker reads that document offline from this provider's evidence only,
|
|
1308
|
+
checks its digest, and never fetches the pin's URL. Keep dataset semantics
|
|
1309
|
+
that the envelope does not describe in a separate manual check.
|
|
1310
|
+
|
|
1311
|
+
A provider whose public API is split across documents may declare
|
|
1312
|
+
`evidence/openapi.json` as `{ revision, documents: [{ pin, prefix }], options? }`.
|
|
1313
|
+
Each `pin` names a simple supplemental sibling pin and byte-exact document;
|
|
1314
|
+
`prefix` is its literal API path and must match that document's one server.
|
|
1315
|
+
All documents must share an HTTPS origin. A composition has no `url`, `digest`,
|
|
1316
|
+
`latest`, or `vendored` field of its own: its actual source URLs and composite
|
|
1317
|
+
digest are derived from those pins. `providers:spec` namespaces local component
|
|
1318
|
+
references, prefixes paths, omits empty source paths that establish no route,
|
|
1319
|
+
refuses collisions, and compiles the combined
|
|
1320
|
+
contract through the same compiler. Missing operation summaries inherit the
|
|
1321
|
+
path item's resource description with a method-specific action. Both the source
|
|
1322
|
+
pin bytes and normalized composed document are checked for offline freshness.
|
|
1323
|
+
A composition moves only by explicit review of its source pins/documents;
|
|
1324
|
+
`--record` and `--file` cannot replace it. `providers:check` compares the actual
|
|
1325
|
+
public documents independently; it never fetches a synthetic combined URL.
|
|
1326
|
+
|
|
1327
|
+
The indexed hatch (`src/http/hatch.ts`) serves
|
|
973
1328
|
`<vendor>_api_search`, `_api_details`, `_api_read`, and `_api_write` over an
|
|
974
1329
|
`OperationIndex` that `pnpm run providers:spec` compiles offline from the
|
|
975
|
-
provider's
|
|
1330
|
+
provider's pin (`evidence/openapi.json`)
|
|
1331
|
+
into `openapi.generated.ts`. A `vendored: true` pin compiles offline from
|
|
1332
|
+
the reviewed conversion in `evidence/openapi.document.json`, or derives a
|
|
1333
|
+
deterministic Discovery conversion from its pinned original as described below.
|
|
1334
|
+
The generated header records the converted document's digest beside the pin's,
|
|
1335
|
+
so offline freshness sees a changed conversion.
|
|
976
1336
|
Each call is matched to an operation and checked against its parameters before
|
|
977
|
-
transport
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1337
|
+
transport. Resolved path parameters are validated before authentication or dispatch,
|
|
1338
|
+
including pinned enums and numeric bounds. Number path values must decode to canonical finite ASCII decimals without
|
|
1339
|
+
exponents, hexadecimal, a leading plus or whitespace. Integer path values must decode to
|
|
1340
|
+
canonical ASCII decimal safe integers: `0`, positive decimals without leading
|
|
1341
|
+
zeros, or negative decimals without leading zeros. `-0`, leading `+`, whitespace,
|
|
1342
|
+
exponents, decimal points and non-ASCII digits are refused. Negative integers
|
|
1343
|
+
remain valid when the pin has no minimum excluding them. String-valued ids keep
|
|
1344
|
+
their declared string contract. `_api_read` admits only operations with explicit
|
|
1345
|
+
reviewed read verdicts, including GET/HEAD and read-only POSTs where declared;
|
|
1346
|
+
classification is per tool name. Providers declare a confined kit connection,
|
|
1347
|
+
a value-safety table (required;
|
|
1348
|
+
see below), and any framing, scope, paging or refusal hooks through the hatch
|
|
1349
|
+
binding. The connection owns authentication, transport, failure mapping and
|
|
1350
|
+
result safety. An index's `slashParams` can admit an encoded `/` in a reviewed
|
|
1351
|
+
final key segment. Current hatch admission, paging, privacy and one-send
|
|
1352
|
+
outcomes are exercised by the required `eval:outcomes` tasks against stateful
|
|
1353
|
+
vendors and HTTP MCP deployments. `test/provider-hostile-response.node.test.ts`
|
|
1354
|
+
commits real HTTP writes before returning HTML, malformed JSON or an oversized
|
|
1355
|
+
acknowledgement, then reconciles vendor state with exactly one physical send.
|
|
1356
|
+
`test/provider-privacy-journey.node.test.ts` checks credential echoes over HTTP.
|
|
1357
|
+
Focused provider suites retain MIME/draft restrictions, revision conflicts,
|
|
1358
|
+
CCB routing, Planning Center product exclusions and deployment pins. Native
|
|
1359
|
+
HTTP framing, cursor and request-lifetime fault probes remain in the HTTP suites;
|
|
1360
|
+
catalog copies and decoder/adapter parity matrices are retired under #1072.
|
|
1361
|
+
No legacy REST tool adapter remains.
|
|
987
1362
|
Large documents opt into generator options that drop restated operation ids
|
|
988
1363
|
and plain path parameters and cap one operation's details (`opBudget`).
|
|
1364
|
+
`numericBounds: true` preserves documented numeric minimum/maximum constraints
|
|
1365
|
+
in request schemas and enforces them before dispatch, including numeric query
|
|
1366
|
+
strings. Existing indexes without those optional constraints are unchanged.
|
|
1367
|
+
The compiler separately records response property paths from the lowest
|
|
1368
|
+
declared JSON success status (then `2XX`), resolving local references and
|
|
1369
|
+
composition. It includes top-level and list-item properties (root arrays or
|
|
1370
|
+
an envelope's immediate arrays), and shares paths and rows in a lazy `OpenApiData.fields`
|
|
1371
|
+
JSON string. Traversal stops at 1,024 schema visits, 24 reference/composition
|
|
1372
|
+
levels, 256 paths or 200 characters per path; overflow or an unresolved
|
|
1373
|
+
reference omits that operation's whole response-field contract. Explicitly open
|
|
1374
|
+
roots (including root-array items) also leave fields absent. Empty,
|
|
1375
|
+
schemaless and non-JSON responses declare no fields. Request shrinking and
|
|
1376
|
+
endpoint contract digests do not depend on this metadata.
|
|
1377
|
+
Fields remain optional additive metadata, so a previously compiled
|
|
1378
|
+
index without the source bytes remains usable without inventing a response
|
|
1379
|
+
contract. Vercel's step-2 pin review restores response metadata from the reviewed public
|
|
1380
|
+
document; operations without declared response properties still refuse fields. Breeze's pin declares no response
|
|
1381
|
+
properties. GitHub's immutable official OpenAPI pin supplies its complete index and response fields; a reviewed repository allowlist controls dispatch. Natural queries with reviewed matches scope discovery to those matches and named-only operations; explicit paths and unmatched queries retain full-pin refusal discovery.
|
|
989
1382
|
`check:providers-generated` checks each index header against its source record
|
|
990
1383
|
offline; `providers:check` reports a pin that differs from the live document.
|
|
991
1384
|
No maintained provider selects an implementation with `surface` any more. See
|
|
992
1385
|
the
|
|
993
|
-
[provider auth migration](./provider-auth-migration.md).
|
|
994
|
-
|
|
995
|
-
|
|
1386
|
+
[provider auth migration](./provider-auth-migration.md).
|
|
1387
|
+
|
|
1388
|
+
#### Generated index storage (contributor contract)
|
|
1389
|
+
|
|
1390
|
+
Generated format 5 stores the same logical `OpenApiData` as earlier formats.
|
|
1391
|
+
The shared renderer interns complete parameter rows by their exact JSON text,
|
|
1392
|
+
including descriptions, required flags and compact schema references. Each
|
|
1393
|
+
operation records parameter-row ids and its unchanged serialized body. Shared
|
|
1394
|
+
schema definitions, response fields, lossless request graphs, operation order,
|
|
1395
|
+
pins and all compiler shrink options retain their existing meaning. The two
|
|
1396
|
+
indexes derived directly from kit declarations continue using their plain
|
|
1397
|
+
logical representation.
|
|
1398
|
+
|
|
1399
|
+
`compactOpenApi` exposes the exact original `details` string through an
|
|
1400
|
+
enumerable, replaceable property. `OperationIndex` parses definitions once per
|
|
1401
|
+
instance and decodes each operation row on first use. Each row occurrence gets
|
|
1402
|
+
its own parsed parameters; dictionary sharing introduces no new mutable object
|
|
1403
|
+
sharing between operations or instances. Existing shared schema references and
|
|
1404
|
+
their mutation behaviour remain intact. Ordinary logical indexes still use
|
|
1405
|
+
`JSON.parse`. Construction neither expands the complete details string nor
|
|
1406
|
+
computes an identity hash. Cursor binding continues hashing the same canonical
|
|
1407
|
+
logical data and policy, with no encoding metadata in its preimage, preserving
|
|
1408
|
+
existing cursor bytes for unchanged content.
|
|
1409
|
+
|
|
1410
|
+
The runtime codec uses no platform-specific imports, recursive references or
|
|
1411
|
+
compression dependency. Parameter ids must be nonnegative safe integers naming
|
|
1412
|
+
existing strings. Reconstructing a row or full details text is bounded to 64 Mi
|
|
1413
|
+
UTF-16 code units; full reconstruction must match its recorded exact length.
|
|
1414
|
+
These physical decode checks do not change request validation, schema traversal,
|
|
1415
|
+
search, refusal, result or discovery budgets. Generation and offline roundtrip
|
|
1416
|
+
tests must preserve every pinned logical contract; storage changes never accept
|
|
1417
|
+
new upstream evidence. The compaction suites cover exact strings,
|
|
1418
|
+
native Web Crypto identity, malformed references, lazy decoding, mutation
|
|
1419
|
+
semantics and every maintained provider.
|
|
996
1420
|
|
|
997
1421
|
#### Value safety for REST vendors
|
|
998
1422
|
|
|
999
1423
|
The bar ([decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)):
|
|
1000
1424
|
no REST tool response, success or error, returns a credential that grants
|
|
1001
1425
|
access to the vendor or a third party, or a stored secret value, unless a
|
|
1002
|
-
reviewed named tool exists to return it. The threat model
|
|
1003
|
-
|
|
1004
|
-
scope. Field-by-field redaction over a whole vendor API does not converge, so
|
|
1426
|
+
reviewed named tool exists to return it. The threat model assumes an honest server; secrets people type into free
|
|
1427
|
+
text are out of scope. Field-by-field redaction over a whole vendor API does not converge, so
|
|
1005
1428
|
every vendor ships one reviewed table through one mechanism
|
|
1006
1429
|
(`src/providers/_shared/rest/value-safety.ts`):
|
|
1007
1430
|
|
|
@@ -1012,12 +1435,14 @@ every vendor ships one reviewed table through one mechanism
|
|
|
1012
1435
|
signing, webhooks, drains, transfer, claim, and invite codes). It is the
|
|
1013
1436
|
union of the detectors Cloudflare and Vercel shipped first, and
|
|
1014
1437
|
deliberately over-inclusive. `providers:spec` writes
|
|
1015
|
-
`value-safety.candidates.json`
|
|
1438
|
+
`evidence/value-safety.json` (`value-safety.candidates.json` until the
|
|
1439
|
+
provider's step 1) when the source record sets
|
|
1016
1440
|
`options.valueSafety` (vendor `operationWords`; Stripe's `expansions: false`
|
|
1017
1441
|
skips id-or-object expansions, which are their own operations' candidates),
|
|
1018
1442
|
stamped with the pinned digest, the detector format, and its options. The
|
|
1019
1443
|
file is test evidence and never ships.
|
|
1020
|
-
2. **One reviewed verdict per candidate**, in the vendor's `
|
|
1444
|
+
2. **One reviewed verdict per candidate**, in the vendor's `safety.ts`
|
|
1445
|
+
(`value-safety.ts` until its step 1):
|
|
1021
1446
|
`refuse` (the generic tools refuse it before transport, naming the
|
|
1022
1447
|
operation and the reason), `redact` (reviewed field paths), or `safe`, each
|
|
1023
1448
|
with a reason. Paths are dot paths into the data a tool returns: a list on
|
|
@@ -1038,8 +1463,8 @@ every vendor ships one reviewed table through one mechanism
|
|
|
1038
1463
|
otherwise. `providers:spec` derives the binding fields from the pinned
|
|
1039
1464
|
schema, and the harness fails until the condition lists each one. Argument-dependent refusals (Stripe expansions,
|
|
1040
1465
|
Vercel `decrypt`) stay in the vendor's `refuse` hook.
|
|
1041
|
-
3. **Redaction on every success body**, in
|
|
1042
|
-
before cursors and `
|
|
1466
|
+
3. **Redaction on every success body**, in the kit connection after `result`
|
|
1467
|
+
and before cursors and `fields`, so named tools, HEAD data, and uploads pass
|
|
1043
1468
|
through it too: the operation's reviewed paths, then the key-name
|
|
1044
1469
|
heuristic as defense in depth. The heuristic is the union of the former
|
|
1045
1470
|
vendor engines: a credential word anywhere in a non-metadata name removes
|
|
@@ -1052,10 +1477,22 @@ every vendor ships one reviewed table through one mechanism
|
|
|
1052
1477
|
reviews the pinned spec's free-text fields for embedded vendor-minted links
|
|
1053
1478
|
and records its verdict and source limitations beside its value-safety table.
|
|
1054
1479
|
Stripe, Vercel, and Cloudflare use **reviewed paths plus global signing
|
|
1055
|
-
parameters**.
|
|
1480
|
+
parameters**. Known AWS SigV4/Google V4 `X-Amz-`/`X-Goog-` Signature,
|
|
1056
1481
|
Credential and Security-Token values are withheld across all decoded string
|
|
1057
|
-
values
|
|
1058
|
-
|
|
1482
|
+
values, including values written with decimal, hexadecimal or named HTML
|
|
1483
|
+
character references. References are read whole as value data; decoding
|
|
1484
|
+
only finds nested-reference boundaries and classifies a placeholder's
|
|
1485
|
+
remainder. No decoded replacement is returned. Possible named references
|
|
1486
|
+
are read fail-closed with their required semicolon and bounded HTML name
|
|
1487
|
+
length. Numeric references also accept an omitted semicolon, and `amp`
|
|
1488
|
+
may omit its semicolon immediately before `#`. Signing separators accept
|
|
1489
|
+
nested ampersand references around decimal, hexadecimal or named `=`
|
|
1490
|
+
references. Recognized name representations retain their source spelling
|
|
1491
|
+
and use the same fixed signing-name vocabulary and value boundaries.
|
|
1492
|
+
A literal query separator such as `&name=alice` still ends the signing
|
|
1493
|
+
value. Ordinary query strings and prose retain their original spelling.
|
|
1494
|
+
Generic credential names (`code`, `key`, `token`, `signature`, `sig`, etc.)
|
|
1495
|
+
and credential-looking fragments apply only to reviewed diagnostic
|
|
1059
1496
|
paths and vendor error text. Matching destinations lose their entire query
|
|
1060
1497
|
and fragment. The tables list exact operations/resource paths with evidence:
|
|
1061
1498
|
Stripe charge failure/outcome messages, Vercel deployment failures and
|
|
@@ -1064,6 +1501,9 @@ every vendor ships one reviewed table through one mechanism
|
|
|
1064
1501
|
invocation maps. The shared review harness rejects embedded-URL operation
|
|
1065
1502
|
keys absent from each vendor's pinned index and invalid path syntax.
|
|
1066
1503
|
The current Vercel pin was reviewed from its full response document; its unversioned URL does not retain older documents.
|
|
1504
|
+
Vercel project creation registers every submitted environment-variable value
|
|
1505
|
+
before dispatch, so echoes in response metadata, keys and prose are withheld
|
|
1506
|
+
before field selection while safe project and variable metadata remain.
|
|
1067
1507
|
Generic parameters in descriptions, metadata, nicknames, billing footers,
|
|
1068
1508
|
notes, DNS TXT, KV values and operator HTML retain their original bytes,
|
|
1069
1509
|
including whole URL strings. Ordinary code/key URLs can be false positives
|
|
@@ -1095,42 +1535,85 @@ every vendor ships one reviewed table through one mechanism
|
|
|
1095
1535
|
(decoded bytes are at most three quarters of encoded characters). Cloudflare
|
|
1096
1536
|
KV additionally enforces its documented 25 MiB decoded value limit;
|
|
1097
1537
|
malformed base64 and values beyond that limit refuse with `invalid_args`.
|
|
1098
|
-
The
|
|
1099
|
-
|
|
1538
|
+
The authenticated MCP `admission.requests.maxBodyBytes` cap and any smaller
|
|
1539
|
+
host cap still apply; response and raw-upload limits do not bound JSON fields.
|
|
1100
1540
|
JSON admission iteratively checks every key and value, with no separate
|
|
1101
1541
|
depth or node-count ceiling below the accepted request's size. Cyclic
|
|
1102
1542
|
direct arguments refuse with `invalid_args`; repeated non-cyclic references
|
|
1103
1543
|
are inspected. The scans remain linear in the supplied and decoded bytes.
|
|
1104
1544
|
Withheld values must not be written back; obtain the original
|
|
1105
|
-
link or omit the field.
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
domain or subdomain, or a reviewed S3 bucket, virtual-hosted or exactly
|
|
1115
|
-
the first path segment on an S3 endpoint. A secondary scheme or `//` in
|
|
1116
|
-
another URL's path or query cannot re-anchor it; those proxy links keep
|
|
1117
|
-
their bytes. Schemes and slashes may be escaped. File links lose their
|
|
1118
|
-
whole query and fragment from a literal, percent-, entity-, or
|
|
1119
|
-
`\u`-encoded opener through the next whitespace, quote, or angle bracket;
|
|
1120
|
-
trailing closing punctuation comes back. AWS SigV4 and Google V4
|
|
1121
|
-
`Signature`, `Credential`, and `Security-Token` parameter values are
|
|
1122
|
-
withheld wherever their literal signing names occur, at any host, with
|
|
1123
|
-
plain or encoded equals signs. `carriesWithheldLink(value, hosts)` is
|
|
1124
|
-
the shared write-admission helper: Notion refuses a write before dispatch
|
|
1125
|
-
when a file link's own query/fragment carries the exact `[redacted]`
|
|
1545
|
+
link or omit the field. This link scanner alone treats a marker outside a
|
|
1546
|
+
destination as ordinary text. Kit admission additionally refuses whole
|
|
1547
|
+
marker values and any marker containment in paths and queries; longer prose
|
|
1548
|
+
in body fields may mention a marker (see Contributor provider kit).
|
|
1549
|
+
Reviewed text paths withhold credential-bearing URL queries and fragments.
|
|
1550
|
+
AWS and Google signing parameter values are withheld at every host.
|
|
1551
|
+
`carriesWithheldLink(value)` is
|
|
1552
|
+
a shared write-admission helper that refuses a write before dispatch
|
|
1553
|
+
when a link's own query/fragment carries the exact `[redacted]`
|
|
1126
1554
|
placeholder, plain or up to four layers encoded. Admission resumes at
|
|
1127
1555
|
each actual Markdown destination's close, including adjacent links and
|
|
1128
1556
|
images and balanced/escaped parentheses; leading spaces, tabs, and one
|
|
1129
|
-
line ending before the destination are recognized.
|
|
1130
|
-
ordinary text
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1557
|
+
line ending before the destination are recognized. This scanner treats a marker
|
|
1558
|
+
after the close as ordinary text; kit admission applies the whole-value and
|
|
1559
|
+
path/query rules described above. This boundary is independent of redaction's fail-closed
|
|
1560
|
+
span. Each string is scanned alone; split rich-text runs are outside this
|
|
1561
|
+
scan's threat model.
|
|
1562
|
+
Sent-secret redaction runs before this step, in the provider kit's parse
|
|
1563
|
+
pass, so a rule that decides on a value can read the `[redacted]`
|
|
1564
|
+
placeholder where the vendor wrote what the rule matches. Every such
|
|
1565
|
+
decision input fails closed through one check, `redactionAltered` in
|
|
1566
|
+
`src/redaction-marker.ts`. A resource or embedded-URL type discriminator
|
|
1567
|
+
that carries the placeholder applies every resource rule's paths and
|
|
1568
|
+
withheld fields, never a verbatim URL. A label (`key`, `name`,
|
|
1569
|
+
`target.key`) or `type` that carries it makes the record a labelled or
|
|
1570
|
+
typed secret, a header, cookie, or query rule, and an environment item; an
|
|
1571
|
+
environment list string that carries it is withheld like `NAME=value`. A
|
|
1572
|
+
whole string whose scheme it may have taken (no whitespace, and only
|
|
1573
|
+
scheme characters before the placeholder) is withheld, at a destination
|
|
1574
|
+
key too. A parameter name it altered (the placeholder inside a run of name
|
|
1575
|
+
characters that ends at a plain or encoded `=`, and not directly after an
|
|
1576
|
+
`=`) loses its value wherever signing parameters are scanned, and in
|
|
1577
|
+
reviewed text withholds the whole link, as does a placeholder opening a
|
|
1578
|
+
word where a link's scheme and host would be; a fragment it altered
|
|
1579
|
+
withholds the query and fragment. A key that carries it (text a result
|
|
1580
|
+
hook parses after the kit redacted it whole, such as NDJSON and log
|
|
1581
|
+
streams) loses its value, and its record's key-based decisions take their
|
|
1582
|
+
protective outcome, route header pairs included. A placeholder can also
|
|
1583
|
+
stand for a credential's public prefix (`sk_live_`, `whsec_`, or a signing
|
|
1584
|
+
parameter's name and `=`) and leave its remainder, which with the prefix
|
|
1585
|
+
known rebuilds the credential however short or encoded it is. Wherever
|
|
1586
|
+
signing parameters are scanned, a placeholder followed by any of a value's
|
|
1587
|
+
base64 or base64url characters (letters, digits, `+`, `/`, `=`, `-`, `_`)
|
|
1588
|
+
or `%` escapes is withheld with them through the end of the value
|
|
1589
|
+
(`splitRemainder`), so a tail of `+/` alone goes too. A reference in an
|
|
1590
|
+
inferred remainder counts when it decodes to credential characters or an
|
|
1591
|
+
ampersand. Without a semicolon, numeric digits may run into the following
|
|
1592
|
+
value: a credential character spelled by the first 1–3 decimal or 1–2 hex
|
|
1593
|
+
digits after leading zeros also counts. Other references end that
|
|
1594
|
+
remainder, preserving text after spacing or punctuation references.
|
|
1595
|
+
Known signing values read references whole regardless of the character
|
|
1596
|
+
they spell. A value's end never splits
|
|
1597
|
+
a placeholder. In reviewed links, a query component that carries the
|
|
1598
|
+
placeholder and has no readable name withholds the query. What
|
|
1599
|
+
stays visible cannot complete a credential: other punctuation alone after a
|
|
1600
|
+
placeholder (`.`, `,`, `;`, `!`), which no base64 value contains, and text
|
|
1601
|
+
past a value's end (whitespace, a quote, an angle bracket, `&`, `#`, `)`,
|
|
1602
|
+
`]`, a backtick or a backslash), except where that syntax is part of a
|
|
1603
|
+
character reference read whole. A bare numeric reference body (`#50;`,
|
|
1604
|
+
`#x32;`, with optional semicolons and nested ampersand continuations) can
|
|
1605
|
+
remain when redaction removes part of a reference. It is read as value
|
|
1606
|
+
data, so `#` ends a signing value only when it does not open such a body.
|
|
1607
|
+
Numeric fragments directly after a signing value are withheld too.
|
|
1608
|
+
A following parameter is decided by its own name. Value safety's own
|
|
1609
|
+
placeholders are not alterations: `@keys` map keys, a withheld value right
|
|
1610
|
+
after `=`, and a withheld `#[redacted]` fragment. More is withheld only
|
|
1611
|
+
when a private value occurs in a decision input, and then deliberately:
|
|
1612
|
+
a placeholder inside a base64 value before its `=` padding counts as a
|
|
1613
|
+
parameter name, so its link goes whole; an altered fragment withholds the
|
|
1614
|
+
query and fragment; a Content-Type redaction altered on a body that is not
|
|
1615
|
+
JSON leaves it unreadable (see "Contributor provider kit"); a vendor key
|
|
1616
|
+
spelled with the placeholder loses its value.
|
|
1134
1617
|
4. **Errors.** A reviewed operation, or an unreviewed one whose request
|
|
1135
1618
|
accepts a credential-named field, answers failures with the vendor's codes
|
|
1136
1619
|
and status but not its text, unless its verdict is wrapped in
|
|
@@ -1143,13 +1626,15 @@ spec: every `redact` path and `keep` must name something the operation's
|
|
|
1143
1626
|
response schema can return (through `options.valueSafety.dataRoot` for an
|
|
1144
1627
|
envelope such as Cloudflare's `result`), and every resource rule path
|
|
1145
1628
|
something the resource's schema can return. It stamps the paths that resolve
|
|
1146
|
-
into the candidates file and fails on the rest, unless
|
|
1147
|
-
`value-safety.absent.json`
|
|
1148
|
-
|
|
1149
|
-
such paths still apply at
|
|
1629
|
+
into the candidates file and fails on the rest, unless the table's `absent`
|
|
1630
|
+
field (`value-safety.absent.json` until the provider's step 1) acknowledges
|
|
1631
|
+
them with a reason (shared verdicts applied to operations whose schema lacks a
|
|
1632
|
+
path, or fields a schema leaves undeclared; such paths still apply at
|
|
1633
|
+
runtime).
|
|
1150
1634
|
|
|
1151
1635
|
`test/fixtures/value-safety.ts` is the shared harness each vendor's
|
|
1152
|
-
`value-safety.node.test.ts`
|
|
1636
|
+
`value-safety.node.test.ts` (`provider.safety.node.test.ts` after its step 1)
|
|
1637
|
+
runs; `reviewProblems` is pure, and
|
|
1153
1638
|
`test/value-safety-harness.node.test.ts` proves wrong paths fail it. It fails
|
|
1154
1639
|
on candidates from another pin, format, or option set; an unreviewed
|
|
1155
1640
|
candidate; a verdict for an operation the index lacks; a table path the stamp
|
|
@@ -1164,89 +1649,62 @@ verdict count.
|
|
|
1164
1649
|
`RestVendor.valueSafety` is required, so a new REST vendor cannot ship without
|
|
1165
1650
|
a table.
|
|
1166
1651
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
complete captured schema that passes runtime verification; changed input or
|
|
1224
|
-
output schemas must fail it. The guard also exercises both digest prefixes.
|
|
1225
|
-
|
|
1226
|
-
The maintainer `scripts/drift-check.mjs` compares published inventories with
|
|
1227
|
-
provider evidence. Table inventories can require `inventory.accessColumn` and
|
|
1228
|
-
record `reviewedAccess` (`read` or `write`, with destructive verdicts recorded
|
|
1229
|
-
as write). These access snapshots must agree with the provider's runtime
|
|
1230
|
-
classification. A documented access change or disappearance of a reviewed explicit Access
|
|
1231
|
-
value is advisory drift, never an
|
|
1232
|
-
automatically accepted verdict. An explicit access value makes an
|
|
1233
|
-
`acknowledgedUnclassified` entry stale: report it and resume reporting that
|
|
1234
|
-
unreviewed tool as an addition. Missing tools and already reviewed names also
|
|
1235
|
-
invalidate such acknowledgements. Duplicate explicit access values must agree;
|
|
1236
|
-
a blank duplicate does not erase an explicit value elsewhere. Missing required
|
|
1237
|
-
columns in any tool table, unknown access values and conflicting rows are provider-local parser
|
|
1238
|
-
findings. The internal `withVettedCatalog()`
|
|
1239
|
-
helper has been removed. Public provider `*_VETTED_CATALOG` exports remain
|
|
1240
|
-
deprecated aliases derived from each definition
|
|
1241
|
-
(`test/classified-decorators.test.ts`, `test/hosted-presets.test.ts`).
|
|
1242
|
-
|
|
1243
|
-
Wrappers must forward `classification` to keep the review. `{ ...connector }`,
|
|
1244
|
-
`Object.assign`, and `Object.create` keep it. A forwarding class that omits it
|
|
1245
|
-
serves an unreviewed connector: the downstream's annotations are its own
|
|
1246
|
-
claims and fail closed when absent, and nothing it persists carries safety.
|
|
1247
|
-
Phase 2's deployment-level classifier overrides, keyed by connector id and
|
|
1248
|
-
tool ([#706](https://github.com/zackbart/connecta/issues/706)), apply
|
|
1249
|
-
regardless of wrapping.
|
|
1652
|
+
Remote MCP tools classify from server annotations and deployment policy.
|
|
1653
|
+
Only `readOnlyHint === true` with `destructiveHint !== true` establishes a
|
|
1654
|
+
source read; missing or contradictory hints mean write. Deployment overrides
|
|
1655
|
+
in `ConnectaConfig.classification` choose effective `read`, `write`, or `hidden`.
|
|
1656
|
+
They cannot relax source replay safety. Effective or source write makes a call
|
|
1657
|
+
write-like on every resend and recovery path. Source classifications travel in
|
|
1658
|
+
private metadata with host-owned tool clones and stay out of persisted catalogs.
|
|
1659
|
+
A leftover `classify` throws and names `ConnectaConfig.classification`.
|
|
1660
|
+
There are no hosted review maps, pins, drift comparisons or review UI fields.
|
|
1661
|
+
Catalog refresh activity still compares successive accepted catalog fingerprints
|
|
1662
|
+
and reports only added, removed and changed counts in the caller's partition.
|
|
1663
|
+
|
|
1664
|
+
#### Behavioral evidence (contributor)
|
|
1665
|
+
|
|
1666
|
+
The characterization snapshot engine, provider JSON snapshots, source scheduler
|
|
1667
|
+
and its self-tests are retired under
|
|
1668
|
+
[#1072](https://github.com/zackbart/connecta/issues/1072). Provider changes no
|
|
1669
|
+
longer require golden enrollment, snapshot updates, byte equivalence or nightly
|
|
1670
|
+
capture determinism. Production invariants remain unchanged.
|
|
1671
|
+
|
|
1672
|
+
Use the existing stateful fixture vendors and published-edge deployment adapters:
|
|
1673
|
+
|
|
1674
|
+
- `pnpm run eval:outcomes` drives representative joins, retained paging, endpoint
|
|
1675
|
+
refusal, auth handoff, value safety and refund recovery. Graders inspect
|
|
1676
|
+
independent downstream state and the physical mutation ledger, and reject
|
|
1677
|
+
wrong targets, duplicate writes, missing completion and interrupted trials.
|
|
1678
|
+
- `pnpm run eval:smoke` boots the Node template, Node eval deployments and native
|
|
1679
|
+
Workers deployments, including the actual QuickBooks provider, then exercises
|
|
1680
|
+
MCP and the operator page.
|
|
1681
|
+
- `test/provider-privacy-journey.node.test.ts` sends credentials through real
|
|
1682
|
+
HTTP MCP and a maintained API provider. Unknown vendor fields and keys carry
|
|
1683
|
+
raw, escaped, percent-encoded, base64 and Basic echoes through direct calls,
|
|
1684
|
+
QuickJS programs, failure and recovery. The independent decoder in
|
|
1685
|
+
`test/helpers/secret-echoes.ts` checks the wire responses for secret fragments;
|
|
1686
|
+
it does not normalize them to placeholders or compare saved output.
|
|
1687
|
+
- `test/provider-failure-sweep.test.ts` checks retained catalog examples for
|
|
1688
|
+
dispatched failure, classification, auxiliary and composite write outcomes.
|
|
1689
|
+
New providers use the required deployment outcomes above. Existing
|
|
1690
|
+
`test/sent-secrets.test.ts`, `test/sent-secret-output-boundary.test.ts`,
|
|
1691
|
+
`test/request-secret-redaction.test.ts` and `test/catalog-secret-redaction.test.ts`
|
|
1692
|
+
cover secrecy before projection, truncation, caching, paging and diagnostics.
|
|
1693
|
+
- `test/write-evidence.node.test.ts`, `test/remote-mcp-attempts.node.test.ts` and
|
|
1694
|
+
`test/oauth-credential-attempts.node.test.ts` inspect physical socket sends.
|
|
1695
|
+
Native transport parity, OAuth and write-provenance suites retain their
|
|
1696
|
+
workerd coverage. The shared throwaway RSA key in `test/fixtures/rsa.ts` is
|
|
1697
|
+
used by write-provenance and unknown-write-advice fixtures.
|
|
1698
|
+
- The request, catalog, auxiliary-call, renewal and Promise-boundary lifetime
|
|
1699
|
+
suites, plus `test/auth-admission-binding.test.ts`, exercise cancellation,
|
|
1700
|
+
held native work and garbage collection. These test actual request ownership;
|
|
1701
|
+
the retired capture scheduler's source guards were only harness constraints.
|
|
1702
|
+
|
|
1703
|
+
Add a compact fault journey only for a meaningful outcome those suites cannot
|
|
1704
|
+
observe. Reuse their fixtures and adapters; do not reintroduce a snapshot
|
|
1705
|
+
format, module loader or virtual scheduler. Workerd enrollment is for native
|
|
1706
|
+
fetch, streams, Web Crypto, lifetime and request-byte behavior, as listed in
|
|
1707
|
+
`vitest.config.ts`; pure logic runs on Node.
|
|
1250
1708
|
|
|
1251
1709
|
## Optional deployment modules
|
|
1252
1710
|
|
|
@@ -1323,7 +1781,7 @@ flight's deadline, never touching the owner's signal or response.
|
|
|
1323
1781
|
|
|
1324
1782
|
Nothing reachable from `src/index.ts` may import a `node:` builtin, so the same
|
|
1325
1783
|
core runs unchanged in workerd and in Node. The Node-touching paths — `src/node.ts`
|
|
1326
|
-
(the `node:http` adapter), `src/sqlite.ts` (`node:sqlite`), and the QuickJS process pool
|
|
1784
|
+
(the `node:http` adapter and its owned `http/node-transport.ts` undici dispatcher), `src/sqlite.ts` (`node:sqlite`), and the QuickJS process pool
|
|
1327
1785
|
(`src/executors/quickjs.ts` plus its child) — each sit behind an explicit subpath
|
|
1328
1786
|
and must stay unreachable from the root. `./auth/clerk` is separate because
|
|
1329
1787
|
`@clerk/backend` is an optional peer rather than a dependency, and
|
|
@@ -1340,7 +1798,7 @@ shape from building, in someone else's repository rather than this one.
|
|
|
1340
1798
|
|
|
1341
1799
|
## Effect inside
|
|
1342
1800
|
|
|
1343
|
-
The core runs on
|
|
1801
|
+
The core runs on Effect v4, pinned to exactly `4.0.5`; no API a deployment
|
|
1344
1802
|
touches does. `createConnecta`,
|
|
1345
1803
|
`remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts`
|
|
1346
1804
|
are Promise-shaped and name no Effect type, so a connector author never meets a
|
|
@@ -1411,14 +1869,254 @@ storage is the root's namespaced to its principal.
|
|
|
1411
1869
|
| Requests (`src/server.ts`, `src/routes/mcp.ts`, `src/routes/oauth.ts`) | One fiber per `fetch`, tied to `request.signal`. An `/mcp` request's admission permit and every `McpServer` it builds live in a Scope the response carries out, closed when the body ends, fails, or is cancelled, when the signal aborts, when its admitted-request lifetime ends in that request, or at once if the handler fails first. If workerd runs none of those callbacks, the next request reclaims only the expired admission record; it cannot close another request's Scope. The OAuth callback is uninterruptible: a single-use code's exchange and catalog invalidation are one commitment. |
|
|
1412
1870
|
| Admission (`executor-admission.ts`, `call-admission.ts`) | Queued waiters are Deferreds settled by whoever removes them from the queue; a wait is one flat race of grant, Clock timeout, and signal. The uncontended path stays synchronous. Two controllers, as [#453](https://github.com/zackbart/connecta/issues/453) requires. |
|
|
1413
1871
|
| One tool call (`invocation.ts`) | One fiber: resolution, the read-only and schema refusals, admission, and the downstream attempt sit under a single `withDeadlineEffect`, whose expiry interrupts the call wherever it is. The permit is an `acquireRelease`; the connector call is `Effect.tryPromise` over the unchanged `Connector`. |
|
|
1414
|
-
| `execute_code` (`execute.ts`) | One fiber whose Scope owns the run's signal and executor lease, so a result, a throw, the watchdog, and cancellation all release the lease and abort the signal the same way.
|
|
1872
|
+
| `execute_code` (`execute.ts`) | One fiber whose Scope owns the run's signal and executor lease, so a result, a throw, the watchdog, and cancellation all release the lease and abort the signal the same way. Public executor methods stay Promise-shaped; registered built-in execution composes directly as an Effect under the signal and `execute.watchdogMs`. Queued acquisition retains its Promise request handoff. Each guest host call is a fiber of its own. |
|
|
1415
1873
|
| Discovery (`catalog-service.ts`) | A request-scoped cache: one shared read per connector (`runtime/shared-read.ts`), settled by the read itself and carrying its own signal and the probe timeout whichever asker starts it. Each asker waits under its own deadline and signal, so one that times out or is cancelled fails alone, and the read is cancelled only once every asker has gone. Fan-out is `Effect.forEach` under the discovery concurrency. |
|
|
1416
1874
|
| Registry (`registry.ts`) | The result stash is a program over `Storage`. Same-request catalog loads share one read the way discovery's do. Remote MCP caching uses the SDK ResponseCacheStore adapter over SQL-backed KV, with no cross-request catalog flight. |
|
|
1417
1875
|
| Remote MCP (`connectors/remote-mcp.ts`) | Each request scope's state holds a Scope, each connection is a lease forked from it, and a connect in flight is a Deferred carrying the client it connected. Closing a session and the transport are each bounded to a second. |
|
|
1418
|
-
| Downstream OAuth (`auth/downstream-oauth.ts`)
|
|
1876
|
+
| Downstream OAuth (`auth/downstream-oauth.ts`, `auth/static-oauth.ts`) | A refresh flight is a Deferred. Before the first durable coordinator write (claim CAS) is submitted, call/request closure stops preparation without a CAS, token request or state change. Submission transfers renewal to the storage coordinator under its own 35-second preparation deadline and 20-second token-request/body deadline, followed by the fenced rotation commit through the runtime deferred-work hook. A closed caller promptly fails without consuming the result, marking its ledger renewed or sending/resending a resource request. |
|
|
1419
1877
|
| Operator and activity data (`routes/operator.ts`) | Each JSON route is one program run by `serveOperator` behind the Promise `handle()`. Reads run under the request's signal; writes do not, so a vault write or OAuth disconnect that started reaches its cache invalidation. |
|
|
1420
1878
|
| QuickJS pool (`executors/quickjs.ts`, Node only) | Each child is a scoped resource whose release sends SIGTERM, then SIGKILL after a second; crash respawn backoff is a `Schedule`. |
|
|
1421
1879
|
|
|
1880
|
+
### Native registry and request services (contributor)
|
|
1881
|
+
|
|
1882
|
+
Registry catalog loading, classification, OAuth handoffs, status, invalidation,
|
|
1883
|
+
credential readiness, and result stashing have private Effect programs in
|
|
1884
|
+
`src/runtime/registry.ts`. Static class blocks install those programs while
|
|
1885
|
+
preserving the published Registry declaration, including its private member
|
|
1886
|
+
names. `registryProgram(view, method, ...args)` checks the installed method's
|
|
1887
|
+
identity; a custom or replaced method remains a Promise leaf. A Promise shell
|
|
1888
|
+
selects its original class implementation through `registryEdgeProgram`, so a
|
|
1889
|
+
wrapper can call the original without redispatching to itself. Catalog search,
|
|
1890
|
+
describe, resolution, and listing compose these programs directly. The shared
|
|
1891
|
+
listing is a native child of its request's Scope, started through
|
|
1892
|
+
`forkRequest` in the edge runner; an individual reader cannot terminate a read
|
|
1893
|
+
another reader still needs. Closing the request aborts and closes those children.
|
|
1894
|
+
Settled reads remove their request-close finalizer; active reads retain it,
|
|
1895
|
+
including in long-lived caller-supplied scopes.
|
|
1896
|
+
|
|
1897
|
+
Native catalog and maintained API entry points report policy, validation, and
|
|
1898
|
+
operation-construction throws as typed failures with the original thrown value,
|
|
1899
|
+
matching the Promise boundary. Native consumers can use `Effect.catch` and
|
|
1900
|
+
`Effect.result` without losing refusals to the defect channel. Interruption
|
|
1901
|
+
remains interruption. Normalization belongs at these program boundaries;
|
|
1902
|
+
pure catalog algorithms and core validation retain their existing behavior.
|
|
1903
|
+
|
|
1904
|
+
`RequestScope` in `src/runtime/request-scope.ts` represents the existing request
|
|
1905
|
+
identity. It owns call revocation, teardown, operation signals, the complete
|
|
1906
|
+
sent-secret set, and invocation/auth recovery facts. `openRequest` preserves
|
|
1907
|
+
its synchronous revocation bridge. Live context access refuses after closure;
|
|
1908
|
+
`recordedService` and the owner's `retained` facts remain available for
|
|
1909
|
+
settlement and redaction. Sharing a request identity grants no call authority
|
|
1910
|
+
to a spread copy of a context. Call deadlines, request lifetime, and the OAuth
|
|
1911
|
+
refresh coordinator remain distinct owners.
|
|
1912
|
+
|
|
1913
|
+
Maintained API programs register their original tool-handler identity through
|
|
1914
|
+
`src/runtime/call-entry.ts`. Invocation enters the native API validation and
|
|
1915
|
+
settlement pipeline, which composes a registered tool program directly; the
|
|
1916
|
+
Promise handler is its public shell. Replacement and custom handlers stay
|
|
1917
|
+
Promise leaves. Both paths retain argument validation, generic withheld
|
|
1918
|
+
handler diagnostics, secret redaction, call revocation, and core write-outcome
|
|
1919
|
+
settlement. Custom connector hooks, vault and cache adapters, Web Crypto,
|
|
1920
|
+
downstream OAuth, and the remote SDK retain their existing Promise boundaries.
|
|
1921
|
+
Cross-request call admission deliberately remains a Promise handoff so workerd
|
|
1922
|
+
resumes queued work in the request waiting for the permit.
|
|
1923
|
+
|
|
1924
|
+
The `Storage` service composes built-in `getNativeStorage` operations directly.
|
|
1925
|
+
Registry wrappers propagate that optional capability through principal,
|
|
1926
|
+
connector, subject, and result prefixes, including nested namespaces; list
|
|
1927
|
+
results strip only their own prefix. Custom stores keep the Promise fallback.
|
|
1928
|
+
These adapters add no retry, transaction, or storage encoding and preserve
|
|
1929
|
+
D1's single atomic batch and SQLite's native transaction. Native activity
|
|
1930
|
+
lookup remains available to the route and activity consumers.
|
|
1931
|
+
|
|
1932
|
+
### Native executor hosts (contributor)
|
|
1933
|
+
|
|
1934
|
+
`src/runtime/executor.ts` registers private execution programs by executor or
|
|
1935
|
+
lease identity and the original `execute` method. `executorProgram` composes a
|
|
1936
|
+
registered implementation directly; custom or replaced methods remain Promise
|
|
1937
|
+
leaves. Public shells call their own implementation directly, allowing wrappers
|
|
1938
|
+
to delegate to a saved original without recursion. Native interruption retains
|
|
1939
|
+
the QuickJS log prefix; Promise leaves are observed even when a terminal budget
|
|
1940
|
+
race wins before their settlement. Redaction and write settlement remain in the
|
|
1941
|
+
existing execute pipeline, using its request identity and call capabilities.
|
|
1942
|
+
The executor seam creates no second request or call owner.
|
|
1943
|
+
|
|
1944
|
+
Both built-ins acquire their permits through native admission and return
|
|
1945
|
+
execute-once leases with synchronous, idempotent release. A queued standalone
|
|
1946
|
+
execution crosses `awaitExecutorLease`'s Promise handoff before host setup, so
|
|
1947
|
+
workerd resumes in the waiting request. `execute_code` retains its public
|
|
1948
|
+
`acquire()` handoff for that same reason. A cancelled queued handoff withdraws
|
|
1949
|
+
its waiter, and a late custom acquisition releases its abandoned lease.
|
|
1950
|
+
|
|
1951
|
+
The Worker SDK prepares source and dispatchers against an inert loader. The
|
|
1952
|
+
native host then acquires the actual Loader handle and RPC entrypoint with
|
|
1953
|
+
`Effect.acquireRelease`, awaits the native RPC Promise, and races the run with
|
|
1954
|
+
its lease's cancellation Deferred. A resource Scope owns entrypoint-before-
|
|
1955
|
+
Loader disposal on every exit, including never-settling evaluation. Release
|
|
1956
|
+
closes that Scope synchronously before returning the permit; a failed disposer
|
|
1957
|
+
cannot skip the next one. Forwarders lose their providers and pending waits at
|
|
1958
|
+
run end. Only native RPC output and the run's private host-failure map can
|
|
1959
|
+
supply failure provenance. Guest source isolation, intrinsic capture, import
|
|
1960
|
+
routing, codecs and the SDK's source-generation checks remain ordinary leaves.
|
|
1961
|
+
Both built-ins normalize provider leaf returns with `Promise.resolve`, retaining
|
|
1962
|
+
direct JavaScript callers' synchronous value tolerance and original host
|
|
1963
|
+
rejections. The published provider-function signature remains Promise-shaped.
|
|
1964
|
+
|
|
1965
|
+
QuickJS owns host-call fibers in each active run's Scope. Completion,
|
|
1966
|
+
interruption, cancellation, recycling and shutdown revoke provider lookup and
|
|
1967
|
+
interrupt those fibers; late SDK results and stale IPC cannot reply to another
|
|
1968
|
+
run. Releasing a lease during readiness or crash backoff also cancels the old
|
|
1969
|
+
run before its slot is handed back. Child retirement closes its resource Scope,
|
|
1970
|
+
sends SIGTERM, and escalates to SIGKILL after one second. Shutdown joins native
|
|
1971
|
+
completion Deferreds for all retirement tails. Crash backoff remains a Schedule.
|
|
1972
|
+
`forkOwned` in `src/runtime/run.ts` starts the host and startup-watchdog children;
|
|
1973
|
+
no executor starts a fiber directly. Child process events, IPC encoding and
|
|
1974
|
+
bounds, QuickJS VM/job pumping and guest code remain platform or pure leaves.
|
|
1975
|
+
|
|
1976
|
+
`test/executor-native.test.ts` exercises native dispatch and Worker disposal,
|
|
1977
|
+
never-settling evaluation/host calls, late rejection, execute-once ownership and
|
|
1978
|
+
original acquisition errors in workerd. `test/quickjs-pool.node.test.ts` covers
|
|
1979
|
+
readiness release, stale/malformed IPC, late host failures, backoff and bounded
|
|
1980
|
+
kill. Real Worker and QuickJS guest contracts, the HTTP Worker budget/queued
|
|
1981
|
+
request fixture and native-output suites supply runtime parity evidence.
|
|
1982
|
+
|
|
1983
|
+
### Native SQL storage (contributor)
|
|
1984
|
+
|
|
1985
|
+
`src/storage/sql-native.ts` composes KV, activity schema upgrades, retention,
|
|
1986
|
+
and bounded SQL copies as Effects. `src/storage/d1-copy.ts` composes the
|
|
1987
|
+
Workers KV copy, source-bound resume claims, verification, cutover guard, and
|
|
1988
|
+
recovery. Public factories and copy functions retain their Promise contracts;
|
|
1989
|
+
custom Promise SQL drivers retain the fallback lift.
|
|
1990
|
+
|
|
1991
|
+
`src/storage/sql-driver.ts` constructs a public Effect 4.0.5 `SqlClient`
|
|
1992
|
+
service inside each operation. Reads execute SQL `Statement` Effects and writes
|
|
1993
|
+
execute their raw operations through that service, including its connection
|
|
1994
|
+
borrowing, statement-transformer context, and tracing semantics. Internal
|
|
1995
|
+
statement transformers see the current service; no client, statement, or
|
|
1996
|
+
Reactivity state is cached across operations or requests.
|
|
1997
|
+
|
|
1998
|
+
Atomic batches remain one native database operation: D1's public `batch`, or
|
|
1999
|
+
SQLite's synchronous `BEGIN IMMEDIATE` through `COMMIT`, with rollback on
|
|
2000
|
+
failure. Neither path retries a dispatched write. The D1 binding's
|
|
2001
|
+
`meta.changes` is retained for every statement, including zero; no `RETURNING`
|
|
2002
|
+
payload, extra metadata statement, or D1 transaction is needed. The dependency
|
|
2003
|
+
is the existing exact `effect` pin. Connecta does not adopt `@effect/sql-d1`:
|
|
2004
|
+
its 4.0.5 batch API drops metadata and its binding contract is wider than
|
|
2005
|
+
Connecta's published structural interface.
|
|
2006
|
+
|
|
2007
|
+
The D.2 architecture allowance was measured against `0e314247` using cached
|
|
2008
|
+
official `node:22-slim` (Node 22.23.4, zlib 1.3.1-e00f703), no network, and
|
|
2009
|
+
read-only bundle artifacts: `/d1` grew from 62,516 to 88,305 bytes gzip
|
|
2010
|
+
(+25,789); the Worker example grew from 729,847 to 756,375 (+26,528).
|
|
2011
|
+
Final typed-driver failure normalization measures 88,319 and 756,377 bytes
|
|
2012
|
+
on that same official runtime. Executing real services and statement operations requires those runtime
|
|
2013
|
+
semantics; the earlier identity-compiler facade did not provide them. The
|
|
2014
|
+
explicit allowance changes only `/d1`'s cap from 62,542 to 90,000 bytes and
|
|
2015
|
+
the Worker example's cap from 743,000 to 760,000. Historical baselines and
|
|
2016
|
+
all other caps stay fixed. Final integration must remeasure the combined
|
|
2017
|
+
implementation and satisfy the same compatibility and privacy contracts.
|
|
2018
|
+
|
|
2019
|
+
The platform connection capability contains no request state. D1 prepares in
|
|
2020
|
+
the current request and retains no prepared handles or promises. SQLite keeps
|
|
2021
|
+
its existing connection-owned prepared-statement cache; externally supplied
|
|
2022
|
+
databases remain caller-owned. Schema readiness is a boolean. A microtask
|
|
2023
|
+
boundary precedes dispatch so pending cancellation can stop physical execution;
|
|
2024
|
+
a SQLite batch never yields between transaction controls. Cancellation cannot
|
|
2025
|
+
undo an already-submitted D1 write and never causes replay.
|
|
2026
|
+
|
|
2027
|
+
Public SQL Statement operations create spans, so SQL tracing is disabled
|
|
2028
|
+
before executing them. Private `SqlError` values contain fixed wording with
|
|
2029
|
+
no driver cause or parameters; the original thrown value is restored only
|
|
2030
|
+
at the private SQL boundary. Operator sinks receive no new SQL diagnostics.
|
|
2031
|
+
The tracing contract is checked with a recording tracer and a mutation that
|
|
2032
|
+
removes the guard, which must fail. Existing import and copy errors retain
|
|
2033
|
+
their fixed, payload-free wording.
|
|
2034
|
+
|
|
2035
|
+
`src/runtime/native-storage.ts` supplies optional `getNativeStorage` and
|
|
2036
|
+
`getNativeActivity` lookups. An internal consumer may compose their Effects
|
|
2037
|
+
without starting another fiber; an absent lookup requires the existing custom
|
|
2038
|
+
Promise-store fallback. Registration normalizes operation-construction,
|
|
2039
|
+
validation, and decoding throws into typed failures with the original thrown
|
|
2040
|
+
value, matching that fallback. Interruption remains interruption. The lookup
|
|
2041
|
+
module imports no SQL or platform implementation and does not change the
|
|
2042
|
+
published storage interfaces.
|
|
2043
|
+
|
|
2044
|
+
The synchronous state-file parser, validation, file read, and one atomic
|
|
2045
|
+
SQLite import remain native synchronous leaves because `importStateFile` is a
|
|
2046
|
+
published synchronous API. `openSqlite` retains file modes, WAL, busy timeout,
|
|
2047
|
+
and database ownership. `test/sql-effect-contract.ts` runs native-path,
|
|
2048
|
+
affected-row, 50-claim, cancellation, and tracing checks on SQLite and native
|
|
2049
|
+
D1. `test/kv-to-d1.test.ts` also reuses stores across separate workerd requests;
|
|
2050
|
+
the existing SQL contracts prove rollback, database-clock TTL, encodings,
|
|
2051
|
+
activity upgrades, and copy recovery.
|
|
2052
|
+
|
|
2053
|
+
### Internal provider HTTP failures (contributor)
|
|
2054
|
+
|
|
2055
|
+
The kit's `src/http/errors.ts` maps every `HttpFailure` tag through
|
|
2056
|
+
`toConnectorCallError`. Its completeness-checked tag vocabulary is also the
|
|
2057
|
+
typed-client boundary's guard. Vendors declare closed public value vocabularies
|
|
2058
|
+
for body facts that select a failure meaning. Before sent-secret value redaction,
|
|
2059
|
+
the kit extracts those facts from a token-only copy and retains only declared
|
|
2060
|
+
values; unknown reasons or codes contribute no classification fact and fall
|
|
2061
|
+
back to status rules. The captured facts stay in request-local identity metadata,
|
|
2062
|
+
outside the failure's enumerable data. Classification reads them with complete
|
|
2063
|
+
bounded facts and reviewed headers. Vendors also declare each numeric source in
|
|
2064
|
+
`numericFacts`: an exact body `path` or lowercase `header`, a review reason, and
|
|
2065
|
+
an optional `format` (`integer` by default, `seconds`, `epoch-seconds`, or
|
|
2066
|
+
`retry-after`). The kit reads only these sources from the response before value
|
|
2067
|
+
redaction. It accepts nonnegative whole numbers and strings of at most
|
|
2068
|
+
128 characters that contain only ASCII digits after trimming surrounding
|
|
2069
|
+
whitespace; `retry-after` also accepts HTTP-dates. Integers are capped at
|
|
2070
|
+
`Number.MAX_SAFE_INTEGER`; delay formats yield milliseconds clamped to
|
|
2071
|
+
0–86,400,000. The `retryAfterMs` numeric fact supplies the kit's standard hint
|
|
2072
|
+
and requires an explicit delay format (`seconds`, `epoch-seconds`, or `retry-after`).
|
|
2073
|
+
For example, a vendor declares `{ retryAfterMs: { header: "retry-after",
|
|
2074
|
+
format: "retry-after", review: "HTTP Retry-After delay for failure recovery." } }`.
|
|
2075
|
+
Classification receives only parsed numbers, never their original text or
|
|
2076
|
+
undeclared fields. These facts may select a code, retryability and bounded delay;
|
|
2077
|
+
they never supply wording or records. Only after the verdict is fixed does wording
|
|
2078
|
+
receive numbers read separately from the redacted response, facts extracted
|
|
2079
|
+
separately from the redacted body (eight list entries)
|
|
2080
|
+
and, where the operation's value-safety review permits it, that redacted body.
|
|
2081
|
+
Pre-redaction facts never supply message text, logs, or operator records.
|
|
2082
|
+
Non-2xx responses always take this capture path. A connection opts in with
|
|
2083
|
+
`inBandRefusals: true` only when its result hook reports in-band refusals on
|
|
2084
|
+
successful statuses; their declared facts come from the whole response.
|
|
2085
|
+
Required vendor meanings for 401/403/404 precede the other defaults; 409 has no
|
|
2086
|
+
shared conflict default.
|
|
2087
|
+
Stripe declares HTTP 409 as a conflict, alongside its fact-driven 429
|
|
2088
|
+
`lock_timeout` rule. Its reviewed `bodyFailures` opt-in applies status-only
|
|
2089
|
+
classification to an oversized 424 reply, preserving the dependency-failure
|
|
2090
|
+
meaning when the bounded body cannot be read. The original ceiling diagnostic
|
|
2091
|
+
and answered status remain; the core still decides write outcomes. It does not
|
|
2092
|
+
apply to 401 or alter Q8. A fixed read-phase fact distinguishes failed body
|
|
2093
|
+
reads from malformed JSON only for vendors declaring that wording; raw
|
|
2094
|
+
exceptions never enter diagnostics. Other providers retain their mapping and
|
|
2095
|
+
wording.
|
|
2096
|
+
A 421 is `unavailable` and retryable only when the transport's core
|
|
2097
|
+
positive-read classification proves that request a read, including its actual
|
|
2098
|
+
method and method-override headers. The agent chooses another call; the kit
|
|
2099
|
+
does not resend it. This override preserves the vendor's wording and changes
|
|
2100
|
+
only the code and retryability. A physical write retains its existing failure
|
|
2101
|
+
mapping.
|
|
2102
|
+
|
|
2103
|
+
The mapping records each request's own served provenance and raw 401 facts.
|
|
2104
|
+
`api()` and invocation retain the sole write-outcome decision in the core's
|
|
2105
|
+
`providerCallFailure`/`settleWrite`. A blocked resend reports the first
|
|
2106
|
+
reservation's answer even for a read-classified tool issuing a physical write.
|
|
2107
|
+
An accepted write's blocked retry retains its original HTTP status; a slot
|
|
2108
|
+
occupied by an auxiliary read instead names the separate write step needed.
|
|
2109
|
+
Structured resend advice reaches output only when the core permits it. Generic
|
|
2110
|
+
unknown writes retain the core's diagnostic wait; Q8 removes it. The mapping
|
|
2111
|
+
adds no Promise entry or lifetime owner. `provider-failure-sweep.test.ts`,
|
|
2112
|
+
`guarded-attempts.node.test.ts`, `call-services.test.ts` and the native
|
|
2113
|
+
transport suites exercise these contracts through invocation, physical sockets
|
|
2114
|
+
and request closure. `http-kit.test.ts` retains focused numeric/text fact,
|
|
2115
|
+
request-equivalence and bulk-capacity fault probes. The migration-era
|
|
2116
|
+
kit/core verdict comparison is retired under #1072. Maintained API providers use the kit through their
|
|
2117
|
+
explicit provider subpaths; it stays off the root import graph and published
|
|
2118
|
+
declarations expose Promises.
|
|
2119
|
+
|
|
1422
2120
|
### What stays plain
|
|
1423
2121
|
|
|
1424
2122
|
- **Everything a deployment writes against.** The `Connector` contract,
|
|
@@ -1439,7 +2137,17 @@ storage is the root's namespaced to its principal.
|
|
|
1439
2137
|
|
|
1440
2138
|
### Across requests
|
|
1441
2139
|
|
|
1442
|
-
|
|
2140
|
+
Organization-mode Clerk Backend API reads share only plain membership verdicts,
|
|
2141
|
+
start times and sequences, active-call counters (16 per isolate, two per
|
|
2142
|
+
identity), and a bounded 429 backoff timestamp. They never share a promise,
|
|
2143
|
+
queue, signal, controller, or body across requests. A refused busy lookup is
|
|
2144
|
+
not cached. Each admitted read owns its ten-second deadline and cleanup;
|
|
2145
|
+
`finally` releases its counters after ending its upstream I/O, and the owning
|
|
2146
|
+
request's abort handler also releases them idempotently if workerd discards the
|
|
2147
|
+
pending continuation on disconnect. Membership cache
|
|
2148
|
+
hits precede these bounds; see [organization admission](./auth.md#one-clerk-instance-one-organization-per-deployment).
|
|
2149
|
+
|
|
2150
|
+
Asynchronous work shared across requests meets only through a Deferred that the owning
|
|
1443
2151
|
request completes, never through a fiber that outlives its request. Two
|
|
1444
2152
|
workerd rules follow, and both have bitten.
|
|
1445
2153
|
|
|
@@ -1481,7 +2189,714 @@ within five minutes of expiry in the same auth partition. Partial walks and
|
|
|
1481
2189
|
auth or permission failures refuse fallback; resources require unexpired
|
|
1482
2190
|
inventories, and stale fallback cannot authorize post-entry auth recovery.
|
|
1483
2191
|
|
|
1484
|
-
###
|
|
2192
|
+
### Contributor provider kit
|
|
2193
|
+
|
|
2194
|
+
Provider declarations use `src/http/vendor.ts` for HttpApi operations and
|
|
2195
|
+
`src/http/tools.ts` for list, get, read and write builders.
|
|
2196
|
+
`src/http/provider.ts` composes them with `defineProvider`; it returns the
|
|
2197
|
+
ordinary Promise-based Connector factory. The kit stays outside the root
|
|
2198
|
+
graph and its Effect types are internal. In each provider, `api.ts` declares
|
|
2199
|
+
the vendor and `tools.ts` the tools.
|
|
2200
|
+
|
|
2201
|
+
Decision 0011's standard surface (Tithe.ly's step 2) adds four kit pieces.
|
|
2202
|
+
A `list` without the legacy `key` returns `{ items, page }`, with
|
|
2203
|
+
`page.nextCursor` only while `hasMore`, and the kit appends `limit` (the
|
|
2204
|
+
declared default and maximum), `cursor` and `fields` to the tool's filters;
|
|
2205
|
+
`read` and `get` take `fields: true`. `src/http/fields.ts` narrows after every
|
|
2206
|
+
projection. A standard list may opt into `total: true` and supply an optional
|
|
2207
|
+
exact vendor count as `page.total` from its page callback. The kit validates a
|
|
2208
|
+
nonnegative safe integer and includes it on continuing and terminal pages;
|
|
2209
|
+
without a vendor count the property is absent. Lists without the opt-in keep
|
|
2210
|
+
their existing schema and result bytes. Never derive an exact total from a
|
|
2211
|
+
partial page or an estimate. `src/http/fields.ts` refuses an unknown first
|
|
2212
|
+
segment before dispatch; a selected
|
|
2213
|
+
result renders against a sparse output schema. `src/http/hatch.ts` is the
|
|
2214
|
+
standard hatch (`_api_search`, `_api_details`, `_api_read`, `_api_write`) over
|
|
2215
|
+
an `OperationIndex` with explicit verdicts. It keeps the REST family's
|
|
2216
|
+
argument and envelope names and validates `fields` against the operation's
|
|
2217
|
+
declared response roots before dispatch. Search answers stay within 16 KiB
|
|
2218
|
+
(at most eight compact entries, long labels cut and marked, continuation by
|
|
2219
|
+
cursor); details stay within 32 KiB, paging whole parameters and body
|
|
2220
|
+
properties, and chunking any piece larger than one answer, behind a bound
|
|
2221
|
+
cursor. Standard hatch search removes conversational framing; framing-only
|
|
2222
|
+
queries return no matches. It admits partial lexical matches alongside full
|
|
2223
|
+
matches. Action/read queries require all content nouns in the operation text,
|
|
2224
|
+
or any noun in the path/id with at least half the nouns in the text. Their
|
|
2225
|
+
field-only matches need half-noun coverage across text and declared field
|
|
2226
|
+
names. Verb-less noun phrases retain their broader rule: any noun in the
|
|
2227
|
+
path/id, or all nouns in the text. A leading create/add/update/edit/make/set/apply/change/rename/send/delete/remove/cancel/stop
|
|
2228
|
+
with an object sets write intent and retains later read words as nouns. Make,
|
|
2229
|
+
set, apply, change, edit and rename share update synonyms. `make a`,
|
|
2230
|
+
`make an`, `make new` and `set up` phrases instead select create synonyms,
|
|
2231
|
+
including after leading conversational framing such as `please` or `i want to`;
|
|
2232
|
+
`make text bold` retains update intent. Otherwise, a read
|
|
2233
|
+
word anywhere sets read intent, including after refund/charge/issue/process.
|
|
2234
|
+
Short function words and id-shaped tokens do not contribute to noun coverage.
|
|
2235
|
+
Sheets A1-style cell references also supply no resource noun. A write query
|
|
2236
|
+
whose supplied object contains no resource nouns includes its verb synonyms
|
|
2237
|
+
in candidate retrieval, so values do not restrict it to reads mentioning them.
|
|
2238
|
+
Verb-only queries and a rename destination without an object retain ordinary
|
|
2239
|
+
retrieval.
|
|
2240
|
+
Reviewed intent, the requested verb/synonym in summary/id/path, and resource
|
|
2241
|
+
focus sort first. A refused operation with a matching verb shares the intent
|
|
2242
|
+
tier only when its noun coverage exceeds every admitted operation matching that
|
|
2243
|
+
verb and intent. Search and details retain the original indexed summary and expose the reviewed
|
|
2244
|
+
reason in a separate optional `refusal` field. Ranking uses the original indexed
|
|
2245
|
+
text, never the newly attached reason. Search caps refusal text at 512 UTF-16
|
|
2246
|
+
code units without splitting a surrogate pair, independently of its 300-unit
|
|
2247
|
+
summary cap. A cut reason names `operations[i].refusal` in `truncated`; details
|
|
2248
|
+
returns the complete reason within its 32 KiB envelope or reconstructible
|
|
2249
|
+
`of: "refusal"` text chunks. The five long Cloudflare reasons fit whole in the
|
|
2250
|
+
search refusal budget, including their named alternatives. REST safety tables
|
|
2251
|
+
supply the reason through `restSafety`; reviewed bindings declare static operation
|
|
2252
|
+
refusals separately from argument validation. Discovery cursors additionally bind
|
|
2253
|
+
the summary/refusal representation format, so an old piece offset cannot skip a
|
|
2254
|
+
required parameter after this representation changes. The binding's deployment
|
|
2255
|
+
`cursorScope`, named suggestions and paging totals retain their existing seams.
|
|
2256
|
+
An unreviewed operation reports the missing hatch verdict. A refused hit has
|
|
2257
|
+
no read/write hatch pointer; a reviewed named alternative can still be shown.
|
|
2258
|
+
|
|
2259
|
+
Verb-less noun phrases first sort by noun coverage and an exact summary phrase
|
|
2260
|
+
prefix, then by an exact terminal resource: the last literal non-version segment must contain the last query noun
|
|
2261
|
+
or the concatenated noun phrase (`invoiceitems` for `invoice items`). An authored
|
|
2262
|
+
read whose vendor summary/id covers every noun shares that terminal-resource
|
|
2263
|
+
tier only when a real terminal-resource match exists and its description
|
|
2264
|
+
begins with a read action; the action word itself
|
|
2265
|
+
does not supply noun coverage. This preserves aggregate
|
|
2266
|
+
authored entry points without treating a verification action as a token list. When both candidates match the terminal resource,
|
|
2267
|
+
a reviewed read precedes writes and refusals. Depth with a one-segment authored
|
|
2268
|
+
tool preference, exact resource-noun coverage, and authored-tool preference
|
|
2269
|
+
break remaining ties. A pointer to another hatch receives no authored preference.
|
|
2270
|
+
Exact resource nouns include literal namespaces on either side of a version
|
|
2271
|
+
segment; parameters and version markers supply no resource coverage. This
|
|
2272
|
+
prefers `members` over summary-only membership matches and `tokens` over a
|
|
2273
|
+
verification action, while direct versioned collections retain their depth
|
|
2274
|
+
advantage over nested collections. For a single noun, and for
|
|
2275
|
+
leading `list` queries with one exact resource noun plus modifiers, collection
|
|
2276
|
+
ties sort by exact root, fewer nested resources, matching namespace, shallow
|
|
2277
|
+
depth and authored tool. Other leading `list` queries prefer authored tools
|
|
2278
|
+
whose summary/id covers every noun. This retains descriptive aliases while
|
|
2279
|
+
restoring [decision 0014](https://github.com/zackbart/connecta/blob/main/decisions/0014-api-tool-shape.md)'s
|
|
2280
|
+
noun-phrase preference. Person/people and mail/email aliases, s/es/ies plurals and hyphenated list
|
|
2281
|
+
roots share the collection keys. Sort keys are computed once per operation
|
|
2282
|
+
per query; lexical relevance and index order resolve remaining ties.
|
|
2283
|
+
Operations found only through a request or response field follow primary
|
|
2284
|
+
matches, and search names an authored tool whenever one serves an operation.
|
|
2285
|
+
A refused method outside a read/write tool's input schema is inspected through
|
|
2286
|
+
`_api_details` with its indexed method and path. Call validation remains closed:
|
|
2287
|
+
Gmail's 49 non-GET refusals do not add a method field or a write hatch, and Forms'
|
|
2288
|
+
refused DELETE remains outside its write enum. GitHub's refused raw PUT merge
|
|
2289
|
+
is likewise outside its write enum; its named guarded alternative is unaffected.
|
|
2290
|
+
Such schema refusals happen
|
|
2291
|
+
before authentication or I/O; details exposes the same reviewed reason as search.
|
|
2292
|
+
Every schema-reachable refused operation still fails with that reason before
|
|
2293
|
+
authentication or dispatch. This is policy inspection, never execution admission.
|
|
2294
|
+
`_api_read` omits
|
|
2295
|
+
`method` only when GET is its one reviewed read method.
|
|
2296
|
+
A standard list can declare `limit.values` for a vendor's discrete page sizes.
|
|
2297
|
+
The values must be unique positive integers within `max` and contain the default.
|
|
2298
|
+
The catalog declares the enum and decoding refuses other sizes before dispatch.
|
|
2299
|
+
A reviewed read-only POST list supplies `review`, like other reviewed POST reads.
|
|
2300
|
+
A reviewed OpenAPI pin may opt into `additionalResponseMediaTypes` to declare
|
|
2301
|
+
response fields from a vendor's nonstandard media labels. This changes field
|
|
2302
|
+
metadata only; the runtime still enforces its declared response format.
|
|
2303
|
+
|
|
2304
|
+
A provider may opt into `hatch.writeEnabled(options)` to omit its write hatch
|
|
2305
|
+
from a connection whose deployment configuration permits only reads. Discovery
|
|
2306
|
+
omits write-tool routing on that connection; reviewed reads do not gain write
|
|
2307
|
+
verdicts. The default remains enabled, and endpoint read-only policy still
|
|
2308
|
+
enforces every invocation.
|
|
2309
|
+
|
|
2310
|
+
The hatch's optional `pages` table declares an adapter for each indexed read
|
|
2311
|
+
that pages. `src/http/hatch-paging.ts` supplies Stripe forward lists
|
|
2312
|
+
(`has_more`, last `data` id → `starting_after`), Cloudflare
|
|
2313
|
+
(`result_info.page/total_pages`, falling back to `total_count/per_page/count`
|
|
2314
|
+
→ `page`), Vercel (`pagination.next` → `until`), and GitHub (quoted or unquoted
|
|
2315
|
+
Link `rel=next` → `page`). Stripe opts into indexed forward lists, reverse
|
|
2316
|
+
lists (`ending_before`, first safe row id), v1 search (`next_page`) and v2
|
|
2317
|
+
lists (a confined `next_page_url` supplying only `page`).
|
|
2318
|
+
If a Stripe v2 URL adds query parameters the original call omitted, the read
|
|
2319
|
+
keeps its data and returns `hasMore: true` without `nextCursor`; a continuation
|
|
2320
|
+
cannot be minted without guessing which vendor defaults to preserve. Changed
|
|
2321
|
+
explicit filters and URLs outside the confined origin/path remain refused.
|
|
2322
|
+
A declaration names the cursor parameter and `query` or `body`; it must
|
|
2323
|
+
exist in that operation's request contract. The response remains
|
|
2324
|
+
`{ status, data, page? }`, with `page: { hasMore, nextCursor?, param, in }`.
|
|
2325
|
+
With parameter-based continuation, put the opaque `nextCursor` back in the named
|
|
2326
|
+
parameter. The hatch binds it
|
|
2327
|
+
to its tool, content-hash index revision, method, path, other arguments and selected fields,
|
|
2328
|
+
canonicalizing nested object key order while preserving array order and scalar types.
|
|
2329
|
+
It decodes the cursor before index validation and sends only the vendor value. Paging is
|
|
2330
|
+
read after connection safety and before field selection; result hooks must
|
|
2331
|
+
retain the paging metadata. Only an adapter's explicitly requested response
|
|
2332
|
+
headers enter its answer, already sent-secret redacted. GitHub extracts just
|
|
2333
|
+
the page from a Link URL with the same origin and unchanged filters, including
|
|
2334
|
+
GitHub's repository-ID paths; it never follows the URL or changes the original
|
|
2335
|
+
request path. Filter comparison uses parsed scalar parameter multisets,
|
|
2336
|
+
preserving duplicate counts and tolerating order and URL-encoding differences.
|
|
2337
|
+
Adapter continuations use the same root-store HMAC key and admitted
|
|
2338
|
+
connection/personal-owner binding as discovery cursors. The signed v2 token's
|
|
2339
|
+
claims preserve the vendor value's string/number type and pass the request-secret
|
|
2340
|
+
encoder. An unpaged hatch keeps its existing result schema.
|
|
2341
|
+
The optional `pageCursor: "argument"` declaration separates a top-level
|
|
2342
|
+
signed `cursor` from raw first-request anchors. Stripe allows a raw
|
|
2343
|
+
`starting_after` or `ending_before` record id in `query` only without cursor;
|
|
2344
|
+
both directions or a cursor with any raw anchor are refused before dispatch.
|
|
2345
|
+
Returned tokens authenticate the selected adapter parameter and value as well
|
|
2346
|
+
as the call. Search/v2 raw page tokens cannot replace cursor. Providers without
|
|
2347
|
+
this declaration keep parameter-based continuation.
|
|
2348
|
+
An explicit initialAnchor adapter on parameter-based paging can retain a numeric
|
|
2349
|
+
timestamp as a first-page time-window anchor; opaque string continuations still
|
|
2350
|
+
require a signed token.
|
|
2351
|
+
The generated Paging convention follows the read tool's input schema: tools
|
|
2352
|
+
that take `cursor` restore `page.nextCursor` as that top-level argument; tools
|
|
2353
|
+
without it restore the token in `query[page.param]` or `body[page.param]`, as
|
|
2354
|
+
`page.in` specifies. `kit.hatch.paging` still validates declared mode values as
|
|
2355
|
+
a sanity check; it does not select the generated continuation instruction.
|
|
2356
|
+
An unsafe continuation keeps the safe page data with `hasMore: true` and no
|
|
2357
|
+
`nextCursor`.
|
|
2358
|
+
A reviewed `HatchBinding` can supply vendor encoding/origin routing through
|
|
2359
|
+
the same Connection API; the shared hatch retains resolution, verdicts,
|
|
2360
|
+
index/fields validation, withheld-value admission, paging and projection.
|
|
2361
|
+
A binding may resolve configured query routing before validation and signed
|
|
2362
|
+
cursor binding. Vercel uses this to apply its teamId default and deliberate
|
|
2363
|
+
teamId/slug/null overrides consistently with named tools and uploads.
|
|
2364
|
+
A binding may opt into `truncated: true` to supply reviewed size-cut paths
|
|
2365
|
+
(such as `data.content`) in its answer. The hatch then declares and propagates
|
|
2366
|
+
an optional top-level `truncated: string[]` on reads and writes. Without the
|
|
2367
|
+
opt-in, the schemas and results are unchanged; size-cut metadata is not inferred
|
|
2368
|
+
from vendor booleans or added to other providers. A separate `warnings: true`
|
|
2369
|
+
opt-in declares and preserves reviewed `warnings: string[]` beside `data`, even
|
|
2370
|
+
when `fields` narrows the vendor body.
|
|
2371
|
+
|
|
2372
|
+
A binding may expand deployment-owned path defaults (Cloudflare) or canonicalize
|
|
2373
|
+
repository contents paths (GitHub) before indexed resolution, and may prepare a
|
|
2374
|
+
reviewed text/NDJSON body before validation. Both the original
|
|
2375
|
+
and resolved paths pass withheld-value admission. Closed request-header enums
|
|
2376
|
+
are opt-in; selected headers join the paging cursor's call identity. Text body
|
|
2377
|
+
inputs are opt-in too. A provider with bounded call-owned scope lookups declares
|
|
2378
|
+
its fixed write steps, preserving the resource call's single-attempt slot.
|
|
2379
|
+
A page adapter may declare `cursorInputMessage` to give provider-specific repair
|
|
2380
|
+
guidance when an unsigned raw value is supplied; absent keeps the standard refusal.
|
|
2381
|
+
An optional `fieldsRoot` supplies vendor-envelope selection guidance and the
|
|
2382
|
+
corresponding generated convention; it never changes the declared response roots.
|
|
2383
|
+
Field selection on every provider, including named reads, accepts optional `[]`
|
|
2384
|
+
array notation (`result.id` and `result[].id` are equivalent); this normalization
|
|
2385
|
+
is shared behavior, independent of `fieldsRoot`.
|
|
2386
|
+
Bindings retain the hatch's other-server and JSON/form body-type guards by
|
|
2387
|
+
default. `allowOtherServers: true` and `allowOtherBodyTypes: true` explicitly
|
|
2388
|
+
replace those guards with the binding's reviewed routing or encoding checks.
|
|
2389
|
+
Stripe uses it for nested v1 form parameters, v2 JSON, account/context headers
|
|
2390
|
+
and the confined PDF origin. An optional hatch idempotency declaration uses
|
|
2391
|
+
the kit write builder's checked key hook: Stripe POST writes take or generate
|
|
2392
|
+
keys, while DELETE and the read-only invoice-preview POST send none.
|
|
2393
|
+
Redacted or withheld cursor values are refused both before minting a continuation
|
|
2394
|
+
and after restoring a decoded continuation, before dispatch. The shared
|
|
2395
|
+
`refuseWithheldValues` check runs on the restored query/body: paths and queries
|
|
2396
|
+
refuse marker containment, while bodies retain the whole-value/link rule.
|
|
2397
|
+
`src/http/vendor-index.ts` derives that index from a kit vendor's own
|
|
2398
|
+
declarations when the vendor has no pinned OpenAPI document: `form()` request
|
|
2399
|
+
contracts, `Summary` annotations and lenient success schemas become the
|
|
2400
|
+
index's parameters, bodies, summaries and declared fields (optional
|
|
2401
|
+
`OpenApiData.fields`). Kit facts carry the vendor's declared
|
|
2402
|
+
`VendorErrors.meanings` and an `idempotencyKey` checked against the write
|
|
2403
|
+
builders at construction, which the generated Conventions state.
|
|
2404
|
+
|
|
2405
|
+
Hatch request paths and query keys/values refuse any containment of the shared
|
|
2406
|
+
`[redacted]` marker, including decoded spellings. Body fields and kit write
|
|
2407
|
+
arguments refuse a string that equals the marker after decoding, or carries a
|
|
2408
|
+
withheld link (including origin-only links ending in the marker). Longer free
|
|
2409
|
+
text such as `the log says: Authorization: Bearer [redacted]` is accepted.
|
|
2410
|
+
Admission iteratively visits keys and values before the write program runs;
|
|
2411
|
+
it checks agent arguments, not whole bodies later composed from internal
|
|
2412
|
+
reads. The kit connection also checks actual typed path parameters and query
|
|
2413
|
+
values before transport. Legacy non-kit tools retain their link-only admission;
|
|
2414
|
+
arbitrary binary/base64 bytes are not decoded by this scalar rule. The kit
|
|
2415
|
+
sets `refusesWithheld` for the standard hatch, enabling the generated convention.
|
|
2416
|
+
Declared error-code facts use the closed `ConnectorCallErrorCode` set at
|
|
2417
|
+
construction (INV-11); classification callbacks are not run at construction.
|
|
2418
|
+
|
|
2419
|
+
Bound cursors MAC a canonical tuple of version, admitted connection id,
|
|
2420
|
+
personal registry's credential-owner partition (null for shared registries),
|
|
2421
|
+
canonical tool/argument digest and continuation payload with HMAC-SHA-256.
|
|
2422
|
+
The canonical envelope spelling is required too. Hatch discovery binds a
|
|
2423
|
+
SHA-256 content hash of the index (operations, details, fields and routing
|
|
2424
|
+
metadata), alongside its declared label; the displayed revision label remains
|
|
2425
|
+
unchanged. Index edits invalidate old continuations even without a version
|
|
2426
|
+
bump. Search offsets, details pieces/chunks and surrogate boundaries are
|
|
2427
|
+
validated. Tampering or a changed scope/index fails `invalid_args`; changing
|
|
2428
|
+
only `limit` remains allowed.
|
|
2429
|
+
|
|
2430
|
+
One random 256-bit signing key is initialized lazily with compare-and-set
|
|
2431
|
+
in the durable root `continuationKeys` family. Only resolved, nonextractable
|
|
2432
|
+
keys are cached per store; no request promise or key value is logged. Registry
|
|
2433
|
+
`rootStorage` preserves the explicit deployment root across personal partitions
|
|
2434
|
+
independently of `catalogStorage`; standalone API connectors bind their own id
|
|
2435
|
+
and use their supplied store. Every deployment resolves a store, so no unsigned
|
|
2436
|
+
fallback exists. SQL-backed cursors survive isolate/process changes; memory
|
|
2437
|
+
storage loses them with the store. Key-load outages or corrupt stored keys
|
|
2438
|
+
produce retryable `unavailable`, naming the cursor signing key without its
|
|
2439
|
+
value. See [cursor-key recovery](./deploying.md#recover-a-cursor-signing-key):
|
|
2440
|
+
after repairing/deleting a corrupt key, restart every process/isolate to clear
|
|
2441
|
+
cached keys. Pre-upgrade cursors require a new first page.
|
|
2442
|
+
|
|
2443
|
+
The helper derives connection metadata and probe messages from
|
|
2444
|
+
validated options and still accepts complete legacy usage guides for compatibility.
|
|
2445
|
+
All maintained providers have completed the standard-surface migration.
|
|
2446
|
+
Candidate checks can refuse before authentication while
|
|
2447
|
+
preserving the provider's exact diagnostic. Its optional `authScope(options)`
|
|
2448
|
+
selector fixes OAuth grant ownership from deployment mode at construction;
|
|
2449
|
+
CCB uses shared System Auth grants and personal Identity Auth grants. Tool
|
|
2450
|
+
arguments cannot select a grant's owner. On Node, every maintained-provider
|
|
2451
|
+
request uses the owned sender `listen()` installs, reads included. Without it,
|
|
2452
|
+
maintained-provider writes and reviewed POST reads are refused before sending;
|
|
2453
|
+
focused suites exercising those requests configure the sender themselves. Anything a connection
|
|
2454
|
+
caches across calls, such as its typed HttpApi client, is built with no
|
|
2455
|
+
services, because Effect constructors close over the context they run in and a
|
|
2456
|
+
cached value would otherwise keep its first caller's request alive (INV-7).
|
|
2457
|
+
|
|
2458
|
+
The optional `grant(options)` factory supplies a renewable machine-login
|
|
2459
|
+
`Grant`, exclusive with static OAuth. It is built outside caller contexts;
|
|
2460
|
+
each send binds the active call to its token, refresh and rejected effects.
|
|
2461
|
+
`settleWrite` decides the outcome and handoff.
|
|
2462
|
+
Reads replay once after renewal; writes are never resent.
|
|
2463
|
+
|
|
2464
|
+
A machine grant may also declare `grantRenewal` modes, with an optional
|
|
2465
|
+
`Grant.discard(token)` that forgets exactly that refused token and keeps any
|
|
2466
|
+
newer one. `replayRejection: "answer"` discards the token a replayed read was
|
|
2467
|
+
refused with and hands that 401 to the vendor's status table, so the message
|
|
2468
|
+
comes from the bounded, sent-secret-redacted body instead of `rejected`.
|
|
2469
|
+
`writeRenewal: "discard"` discards a write's refused token without renewing:
|
|
2470
|
+
no token request is sent, nothing is recorded as renewed, the write's 401 is
|
|
2471
|
+
answered, and `settleWrite` decides its outcome and handoff. Neither mode
|
|
2472
|
+
resends a write, and the defaults are unchanged. Google Workspace uses both,
|
|
2473
|
+
because its delegated tokens are minted per call and a refused write only
|
|
2474
|
+
needs the next call to mint again.
|
|
2475
|
+
|
|
2476
|
+
All Google Workspace products use `src/providers/_shared/google/kit.ts`, which
|
|
2477
|
+
puts the delegated service account on a machine grant. `workspace.ts` owns
|
|
2478
|
+
shared options, connection parsing and subject resolution; `service-account.ts`
|
|
2479
|
+
owns token exchange and caching. JSON requests use `googleClient`, and
|
|
2480
|
+
Drive's `driveClient` adds binary and charset-aware text projections over
|
|
2481
|
+
that adapter. The subject resolves once per call from deployment config and
|
|
2482
|
+
the admitted caller, and the private token exchange keeps its in-memory
|
|
2483
|
+
cache, so only token strings and expiries cross requests. Each product
|
|
2484
|
+
declares its operations, and a value-safety resolver refuses any path it does
|
|
2485
|
+
not declare. Revision-guarded writes set the
|
|
2486
|
+
`revisionGuarded` request flag that Google's error table reads. The completed
|
|
2487
|
+
product migrations use Effect-declared named tools and indexed
|
|
2488
|
+
hatches. Fixed write steps are snapshotted at construction. Docs declares a
|
|
2489
|
+
create and its optional initial-text edit as separate steps, each sent at most
|
|
2490
|
+
once.
|
|
2491
|
+
|
|
2492
|
+
All six Google Workspace products use the shared standard-surface support.
|
|
2493
|
+
`scripts/google-discovery.mjs` converts
|
|
2494
|
+
archived exact-byte official Discovery documents into deterministic OpenAPI.
|
|
2495
|
+
The primary vendored pin retains the official URL, revision and source digest;
|
|
2496
|
+
its `discovery` relation names the sibling source pin and normalizer format.
|
|
2497
|
+
The sole retained document is `evidence/discovery.document.json`; no converted
|
|
2498
|
+
`openapi.document.json` is retained for these pins. Generation and offline
|
|
2499
|
+
freshness validate the original bytes against `discovery.json` and the primary
|
|
2500
|
+
pin, then derive the conversion in memory, including reviewed summary overrides.
|
|
2501
|
+
Its digest uses two-space JSON indentation and a terminal newline; the generated
|
|
2502
|
+
module records that independent document digest, preserving freshness checks
|
|
2503
|
+
for changes to the normalizer or summaries. This supersedes Decision 0011's
|
|
2504
|
+
retained-conversion requirement for deterministic Discovery evidence. Frozen
|
|
2505
|
+
measurements read those same provider-owned originals but validate the existing
|
|
2506
|
+
`test/fixtures/google-discovery/manifest.json` digests independently: a provider
|
|
2507
|
+
pin update must fail the historical measurement rather than silently rebase it.
|
|
2508
|
+
That reviewed pin-change PR must re-measure and update the product's manifest
|
|
2509
|
+
entry and the Forms #1009 drift-check digests when applicable, or retire the
|
|
2510
|
+
measurement. Full source method inventory
|
|
2511
|
+
and canonical source-to-relative-path mappings survive normalization. Gmail's
|
|
2512
|
+
source `userId` becomes deployment-owned `me`; runtime paths are relative to
|
|
2513
|
+
the existing connection root. Reserved Slides ids remain one encoded segment.
|
|
2514
|
+
Flat-path aliases are reconciled with the declared path parameters. Method-
|
|
2515
|
+
specific required annotations specialize only affected reachable input schemas,
|
|
2516
|
+
including nested and recursive references. Deprecation metadata is retained;
|
|
2517
|
+
compact details opt in with `deprecations: true`. Live Discovery drift compares
|
|
2518
|
+
sorted-key JSON and revision, independently of archived-byte integrity.
|
|
2519
|
+
Only safe JSON query globals enter the contract. Source bytes are contributor
|
|
2520
|
+
evidence and never runtime imports.
|
|
2521
|
+
|
|
2522
|
+
An index can retain `requestSchemas`, a lazy referenced graph for complete
|
|
2523
|
+
request details, independently of the compact depth-bounded admission checker.
|
|
2524
|
+
Details attach the reachable `$defs`, preserving recursion, required fields,
|
|
2525
|
+
dynamic maps and parent schema metadata through the existing bounded
|
|
2526
|
+
continuations. This does not make the compact checker a full JSON Schema
|
|
2527
|
+
validator. Product admission still needs literal reviewed verdicts and
|
|
2528
|
+
independent batch-kind, revision and resource guards. Google indexes explicitly
|
|
2529
|
+
set `pathMatching: "uri-template"` on `OperationIndex` to handle encoded literal
|
|
2530
|
+
collisions and parameter action suffixes. Other indexes retain exact literal
|
|
2531
|
+
segments and their existing refusal ordering. The matching mode also binds
|
|
2532
|
+
discovery cursors. `googleClient(connection, index)` requires each named request's
|
|
2533
|
+
declared `template` and refuses a substituted id resolving to another operation
|
|
2534
|
+
before grant evaluation. Every maintained Google product uses this indexed
|
|
2535
|
+
client; there is no unindexed settlement mode.
|
|
2536
|
+
Both the client and `googleHatchBinding(connection, { index, review })` require
|
|
2537
|
+
the explicit URI-template mode at construction. The hatch requires the binding's
|
|
2538
|
+
exact index identity. Clients and Google bindings reject a missing
|
|
2539
|
+
index with an explicit construction error. The provider-standard suite checks
|
|
2540
|
+
actual hatch construction through every Google product fixture, requiring its
|
|
2541
|
+
Google binding with the same connection and index. The Google binding
|
|
2542
|
+
additionally refuses percent-encoded unreserved characters and colons before
|
|
2543
|
+
dispatch.
|
|
2544
|
+
|
|
2545
|
+
The generator's `allSuccessResponses: true` option unions JSON response fields
|
|
2546
|
+
from every 2xx. Existing providers keep the first JSON success contract until
|
|
2547
|
+
their own reviewed regeneration; no committed provider index changes here.
|
|
2548
|
+
|
|
2549
|
+
`src/providers/_shared/google/hatch.ts` supplies JSON framing over the existing
|
|
2550
|
+
confined `Connection.sendPath`. It retains the hatch's origin/media guards,
|
|
2551
|
+
delegated grant, safety and field-selection pipeline. Its optional `wrapSend`
|
|
2552
|
+
receives a detached copy of the admitted request (including its body) and a
|
|
2553
|
+
zero-argument inner send. The binding retains its original `send` function;
|
|
2554
|
+
wrappers run only after hatch admission and can dispatch the captured request
|
|
2555
|
+
at most once. A second send is an internal defect; after a write dispatch its
|
|
2556
|
+
outcome remains unknown. Opting into this wrapper also enables envelope-level
|
|
2557
|
+
warnings, so request-dependent save advice survives field selection without
|
|
2558
|
+
changing vendor data. Mutating the inspection copy cannot change its path, body,
|
|
2559
|
+
query, revision flag or safety policy. Products use this seam for response
|
|
2560
|
+
semantics that depend on the request, such as independent comment saves.
|
|
2561
|
+
The `googlePageToken`
|
|
2562
|
+
adapter is separately opt-in by exact list key with `pageCursor: "argument"`.
|
|
2563
|
+
It treats missing/empty `nextPageToken` as terminal, rejects malformed tokens,
|
|
2564
|
+
and continues empty pages when a nonempty token exists. `query.fields` is a
|
|
2565
|
+
Google vendor mask; the adapter preserves `nextPageToken` on the wire. The
|
|
2566
|
+
token must be selected as a complete top-level field; `nextPageToken/x` does
|
|
2567
|
+
not select it. The top-level `fields` argument narrows safe result data only after paging. Omit
|
|
2568
|
+
`pageToken` initially and restore the signed `page.nextCursor` as `cursor`;
|
|
2569
|
+
the raw token in safe vendor data is not accepted as continuation input.
|
|
2570
|
+
For example, a direct value-mode result has outer `result.data`, the hatch's
|
|
2571
|
+
`result.data.page.nextCursor`, and vendor JSON at `result.data.data`:
|
|
2572
|
+
`call_tool({ address: "crm.forms_api_read", args: { path:
|
|
2573
|
+
"/forms/form_1/responses", cursor: previous.data.page.nextCursor },
|
|
2574
|
+
resultMode: "value" })`. This is an envelope illustration for an opted-in
|
|
2575
|
+
provider, not a claim that the Forms migration has shipped.
|
|
2576
|
+
|
|
2577
|
+
`googleErrors(product, { standard: true })` declares the product's 404 meaning
|
|
2578
|
+
and uses common status fallbacks; revision-conflict meanings require a
|
|
2579
|
+
separate declaration. `googleClient` preserves failure phase
|
|
2580
|
+
metadata for partial-create recovery while leaving settlement and wording to
|
|
2581
|
+
the kit. Its incomplete-body wording covers JSON and binary reads without
|
|
2582
|
+
claiming that binary success was expected to be JSON. `googleGrant` still uses
|
|
2583
|
+
the nonstandard `googleErrors(product)` mapping for delegated-token rejection;
|
|
2584
|
+
that grant contract is separate from resource-request settlement.
|
|
2585
|
+
|
|
2586
|
+
Drive's two-request reads also declare fixed steps: `get_file_content` uses
|
|
2587
|
+
`metadata` and `content`, and non-root `list_folder_items` uses `folder` and
|
|
2588
|
+
`files`. These reads still send both GETs when deployment classification
|
|
2589
|
+
serves them as writes; each step can leave only once.
|
|
2590
|
+
Gmail declares separate initial and body-download steps for `get_message`,
|
|
2591
|
+
`get_draft`, and `get_attachment`; `search_threads` and `list_drafts` name
|
|
2592
|
+
each of up to 25 summary reads, and `get_thread` names each stored body in
|
|
2593
|
+
Gmail's 100-message conversation limit. Serving these reads as writes keeps
|
|
2594
|
+
every request available once.
|
|
2595
|
+
The request adapter reports how far a failed request got; the kit maps the
|
|
2596
|
+
failure and the core decides every write's outcome. Gmail's auxiliary draft and reply lookups pass both `effect: "read"`
|
|
2597
|
+
and a construction-declared step: the guarded client's early reservation uses
|
|
2598
|
+
that named slot, while the ledger marks the request auxiliary. A failed lookup
|
|
2599
|
+
keeps its read verdict and cannot make an unsent draft write unknown.
|
|
2600
|
+
|
|
2601
|
+
Sheets declares its seven API methods in `src/providers/sheets/api.ts`; its
|
|
2602
|
+
kit tools retain the private digest-bound cell cursor, legacy A1 path encoding,
|
|
2603
|
+
and create/append recovery advice. Its projections and the Google failure
|
|
2604
|
+
classifier read fields by the names Google gave them, which the kit's parse pass
|
|
2605
|
+
keeps. Its catalog and guide stay unchanged in step 1.
|
|
2606
|
+
|
|
2607
|
+
Google's request adapter also offers a reviewed binary send. It marks the
|
|
2608
|
+
operation `googleBytes` for the product's `binaryBodies` selector and forwards
|
|
2609
|
+
an optional `maxBytes` to the success-only prefix reader. It returns the
|
|
2610
|
+
connection's safe answer: bytes pass the existing sent-secret encoding before
|
|
2611
|
+
any result hook, then value safety before the product reads them. Products
|
|
2612
|
+
keep charset decoding and special error-status interpretation local.
|
|
2613
|
+
|
|
2614
|
+
An optional `tagConnector(connector, options)` records construction facts
|
|
2615
|
+
about the built connector, once, before the registry can serve it. Its return
|
|
2616
|
+
value is ignored, so it cannot replace or wrap the connector. Google Workspace
|
|
2617
|
+
uses it to tag whose account the connector acts as, which the registry reads to
|
|
2618
|
+
hide a mapping-subject connector from agents in a pool without an owner.
|
|
2619
|
+
|
|
2620
|
+
GitHub's tools and allowlisted indexed hatch use kit read/write builders and a confined
|
|
2621
|
+
Connection while its App authentication and scope-verification transport stays
|
|
2622
|
+
private. Its tokens are partitioned by App/key, installation, repository IDs,
|
|
2623
|
+
permissions and purpose. A resource 401, or a 403 without rate-limit evidence,
|
|
2624
|
+
invalidates the exact bearer even when its body cannot be read. The next call
|
|
2625
|
+
mints lazily; there is no inline renewal or replay and no renewable Grant.
|
|
2626
|
+
Only completed scalar cache data crosses requests; private signing imports
|
|
2627
|
+
and all transport operations remain request-owned.
|
|
2628
|
+
|
|
2629
|
+
The optional `probe.run(connection, options)` program replaces the operation
|
|
2630
|
+
probe when authentication needs a different read or a private login transport.
|
|
2631
|
+
It is built once at provider construction outside caller contexts, using the
|
|
2632
|
+
confined Connection and validated options. Execution uses the same anonymous,
|
|
2633
|
+
uncharged candidate services, accounting, settlement, failure mapping and
|
|
2634
|
+
finalizer. An empty write-capacity map refuses Connection writes before dispatch.
|
|
2635
|
+
Every defect from a run probe or its hooks uses the generic withheld notice,
|
|
2636
|
+
even without a kit dispatch, because a private transport leaves no kit visibility
|
|
2637
|
+
evidence. An open operation probe retains its original defect.
|
|
2638
|
+
|
|
2639
|
+
`src/providers/_shared/rest/kit.ts` owns the shared REST call shapes, pinned
|
|
2640
|
+
schema conversion and indexed vendor declarations. `indexedVendor` derives operation
|
|
2641
|
+
metadata from the index and builds the full manifest lazily; the typed client
|
|
2642
|
+
contains only named operations and probes. Bulk indexed declarations preserve
|
|
2643
|
+
the OpenAPI brace paths verbatim and carry explicit parameter Schemas. They
|
|
2644
|
+
serve the manifest only: typed sends refuse an undeclared operation name,
|
|
2645
|
+
and indexed hatches dispatch through `sendPath`. Parameter names outside
|
|
2646
|
+
`[A-Za-z0-9_-]+` fail construction. Manifest/index operation-set equality is
|
|
2647
|
+
checked independently of drift digests, which still use full vendor contracts.
|
|
2648
|
+
|
|
2649
|
+
A tool may opt into `inputOrder: "original"` when legacy request bytes depend
|
|
2650
|
+
on argument insertion order. After normal successful decoding, the builder
|
|
2651
|
+
copies only decoded values in the original key order, recursively through
|
|
2652
|
+
objects and arrays. Dropped keys stay dropped and decoder-introduced keys
|
|
2653
|
+
append at the end. Capacity selectors, idempotency and the tool program all
|
|
2654
|
+
receive that reordered decoded value. The default preserves the decoder's
|
|
2655
|
+
existing order; the option retains no original values and skips no validation.
|
|
2656
|
+
|
|
2657
|
+
The kit write builder emits `readOnlyHint:false` and its explicit `destructive`
|
|
2658
|
+
verdict as `destructiveHint`; `destructive:false` describes an additive write.
|
|
2659
|
+
A reviewed `idempotent` declaration emits `idempotentHint` independently of a
|
|
2660
|
+
transport idempotency key. These hints describe behavior; write routing and
|
|
2661
|
+
endpoint restrictions still apply. For example, a tool that only creates a new
|
|
2662
|
+
note can declare `destructive:false` without promising idempotency.
|
|
2663
|
+
|
|
2664
|
+
Kit tools and the indexed hatch may declare a `writeSteps` map whose values are `"once"`.
|
|
2665
|
+
Construction freezes the map; every named step sends at most once and an
|
|
2666
|
+
undeclared step is refused before dispatch. Omitting it retains the single
|
|
2667
|
+
implicit write step. Auxiliary ownership reads in a write call retain that
|
|
2668
|
+
call's write classification and consume their declared lookup steps, so the
|
|
2669
|
+
resource write cannot be resent.
|
|
2670
|
+
|
|
2671
|
+
Reviewed response policies can select text using only the redacted, normalized
|
|
2672
|
+
Content-Type. A separate `binaryBodies` review opts into bounded byte reads.
|
|
2673
|
+
Only the connection sees those bytes: it calls `RequestSecrets.encode` before
|
|
2674
|
+
constructing the legacy `{ contentType, bytes, base64 }` value. Undeclared
|
|
2675
|
+
binary bodies retain the JSON-only failure. Fixed secondary origins use a
|
|
2676
|
+
separate kit Connection built during provider construction, with their own
|
|
2677
|
+
confinement and the same authentication and result safety.
|
|
2678
|
+
|
|
2679
|
+
A definition may derive authentication and default admission from validated
|
|
2680
|
+
options. Probes may derive their request from those options and describe a mapped
|
|
2681
|
+
failure (for example, an accepted key lacking account-read permission). Public
|
|
2682
|
+
routing/version headers remain request or constant headers, never authentication
|
|
2683
|
+
secrets. An optional positive `timeoutMs` retains a vendor's request and body
|
|
2684
|
+
deadline. A path request opting into `emptyForm: true` retains an explicitly empty form on the wire; other empty form declarations keep their previous omission.
|
|
2685
|
+
|
|
2686
|
+
`PathRequest.rawBody` accepts a string, `Uint8Array` or `FormData`; it is
|
|
2687
|
+
exclusive with JSON, form, `emptyForm` and item/bulk identities. Conflicting
|
|
2688
|
+
combinations fail construction where possible and otherwise refuse before
|
|
2689
|
+
dispatch. Framing uses Effect's text, byte-array or form-data body helpers.
|
|
2690
|
+
An explicit Content-Type is preserved exactly; multipart callers omit it so
|
|
2691
|
+
fetch generates its boundary. This field does not change classification,
|
|
2692
|
+
write-step accounting or the sent-secret set, and existing size ceilings apply.
|
|
2693
|
+
|
|
2694
|
+
A send may opt into a success-only `prefix` read with a byte ceiling, an optional
|
|
2695
|
+
tighter time limit and a boolean chunk-stop callback. Failure responses retain
|
|
2696
|
+
the ordinary bounded status and withheld-error path. Early success cancels the
|
|
2697
|
+
unread stream and reports `prefix: { truncated, bytes, stopped }`; accepted bytes
|
|
2698
|
+
still pass sent-secret redaction, the result hook and value safety before any
|
|
2699
|
+
projection. The callback runs with an empty Effect context, receives a copy of
|
|
2700
|
+
the chunk, and cannot transform the answer. Its defects receive the same
|
|
2701
|
+
withheld-text rewrap as result hooks. Invocation cancellation remains authoritative.
|
|
2702
|
+
Reviewed binary prefixes retain bytes through the same sent-secret encoding
|
|
2703
|
+
as whole binary bodies, without a text decode. Only that binary path accepts
|
|
2704
|
+
a zero-byte prefix, which keeps empty bytes and the ordinary prefix facts.
|
|
2705
|
+
|
|
2706
|
+
CCB's static OAuth bridge opts into `oauthWrite401: "grant"`. An optional
|
|
2707
|
+
`refreshOnce` mapper receives only renewal facts and reuses the core's
|
|
2708
|
+
`unsentAfterUnauthorized` diagnostic, preserving the grant's authorization
|
|
2709
|
+
message and the message for a renewed credential whose write was not replayed.
|
|
2710
|
+
The error retains the resource dispatch's provenance; `settleWrite` decides
|
|
2711
|
+
the final outcome. The default middleware behavior and renewal ownership stay
|
|
2712
|
+
unchanged.
|
|
2713
|
+
|
|
2714
|
+
The static compatibility option caps discarded 401 challenges with the shared
|
|
2715
|
+
OAuth challenge limit before renewal. A guarded client's optional
|
|
2716
|
+
`maxUnauthorizedResponseBytes` applies to declared and streamed bodies;
|
|
2717
|
+
`read(response, maxBytes)` can narrow a read's ceiling further. The middleware
|
|
2718
|
+
maps an oversized challenge using its own recorded dispatch, including a
|
|
2719
|
+
declared-size refusal before the response wrapper exists. It never renews
|
|
2720
|
+
after that refusal.
|
|
2721
|
+
|
|
2722
|
+
The kit's parse pass redacts every value of a parsed JSON body and keeps its
|
|
2723
|
+
object keys. Value safety drops and redacts fields, and projections navigate
|
|
2724
|
+
them, by the names the vendor gave them, so a private argument sharing part of
|
|
2725
|
+
a name such as `client_secret` or `paragraph` cannot hide the field from the
|
|
2726
|
+
rule that withholds it or erase the structure a projection reads. A result
|
|
2727
|
+
hook that redacts again does so the same way. Text, binary bodies and JSON
|
|
2728
|
+
embedded inside a string are values and retain ordinary redaction. Response
|
|
2729
|
+
headers keep their names, which `Headers` must accept, and their values are
|
|
2730
|
+
redacted together. Keys are checked once value safety and the projections have
|
|
2731
|
+
run: the `api()` boundary and the invocation apply payload rules to the
|
|
2732
|
+
connector's result and failures, keys included, before the agent boundary's
|
|
2733
|
+
pass. Until then a key is the vendor's text, so a provider that serializes part
|
|
2734
|
+
of a body and cuts it redacts the serialized text first (`redactBeforeCut`), as
|
|
2735
|
+
Breeze's in-band failure text and Slides' raw chunks do. A private value of four
|
|
2736
|
+
to seven units that a vendor echoes only as an object key is therefore matched
|
|
2737
|
+
once, at the connector boundary; the agent boundary's envelope rules do not
|
|
2738
|
+
match values that short.
|
|
2739
|
+
|
|
2740
|
+
Redacted values can still be decision inputs. `textBodies` and `binaryBodies`
|
|
2741
|
+
see a Content-Type that redaction altered as `application/json`, so no altered
|
|
2742
|
+
type routes a body around ResultSafety as one string or as bytes; a body that
|
|
2743
|
+
is not JSON is then unreadable. Value safety's own decisions on values fail
|
|
2744
|
+
closed the same way (see "Value safety for REST vendors").
|
|
2745
|
+
|
|
2746
|
+
For an unreadable successful body, the shared mapper appends an unknown-outcome
|
|
2747
|
+
and check-before-repeating sentence for the call's own write request, including
|
|
2748
|
+
writes transported with GET. An auxiliary read lookup inside a write call
|
|
2749
|
+
keeps its read message; a failed lookup does not establish that the write left.
|
|
2750
|
+
`VendorErrors.unreadableWrite` can override the complete write message.
|
|
2751
|
+
Reads keep their existing wording. Overflow's
|
|
2752
|
+
unreviewed text responses use its write override; its reviewed health text and
|
|
2753
|
+
refund acknowledgement remain accepted.
|
|
2754
|
+
|
|
2755
|
+
A vendor may declare `encodedFields`: exact dotted JSON paths with whole
|
|
2756
|
+
array segments (`payload.parts[*].body.data`), `encoding: "base64url"`, an
|
|
2757
|
+
optional `sizePath` addressing the same array elements, and a one-line review.
|
|
2758
|
+
Construction freezes the declaration. Before ordinary whole-answer redaction,
|
|
2759
|
+
the kit decodes each valid field and redacts its decoded text and bytes through
|
|
2760
|
+
the same request-secret service. Unchanged fields retain their original
|
|
2761
|
+
spelling; changed fields retain the original alphabet and padding style when
|
|
2762
|
+
re-encoded. Both standard and URL-safe alphabets are accepted with valid padding. Invalid base64url stays on
|
|
2763
|
+
the ordinary redaction path. A declared size is checked against the original
|
|
2764
|
+
decoded byte length first; a mismatch also retains ordinary redaction. Size
|
|
2765
|
+
values stay unchanged. This never exempts the encoded field or its surrounding
|
|
2766
|
+
JSON from the generic redaction pass.
|
|
2767
|
+
|
|
2768
|
+
Result hooks alone may call `encodedFieldProvenance(answer, concretePath)`.
|
|
2769
|
+
Its private WeakMap is keyed by the parsed, redacted answer; each frozen
|
|
2770
|
+
entry holds only `originalLength`, optional `sizeValid`, and `changed`.
|
|
2771
|
+
Provenance contains no content and is never serialized into facts, messages
|
|
2772
|
+
or logs. Gmail copies this metadata privately for its decoded body containers:
|
|
2773
|
+
text renders after redaction and before cuts, while changed binary content is
|
|
2774
|
+
withheld in full. Its original byte counts still govern attachment validation.
|
|
2775
|
+
Vendors without this declaration retain the existing parsing path.
|
|
2776
|
+
Gmail refuses to update a draft whose Content-Disposition or any header name
|
|
2777
|
+
was redacted, because it cannot prove that rebuilding the message would retain
|
|
2778
|
+
its headers and attachments.
|
|
2779
|
+
|
|
2780
|
+
Credential descriptions may derive from validated connection options, and a
|
|
2781
|
+
probe may reject a successful answer that identifies the wrong account.
|
|
2782
|
+
Transport diagnostics use the vendor error label rather than the provider
|
|
2783
|
+
definition's title. A vendor may declare reviewed `textBodies` by status and
|
|
2784
|
+
operation: `text` retains decoded UTF-8 and `json-or-text` parses JSON with a
|
|
2785
|
+
text fallback. Other responses still require JSON. The bounded byte reader,
|
|
2786
|
+
sent-secret redaction, and withheld-error policy apply before text can reach
|
|
2787
|
+
vendor wording or a result hook; a legacy hatch's text contract does not admit
|
|
2788
|
+
malformed JSON on named tools.
|
|
2789
|
+
|
|
2790
|
+
The helper's connection-specific `transport(options)` supplies constant
|
|
2791
|
+
headers and final-URL admission outside any caller context. Admission receives
|
|
2792
|
+
the request method and encoded text body before authentication, so product
|
|
2793
|
+
exclusions can inspect JSON:API writes. `sendPath` accepts per-request headers;
|
|
2794
|
+
typed operations can declare header Schemas. Authentication still owns its
|
|
2795
|
+
headers and applies last. A provider's default call-admission policy yields to
|
|
2796
|
+
an explicit deployment policy. A typed refusal thrown by the probe's reply
|
|
2797
|
+
describer becomes the ordinary failed credential-test result.
|
|
2798
|
+
|
|
2799
|
+
`emptyRequired(schema)` preserves a legacy output object's explicit
|
|
2800
|
+
`required: []` through Effect's canonical JSON Schema rendering. It refuses
|
|
2801
|
+
an object with required properties; its decoder is unchanged. Closed empty
|
|
2802
|
+
input records render `properties: {}` and `required: []` explicitly.
|
|
2803
|
+
|
|
2804
|
+
A vendor may set `manifestBase` to prefix its documented operation paths.
|
|
2805
|
+
The prefix accepts literal path segments and whole `{param}` segments whose
|
|
2806
|
+
names contain only letters, digits and underscores. It is concatenated verbatim
|
|
2807
|
+
for the manifest; transport paths remain relative to the confined base URL,
|
|
2808
|
+
with no parameter interpolation. The catalog renderer
|
|
2809
|
+
collapses unions of bare object and array branches into a type array; branches
|
|
2810
|
+
with additional constraints retain `anyOf`.
|
|
2811
|
+
|
|
2812
|
+
Every answer passes through bounded reading, sent-secret redaction, vendor
|
|
2813
|
+
result handling, reviewed value safety and the deployment privacy projection
|
|
2814
|
+
before paging or provider projections can read it. Read methods beyond GET
|
|
2815
|
+
and HEAD require a review. Construction fixes each write step's capacity;
|
|
2816
|
+
the guarded transport reserves it before preparation and the shared core
|
|
2817
|
+
decides the whole call's write outcome. Dedicated upload builders wait for
|
|
2818
|
+
the Google Drive migration in C.7.
|
|
2819
|
+
|
|
2820
|
+
Reviewed path calls resolve their actual method and path through an indexed
|
|
2821
|
+
safety adapter; a provider-supplied template cannot bypass the policy.
|
|
2822
|
+
Unindexed paths require an explicit global review and no reviewed table;
|
|
2823
|
+
a global review never substitutes for a reviewed table's resolver. A uniform
|
|
2824
|
+
policy over legacy unindexed paths declares its own resolver. Sent-secret redaction
|
|
2825
|
+
covers response headers before result hooks can extract fragments. Withheld
|
|
2826
|
+
hooks admit only operation-bound status failures or unreadable bodies; other
|
|
2827
|
+
failures become defects whose text the API boundary withholds. Hook defects
|
|
2828
|
+
are rewrapped once at the tool or credential-probe edge when text is withheld,
|
|
2829
|
+
including defects from reviewed redaction, vendor error mapping, and the
|
|
2830
|
+
provider program that carry a `ConnectorCallError`. A failing visibility hook
|
|
2831
|
+
also withholds the defect's text; non-withheld operations retain their original
|
|
2832
|
+
defects.
|
|
2833
|
+
|
|
2834
|
+
### Production HTTP dispatch (contributor reference)
|
|
2835
|
+
|
|
2836
|
+
`src/server.ts` retains the origin refusal, HTTPS upgrade and security wrapper,
|
|
2837
|
+
then calls the private `src/routes/dispatch.ts` adapter. Each fetch handler owns
|
|
2838
|
+
two immutable public Effect HttpRouters: a human table selects CIMD, connect,
|
|
2839
|
+
OAuth callback, metadata, health and MCP programs; the agent table selects only
|
|
2840
|
+
the existing agent endpoint program. The raw agent-namespace guard runs before
|
|
2841
|
+
human dispatch. An unmatched human route runs optional UI fallthrough, wildcard
|
|
2842
|
+
OPTIONS metadata/CORS, or the deployment's final 404. Core selection uses the
|
|
2843
|
+
router directly, without scanning the other core modules. CIMD runs before UI;
|
|
2844
|
+
every other human program runs after optional UI, including its null fallthrough.
|
|
2845
|
+
Metadata retains MCP-before-provider discovery order.
|
|
2846
|
+
|
|
2847
|
+
The synchronous constructor does no routing work or I/O. The first existing
|
|
2848
|
+
edge fiber builds both fixed tables with public `make`, `addAll` and
|
|
2849
|
+
`asHttpEffect`. Only synchronous registration and cache publication use
|
|
2850
|
+
`Scheduler.PreventSchedulerYield` and an uninterruptible region. The cache
|
|
2851
|
+
check is inside that region, so even concurrent first requests with a 16-operation
|
|
2852
|
+
yield budget publish only two routers. Normal scheduling and interruption are
|
|
2853
|
+
restored before any application callback or I/O. Cached route effects capture
|
|
2854
|
+
only deployment programs; they read the private current `RouteContext` service
|
|
2855
|
+
when executed. No request, body, signal, Promise, client, Scope or request
|
|
2856
|
+
services are retained in the tables. The canonical request/call ownership in
|
|
2857
|
+
the MCP program remains unchanged.
|
|
2858
|
+
|
|
2859
|
+
Routing uses a body-free GET projection of the URL, with percent escapes and
|
|
2860
|
+
the matcher's semicolon delimiter protected. The original request remains in
|
|
2861
|
+
`RouteContext`: existing programs own exact raw paths, arbitrary extension
|
|
2862
|
+
methods, JSON 405/Allow, HEAD, private OPTIONS, cookies, authentication and body
|
|
2863
|
+
refusal order. Router normalization cannot grant a canonical path to an encoded
|
|
2864
|
+
prefix, and invalid escapes in an owned suffix still reach the owning program.
|
|
2865
|
+
The projection introduces no second request handler or runner. Web responses
|
|
2866
|
+
pass through `HttpServerResponse.raw`/`toWeb` with their identity and stream
|
|
2867
|
+
ownership intact. The short dispatch Scope does not own MCP's body-held permit
|
|
2868
|
+
or the OAuth callback's uninterruptible commitment. Only a genuine public
|
|
2869
|
+
`RouteNotFound` before handler entry falls through; application failures,
|
|
2870
|
+
including structural lookalikes and genuine router errors, retain their identity.
|
|
2871
|
+
|
|
2872
|
+
Four public `effect/http/*` modules are allowed only in the private dispatcher;
|
|
2873
|
+
all other root-purity and optional-implementation boundaries remain enforced.
|
|
2874
|
+
Direct public module imports avoid the area's barrel cost. The local D3
|
|
2875
|
+
measurement over the reviewed D1/D2 stack is root 373,435 to 393,470 bytes gzip
|
|
2876
|
+
and Worker 758,274 to 774,384 (official Node 22.23.4/zlib 1.3.1-e00f703, identical
|
|
2877
|
+
repository bundle options). The barrel alternative measures 423,348/787,614.
|
|
2878
|
+
The parent accepted this measured native-routing cost with a Worker cap of
|
|
2879
|
+
785,000 bytes, including room for separately measured native-auth and reviewed
|
|
2880
|
+
host/transport additions. Root and all other caps and historical baselines are
|
|
2881
|
+
unchanged by D3. Separate-input costs are not a combined gzip measurement;
|
|
2882
|
+
the integrated deployment must be measured again.
|
|
2883
|
+
|
|
2884
|
+
`test/server.test.ts` asserts production routing directly on Node and workerd:
|
|
2885
|
+
raw prefixes and malformed suffixes, extension methods, CIMD before auth/UI/body
|
|
2886
|
+
reads, Response/error identity, unmatched fallthrough, concurrent request
|
|
2887
|
+
isolation, and body-held permits through end/failure/cancel/abort/abandonment.
|
|
2888
|
+
`test/node.node.test.ts` bundles one unmodified production Worker and
|
|
2889
|
+
verifies that a queued request owns its native I/O after the preceding response
|
|
2890
|
+
ends. Server-route, request-admission and request-pipeline suites retain ingress
|
|
2891
|
+
ordering, security headers, overload/shutdown and cancellation regressions.
|
|
2892
|
+
Root purity, declaration and bundle checks own import boundaries and size caps.
|
|
2893
|
+
The frozen legacy dispatcher, source transforms, diagnostic latency loops and
|
|
2894
|
+
rejected Effect MCP adapters were migration evidence; their historical source
|
|
2895
|
+
is linked from decision 0016 rather than rebuilt by ordinary tests.
|
|
2896
|
+
The upward MCP protocol leaves remain SDK implementations, with D7's separate
|
|
2897
|
+
native-orchestration work and compatibility evidence unchanged.
|
|
2898
|
+
|
|
2899
|
+
### Why not Schema or HttpApi for core routes
|
|
1485
2900
|
|
|
1486
2901
|
Both were measured and both lost. `effect` is imported through its barrel,
|
|
1487
2902
|
which defeats tree-shaking: the first conversion step measured about +20 ms
|
|
@@ -1495,17 +2910,22 @@ the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB
|
|
|
1495
2910
|
on `./activity`, and matching the wire format meant opting out of most of what
|
|
1496
2911
|
it does: unowned paths fall through rather than 404, a wrong method is a JSON
|
|
1497
2912
|
405, and the content-type and size checks run before a body is read. The root
|
|
1498
|
-
entry
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2913
|
+
entry imports `effect` and the four public HTTP modules confined to the private
|
|
2914
|
+
dispatcher (`test/purity.node.test.ts`).
|
|
2915
|
+
Effect 4.x is a long-term-support line, but its area barrels `effect/http`,
|
|
2916
|
+
`effect/http-api`, `effect/schema`, and `effect/sql` are still tagged
|
|
2917
|
+
`@stability unstable` and can change within it; only core modules such as
|
|
2918
|
+
`effect/Schema` are stable. That is why Effect is pinned to one exact version
|
|
2919
|
+
and every Effect upgrade is its own deliberate pull request. A deployment
|
|
2920
|
+
that also depends on Effect, such as the Worker example's Alchemy stack, must pin the
|
|
2921
|
+
same version to resolve a single copy. Connecta's own lockfile and packed
|
|
2922
|
+
install are checked for one copy (`test/package-surface.node.test.ts` and
|
|
2923
|
+
`scripts/check-package.mjs`).
|
|
2924
|
+
|
|
2925
|
+
The stable `4.0.0` release was re-evaluated on 2026-10-01 with this
|
|
2926
|
+
[historical probe source](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/effect-v4-evaluation.mjs),
|
|
2927
|
+
retained in Git history rather than the current checkout.
|
|
2928
|
+
The [raw observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/effect-v4-evaluation-2026-10-01.json)
|
|
1509
2929
|
are synthetic Node requests and neutral esbuild bundles, not live-client or
|
|
1510
2930
|
workerd lifetime verification. Bundle figures add representative prototypes
|
|
1511
2931
|
alongside the current code; they do not claim savings from removing old code.
|
|
@@ -1514,7 +2934,7 @@ Two native input schemas with validation and JSON Schema rendering add 62,148
|
|
|
1514
2934
|
bytes gzip to the root, taking it from 303,083 to 365,231 against a 339,526
|
|
1515
2935
|
cap. Effect can reject excess properties when both decoding and rendering use
|
|
1516
2936
|
`onExcessProperty: "error"`; its default strips them. The generated schemas
|
|
1517
|
-
still differ from the
|
|
2937
|
+
still differ from the published meta-tool schemas, including record rendering and
|
|
1518
2938
|
composed numeric bounds. A Schema replacement would need to preserve that
|
|
1519
2939
|
contract and memoized rendering. Both MCP SDK packages still depend on Zod,
|
|
1520
2940
|
so changing our inputs alone does not remove Zod from an installation.
|
|
@@ -1526,7 +2946,9 @@ wrong method and an unowned path; our routes need a JSON 405 for the former
|
|
|
1526
2946
|
and module fallthrough for the latter. Their `toWebHandler` also builds its
|
|
1527
2947
|
layer immediately and owns a runner, so adopting it would require proving
|
|
1528
2948
|
global-scope safety and preserving our scheduler and response-lifetime rules.
|
|
1529
|
-
|
|
2949
|
+
Those historical defaults are not the production adapter: the narrow public
|
|
2950
|
+
HttpRouter dispatch above preserves explicit method refusals, unowned fallthrough
|
|
2951
|
+
and the existing edge runner without `toWebHandler`.
|
|
1530
2952
|
|
|
1531
2953
|
Effect's MCP server has a separate compatibility gate. Its `2026-07-28`
|
|
1532
2954
|
adapter accepted a stateless `tools/list`, but its `2025-06-18` adapter refused
|
|
@@ -1535,15 +2957,20 @@ next request returned 404 on a fresh handler; Connecta served the same fresh
|
|
|
1535
2957
|
legacy list with 200 JSON and no session. The stable package exports no MCP
|
|
1536
2958
|
client, so it also cannot replace our downstream SDK and OAuth implementation.
|
|
1537
2959
|
[Issue #622](https://github.com/zackbart/connecta/issues/622) records the server
|
|
1538
|
-
parity requirements. These measurements
|
|
1539
|
-
|
|
1540
|
-
|
|
2960
|
+
parity requirements. These historical measurements supported retaining MCP protocol and validation
|
|
2961
|
+
implementations. Production HTTP dispatch now uses the separately proven narrow
|
|
2962
|
+
public adapter above; stable v4 alone did not establish its compatibility.
|
|
1541
2963
|
|
|
1542
2964
|
The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
|
|
1543
2965
|
grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
|
|
1544
2966
|
314,336, and each provider by about 36 KB, since each reaches Effect through
|
|
1545
|
-
`remoteMcp()
|
|
1546
|
-
|
|
2967
|
+
`remoteMcp()`. `pnpm run check:bundle` caps the deployed shapes (the root,
|
|
2968
|
+
`./worker`, `./ui`, the operator UI assets, `./d1`, `./auth/access-tokens`,
|
|
2969
|
+
the Worker example, and the other non-provider entries) against
|
|
2970
|
+
`scripts/bundle-budget.json`. Per-provider entries are reported with their
|
|
2971
|
+
size and change from baseline but not capped: every deployment already imports
|
|
2972
|
+
the root, which carries the Effect core, so a provider's standalone bundle
|
|
2973
|
+
counts that core a second time. An install carries about 53 MB of `effect`, with
|
|
1547
2974
|
no dependencies of its own.
|
|
1548
2975
|
|
|
1549
2976
|
## Where else to look
|
|
@@ -1555,7 +2982,6 @@ src/
|
|
|
1555
2982
|
server.ts route ordering, HTTPS upgrade, security wrapper
|
|
1556
2983
|
routes/ one file per surface; shared.ts holds the auth gate
|
|
1557
2984
|
skills.ts MCP instructions, the usage skill, connector guides
|
|
1558
|
-
catalog-drift.ts reviewed classifications and the drift counts a refresh produces
|
|
1559
2985
|
provider.ts defineProvider(): the one maintained-provider shape
|
|
1560
2986
|
activity.ts optional history factory and best-effort recorder
|
|
1561
2987
|
auth/ Cloudflare Access, clerk (optional peer), downstream OAuth (remote and static)
|
|
@@ -1579,9 +3005,423 @@ compiling and configuring the real thing.
|
|
|
1579
3005
|
- **Route order is behavior.** Moving a mutation route below the wildcard
|
|
1580
3006
|
`OPTIONS` opts it into CORS preflight; reordering admission after auth makes
|
|
1581
3007
|
the cheapest possible attack the most expensive request.
|
|
3008
|
+
- **Operation and call state live in services.** `src/runtime/call.ts` owns
|
|
3009
|
+
the admitted `Caller`, credentials, admission budget, recovery writer,
|
|
3010
|
+
classification, signals, secrets and request ledgers. Registry operations
|
|
3011
|
+
provision caller and operation policy; invocation adds a call lease and
|
|
3012
|
+
finalizes it on every exit. `api()` has a private Effect entry and a shared
|
|
3013
|
+
core with its Promise `callTool`. The public `ConnectorContext` is a view
|
|
3014
|
+
of those services. `deriveContext` shares its entry in the single view
|
|
3015
|
+
bridge; a spread has no caller or admission authority. Candidate credential
|
|
3016
|
+
probes get anonymous read-call services instead of deriving authority.
|
|
3017
|
+
Credentials and charges check closure before and after every await, pending
|
|
3018
|
+
work is interrupted, and late recovery facts are dropped. Every published
|
|
3019
|
+
Promise view and connector operation also races its complete caller-facing
|
|
3020
|
+
Promise against its owner through `settleOnClose`: credential and fetch
|
|
3021
|
+
views, guarded helpers, token minting, catalog loading, MCP operations and
|
|
3022
|
+
applicable authorization operations. An opaque storage, crypto, resolver
|
|
3023
|
+
or body-cleanup wait cannot keep that caller pending after closure.
|
|
3024
|
+
Work starts synchronously after the owner guard so shared-session waiter
|
|
3025
|
+
accounting and claim ownership keep their existing ordering. Late
|
|
3026
|
+
rejections are observed; dispatch and lease guards still contain detached
|
|
3027
|
+
work. Per-await I/O cancellation remains useful for releasing resources,
|
|
3028
|
+
while prompt refusal is enforced at the boundary
|
|
3029
|
+
(`test/promise-boundary-lifetime.test.ts`). Invocation owns
|
|
3030
|
+
the recovery and logical-call records it reads after teardown. Closing an
|
|
3031
|
+
outer call also revokes nested calls paused in preparation. Request-owned
|
|
3032
|
+
MCP sessions and token flights outlive individual calls and close with the
|
|
3033
|
+
request; their credential access comes from the operation. Operation views
|
|
3034
|
+
resolve request cancellation even without an external signal; auxiliary
|
|
3035
|
+
authentication, sends and response handling all use that signal. The
|
|
3036
|
+
invocation deadline and original request cancellation remain separate,
|
|
3037
|
+
so deadline cleanup cannot end request-owned auxiliary work.
|
|
3038
|
+
Concurrent operator probes use separate row scopes, so finalizing a failed
|
|
3039
|
+
row cannot revoke a sibling that is still running.
|
|
3040
|
+
Shared MCP sessions bind renewal preparation to the challenged call's signal,
|
|
3041
|
+
rather than the connection's longer lifetime. OAuth renewal
|
|
3042
|
+
is call-owned until its first durable claim CAS is submitted. Submission
|
|
3043
|
+
transfers preparation, token exchange and fenced rotation commit to the
|
|
3044
|
+
storage coordinator under its own deadlines and deferred-work hook; only
|
|
3045
|
+
the caller's wait still closes with the call. That completion
|
|
3046
|
+
cannot resume a closed call or resend its resource request
|
|
3047
|
+
(`test/call-renewal-lifetime.test.ts`,
|
|
3048
|
+
`test/remote-mcp-renewal-lifetime.test.ts`). Explicit request revocation also
|
|
3049
|
+
aborts pending authorization-code exchange and refuses late token writes
|
|
3050
|
+
(`test/operation-cancellation.test.ts`). Request closure
|
|
3051
|
+
revokes views and calls first, then runs every finalizer in reverse order,
|
|
3052
|
+
collecting failures. The caller's original cancellation signal is retained
|
|
3053
|
+
separately from the lease signal, so normal teardown never looks like caller
|
|
3054
|
+
cancellation (`test/call-services.test.ts`, `test/derived-context.test.ts`).
|
|
3055
|
+
Construction identities and immutable request/failure facts stay in the pure
|
|
3056
|
+
`src/provider-call-facts.ts`, keeping provider stamping and operator sinks
|
|
3057
|
+
free of the Effect call-service graph.
|
|
3058
|
+
- **A write leaves once (INV-9).** `src/write-outcome.ts` owns the shared rules
|
|
3059
|
+
every transport applies to a write: `unknownOutcome` turns a retryable
|
|
3060
|
+
failure that may follow the downstream acting (a 5xx, a gateway's 502 to
|
|
3061
|
+
504, a lost connection or reply) into a non-retryable `connector_call_failed`,
|
|
3062
|
+
and `unsentAfterUnauthorized` reports a write answered 401 after credential
|
|
3063
|
+
renewal ran, which is never resent: until renewal succeeds it keeps an
|
|
3064
|
+
authorization code (renewal's own verdict, else the 401's), and a renewed
|
|
3065
|
+
credential leaves a non-retryable `connector_call_failed`. Both mark the error
|
|
3066
|
+
`writeOutcome: "unknown"`, which `classifyWriteOutcome` honors before any
|
|
3067
|
+
refusal provenance, so an authorization verdict reached after the write left still
|
|
3068
|
+
counts as unknown in program write accounting (`src/program-writes.ts`),
|
|
3069
|
+
whose result is then that verdict with its handoff, caught or not.
|
|
3070
|
+
`remoteMcp()` classifies by the invocation's verdict
|
|
3071
|
+
(`src/call-classification.ts`), falling back to `classifyTool`, and holds a
|
|
3072
|
+
write to one send per logical call, never per JSON-RPC message: `callTool`
|
|
3073
|
+
puts a dispatch record on the `tools/call` params under a symbol key (spread
|
|
3074
|
+
copies it; `JSON.stringify` drops it), the transport's `send` refuses a
|
|
3075
|
+
second message for that call whatever its id, and the tracked fetch refuses
|
|
3076
|
+
a second request within one message (the SDK's resend after a 401 or a
|
|
3077
|
+
step-up, a redirect hop) and fails a write answered with a redirect. Both
|
|
3078
|
+
layers refuse a missing record unless the active calls for that tool are
|
|
3079
|
+
all reads; catalog hints cannot overrule a served write verdict. Tracking
|
|
3080
|
+
loss gives a non-retryable unknown outcome, never a fresh write attempt. A write
|
|
3081
|
+
always gives the SDK a tool definition, so `callTool` never relists and
|
|
3082
|
+
resends it after a `HeaderMismatch` answer. `api()` OAuth
|
|
3083
|
+
(`src/auth/static-oauth.ts`), the Google Workspace client
|
|
3084
|
+
apply the same one-send rule to credential recovery. All maintained API
|
|
3085
|
+
provider failure mappers run inside `guardedFetch`. Each `api()` entry owns
|
|
3086
|
+
a fresh request ledger (`src/provider-call.ts`) on its derived handler context;
|
|
3087
|
+
further `deriveContext` copies share that ledger without sharing it with
|
|
3088
|
+
concurrent calls. Every guarded request records dispatch, response status,
|
|
3089
|
+
and provider-declared auxiliary status. Plain handler fetches and OAuth
|
|
3090
|
+
resource sends record their dispatch too; OAuth credential checks precede
|
|
3091
|
+
the resource dispatch record. Token exchanges, GitHub installation
|
|
3092
|
+
and repository binding lookups, token minting, and Cloudflare zone-ownership
|
|
3093
|
+
pin lookups are auxiliary: they cannot perform the logical resource write.
|
|
3094
|
+
No transport rejection proves a dispatched write unsent, whatever errno it
|
|
3095
|
+
names. One `fetch` call can hide several attempts: Node's fetch sends a
|
|
3096
|
+
request again after a 421 (even under `redirect: "manual"`) and after an
|
|
3097
|
+
HTTP/2 `REFUSED_STREAM`, both of which servers define as not processed, and
|
|
3098
|
+
an operator-installed dispatcher such as undici's `RetryAgent` can retry
|
|
3099
|
+
further. A refusal on the last attempt says nothing about the earlier ones.
|
|
3100
|
+
Remote MCP writes on Node use the owned runtime sender installed by
|
|
3101
|
+
`listen` from `/node`, selected before recording dispatch. Without it they
|
|
3102
|
+
are refused before sending. Remote MCP reads retain native fetch; Workers uses native
|
|
3103
|
+
fetch with manual redirects. The owned Node sender bypasses the global
|
|
3104
|
+
dispatcher and fetch's 421 replay, preserving the SDK's JSON/SSE bodies and
|
|
3105
|
+
abort signals. A reset or unconfirmed server failure remains unknown.
|
|
3106
|
+
Connecta never reports a transport-failed write as known failed. A write
|
|
3107
|
+
is a known failure only on refusal provenance recorded where the evidence is
|
|
3108
|
+
observed, never inferred from an error code: (a) the ledger proves it never
|
|
3109
|
+
left, or (b) the downstream served an HTTP refusal, a 4xx other than 408
|
|
3110
|
+
(`refusedStatus` and `vendorRefusal` in `src/provider-call.ts`), under the
|
|
3111
|
+
provider's existing served classification (a write's HTTP 409 is a `conflict`). One helper, `servedRefusal` in `src/write-outcome.ts`, decides
|
|
3112
|
+
(b) for the ledger and for a transport outside one: a refusing status the
|
|
3113
|
+
provider reads as `unavailable` or a timeout (Stripe's 424) is not proof, so
|
|
3114
|
+
only a genuine idempotency key keeps such a direct write's retry. Nor is a
|
|
3115
|
+
401, since a server may act on a request before it rejects the credential,
|
|
3116
|
+
unless the transport declares reviewed `authBeforeExecution` evidence
|
|
3117
|
+
(`GuardedFetchOptions`: the vendor's cited guarantee and a narrow predicate
|
|
3118
|
+
over the raw status and headers and the request's method and path, checked
|
|
3119
|
+
at construction) and the predicate matched, which the ledger records on the
|
|
3120
|
+
request. Only Stripe declares it (`src/providers/stripe/api.ts`, reviewed
|
|
3121
|
+
as a `manual` check in its `drift.json`): an API v1 answer carrying Stripe's
|
|
3122
|
+
`Request-Id`. `test/provider-failure-sweep.test.ts` holds that list to the
|
|
3123
|
+
sources. Q8 is decided in one place, `settleWrite` in
|
|
3124
|
+
`src/write-outcome.ts`, once per logical call and over every request it
|
|
3125
|
+
recorded. The invocation calls it for every failure of a served write: a
|
|
3126
|
+
thrown or mapped error, a custom connector's, a program's call, and its
|
|
3127
|
+
own deadline or the caller's cancellation, when no connector failure
|
|
3128
|
+
arrives at all. It reads the logical call's requests
|
|
3129
|
+
(`logicalCallRequests`), which hold every resource request dispatched
|
|
3130
|
+
under the call from any ledger: nested `api()` calls, remote MCP, guarded
|
|
3131
|
+
transports, `ctx.fetch` and the kit. Without an invocation (a direct call,
|
|
3132
|
+
or a context no invocation opened), only the outermost boundary calls it,
|
|
3133
|
+
at the end of `providerCallFailure`, over its own ledger and every ledger
|
|
3134
|
+
of an `api()` call entered beneath its handler (`nestedProviderCall`,
|
|
3135
|
+
`nestedProviderRequests`), or over the one request a transport outside a
|
|
3136
|
+
ledger sent. Boundaries only record what they observed: the dispatch and
|
|
3137
|
+
status, a reviewed `authBeforeExecution` match, the 401's own mapped verdict
|
|
3138
|
+
(`vendorRefusal`), and a renewal of that one request's own credential
|
|
3139
|
+
(`renewedAfterUnauthorized(request, sent, fresh)`, only for a credential
|
|
3140
|
+
other than the one it sent). The rule: a write request answered 401 that
|
|
3141
|
+
is not a declared refusal standing for the whole call makes the call
|
|
3142
|
+
unknown, never retryable, with no wait and no resend or key-reuse advice;
|
|
3143
|
+
while any such 401 is unrenewed the verdict keeps an authorization code,
|
|
3144
|
+
so invocation keeps the `authorize_connector` handoff and picks its
|
|
3145
|
+
reconcile-first words. Its code and words are the failure's when that is an
|
|
3146
|
+
authorization verdict, else the 401's own recorded verdict, else
|
|
3147
|
+
`auth_required`, with a sentence saying the write was sent before the
|
|
3148
|
+
vendor rejected its credential unless the verdict already accounts for the
|
|
3149
|
+
401 (a renewal's words). When every such 401 was renewed, the handoff goes
|
|
3150
|
+
and the failure keeps its words, unknown and without a wait. A declared 401
|
|
3151
|
+
stands only as the call's one request, sent by the boundary that decides,
|
|
3152
|
+
whose own refusal is the failure (and, served, known for the logical call).
|
|
3153
|
+
The recorders are the guarded transport (dispatch, status, declaration
|
|
3154
|
+
match, mapped verdict), `ctx.fetch` (dispatch, status), static OAuth
|
|
3155
|
+
(status, verdict, renewal), Google (per-attempt entry, renewal of a read's
|
|
3156
|
+
replay), the kit (core ledger entry, status, `recordUnauthorized`,
|
|
3157
|
+
refresh-once renewal),
|
|
3158
|
+
`remoteMcp()` (each `tools/call` as an entry under its caller's ledger and
|
|
3159
|
+
logical call, status, the verdict, and renewal by comparing the
|
|
3160
|
+
`Authorization` of the resend the SDK attempts), and `api()` itself (one
|
|
3161
|
+
ledger per call, linked to the one whose handler entered it). The same
|
|
3162
|
+
decision settles a call that returned (`settleWriteResult`, before its
|
|
3163
|
+
result is processed in the invocation, and in `providerCallResult` at an
|
|
3164
|
+
outermost direct boundary, including a guarded transport outside any ledger):
|
|
3165
|
+
any undeclared write 401 blocks success, even after renewal. Renewal drops
|
|
3166
|
+
only the handoff; a program still counts the write in `writes.unknown`.
|
|
3167
|
+
A caught read 401 needs positive operation or tool classification, plus a
|
|
3168
|
+
recorded GET or HEAD, to leave success intact; a method alone proves no
|
|
3169
|
+
read. The guarded transport records its operation classification, otherwise
|
|
3170
|
+
its own `api()` tool supplies it (including nested read tools).
|
|
3171
|
+
`ctx.fetch`, OAuth and the kit record the same evidence. The final outgoing
|
|
3172
|
+
`X-HTTP-Method-Override`, `X-HTTP-Method` or `X-Method-Override` header
|
|
3173
|
+
makes the request a write regardless of its classification or method. A
|
|
3174
|
+
declared refusal followed by a successful write also leaves success intact.
|
|
3175
|
+
A custom guarded transport delegating through `ctx.fetch` or `ctx.oauth.fetch` records the wrapper
|
|
3176
|
+
separately from each physical send. Each physical entry records its own
|
|
3177
|
+
outgoing method and response; a later response cannot erase an earlier
|
|
3178
|
+
401, and a delegated POST cannot inherit a wrapper's GET read evidence.
|
|
3179
|
+
The inner entry does not inherit the wrapper's declaration or explicit
|
|
3180
|
+
read evidence, including when the init is copied. Until linked recording
|
|
3181
|
+
is reviewed, that duplicate may conservatively keep a successful
|
|
3182
|
+
composite unknown even when the wrapper proves its own refusal or read.
|
|
3183
|
+
Request entries are append-only: every physical send, including a read
|
|
3184
|
+
replay after OAuth renewal, gets its own entry. Dispatch refuses an entry
|
|
3185
|
+
already sent and locks its sent method and dispatched flag; the request's
|
|
3186
|
+
own response status is recorded once and locked, and its own renewal can
|
|
3187
|
+
only add a locked mark. These fields are readonly in TypeScript too.
|
|
3188
|
+
The built-in `oauthBearer` transport hands an unsent entry to a known
|
|
3189
|
+
recording OAuth grant once, before any dispatch. The grant consumes that
|
|
3190
|
+
handoff before credential reads and creates a new entry for each further
|
|
3191
|
+
send. It never borrows a dispatched entry. Returned-response identity
|
|
3192
|
+
names the entry whose status was already recorded, without recording it
|
|
3193
|
+
again. This preserves the built-in transport's refusal/read provenance;
|
|
3194
|
+
custom delegation and copied init objects remain conservative.
|
|
3195
|
+
Remote MCP captures
|
|
3196
|
+
each physical read's entry before awaiting its response; a later send
|
|
3197
|
+
cannot overwrite it. The kit's response identity and Google's init identity
|
|
3198
|
+
identify the request whose credential was renewed. The legacy guarded-fetch
|
|
3199
|
+
`answeredRequest` helper has no production caller; tests retain it to exercise
|
|
3200
|
+
renewal provenance outside the kit.
|
|
3201
|
+
The decider normalizes every failure it settles,
|
|
3202
|
+
typed or not (a raw network error, an `AbortError`, a thrown string). Its
|
|
3203
|
+
verdict is final (`settledWrite`, which follows copies and redaction): the
|
|
3204
|
+
invocation reads the mark before anything rebuilds the details, and its
|
|
3205
|
+
enrichers then add no key-reuse, resend or wait advice; only the
|
|
3206
|
+
reconcile-first `retry` words and the handoff. A 408 is a
|
|
3207
|
+
timeout, and a 2xx followed by a mapper, parser, or output-validation
|
|
3208
|
+
failure may have applied. `classifyWriteOutcome` counts a
|
|
3209
|
+
dispatched write `failed` only with that provenance (`answered`, or the
|
|
3210
|
+
error's private known-failure mark); a bare `rate_limited`,
|
|
3211
|
+
`invalid_args`, or `conflict` code is `unknown`. A downstream tool's
|
|
3212
|
+
`isError` is a read's answer only: for a write it arrives over a 2xx after
|
|
3213
|
+
the handler ran, so it is unknown and non-retryable, and its text stays.
|
|
3214
|
+
Refusal evidence holds only for its whole logical call. The invocation
|
|
3215
|
+
opens one per dispatch (`openLogicalCall` in `src/provider-call.ts`), and
|
|
3216
|
+
every `api()` and `remoteMcp()` `callTool` entered beneath it joins it
|
|
3217
|
+
(`enterProviderBoundary`). A served write's mark stands
|
|
3218
|
+
(`knownForLogicalCall`) only when at most one boundary was entered and the
|
|
3219
|
+
invocation invoked that boundary object with its own `callTool`, or a
|
|
3220
|
+
maintained connector object declared to delegate each call to one boundary
|
|
3221
|
+
(`delegatesEachCall`, GitHub) with its declared function. Identities are
|
|
3222
|
+
captured, never read back: the invocation reads the connector and its
|
|
3223
|
+
`callTool` once and invokes that function with `Reflect.apply`, never
|
|
3224
|
+
through the function's own `call` or `apply` property; each boundary
|
|
3225
|
+
records its object with the published function when it is built
|
|
3226
|
+
(`providerBoundary`) and names that object, from its closure, on entry;
|
|
3227
|
+
and a delegator is declared at construction. A connector that reassigns
|
|
3228
|
+
`callTool` mid-call, or a copy (`{ ...boundary }`, `Object.assign`) that
|
|
3229
|
+
shares a boundary's or delegator's function, borrows no standing;
|
|
3230
|
+
`defineProvider`'s inheriting stamp keeps its connector's
|
|
3231
|
+
(`aliasCallIdentity`). A boundary marks a write known only when
|
|
3232
|
+
every request it dispatched is proven unapplied, so the rule reads: known
|
|
3233
|
+
only if every request the logical call dispatched is proven unapplied and
|
|
3234
|
+
the failing request carries the refusal. A custom connector that composes
|
|
3235
|
+
calls, or that could send around them, never inherits an inner verdict; the
|
|
3236
|
+
invocation withdraws the mark and the write is unknown.
|
|
3237
|
+
Write inference also needs positive evidence before relaxing timeout or auth
|
|
3238
|
+
reconciliation. `LogicalCall.auxiliaryReads` records each auxiliary GET or HEAD
|
|
3239
|
+
at dispatch, without method-override headers, separately from the call-owned
|
|
3240
|
+
requests in `sent`. Its physical read proof does not depend on the served
|
|
3241
|
+
tool's write classification. `onlyAuxiliaryRequests` requires exactly one
|
|
3242
|
+
boundary that owns the logical call and declares complete request tracking
|
|
3243
|
+
(`tracksEveryRequest`), at least one dispatched auxiliary resource read, and
|
|
3244
|
+
no dispatched call-owned request or kit auxiliary falsely declared as a read.
|
|
3245
|
+
The kit records `blocksAuxiliaryProof` at dispatch for an `effect: "read"`
|
|
3246
|
+
request without physical GET/HEAD proof; `nonReadAuxiliaryDispatched` retains
|
|
3247
|
+
that blocking fact on the logical call. The
|
|
3248
|
+
invocation's `mayHaveWritten` is true
|
|
3249
|
+
for an entered write unless the call answered with that proof; an explicit
|
|
3250
|
+
`writeOutcome: "unknown"` always overrides the relaxation. Empty or partial
|
|
3251
|
+
ledgers, token POSTs alone, multiple boundaries, custom `api()` and
|
|
3252
|
+
`remoteMcp()` connectors, deadlines, and caller cancellation supply no such
|
|
3253
|
+
proof. Only maintained providers have the internal tracking declaration;
|
|
3254
|
+
they must route every dispatch through their tracked transports. The kit
|
|
3255
|
+
still classifies auxiliary POSTs and method-overridden GETs as writes for
|
|
3256
|
+
reservation and replay; the effect label does not change those rules.
|
|
3257
|
+
Their dispatches block auxiliary-only inference even beside a genuine
|
|
3258
|
+
auxiliary read, and count as call-owned requests for write-failure settlement,
|
|
3259
|
+
including a 503 or a mapped `unavailable` verdict. Guarded authentication
|
|
3260
|
+
token POSTs supply no read proof,
|
|
3261
|
+
but do not invalidate a later auxiliary resource read. A conservative
|
|
3262
|
+
timeout withdraws `answered`; a conservative auth failure does so only with
|
|
3263
|
+
an empty resource ledger, also withdrawing that ledger's private known-failure
|
|
3264
|
+
mark before advice is composed. A refused call-owned write keeps its
|
|
3265
|
+
`answered` evidence and resource-refusal provenance only with a complete
|
|
3266
|
+
ledger proving the refusal covers the whole call; its auth recovery advice
|
|
3267
|
+
uses the same known outcome as program accounting. Partial or unknown ledgers
|
|
3268
|
+
remain conservative, as do token-mint and auxiliary auth failures without
|
|
3269
|
+
positive resource-read proof. Auth failures with no
|
|
3270
|
+
call-owned dispatch, including credential refusals and static-OAuth refresh
|
|
3271
|
+
failures, keep conservative program accounting and reconciliation wording.
|
|
3272
|
+
Pre-dispatch refusals mark themselves: `api()`'s unknown tool and argument
|
|
3273
|
+
validation, `remoteMcp()`'s allowlist and non-auth failures before its
|
|
3274
|
+
`tools/call` is sent, and GitHub's REST target check and token mint. Auth
|
|
3275
|
+
failures after entering a write with an empty resource ledger remain unknown,
|
|
3276
|
+
including a `remoteMcp()` connect or initialize 401 before `tools/call`.
|
|
3277
|
+
`remoteMcp()` applies the rule to direct and served writes alike: once
|
|
3278
|
+
`dispatch.sent`, a JSON-RPC error (`-32602` included), an unreadable result,
|
|
3279
|
+
or a failed output validation is unknown; only an HTTP 4xx (not 408) on the
|
|
3280
|
+
request is a refusal. A downstream's `input_required` is no proof either: a
|
|
3281
|
+
handler asks for input from inside its run, so a write's unsupported
|
|
3282
|
+
suspension (`src/invocation.ts`) and a direct write's failed relay
|
|
3283
|
+
(`src/downstream-input.ts`) are unknown and say to reconcile, not resend.
|
|
3284
|
+
The invocation finalizes every served write `classifyWriteOutcome` calls
|
|
3285
|
+
unknown before it adds advice: `retryable: false`, `writeOutcome:
|
|
3286
|
+
"unknown"`, and no grant-then-retry or correct-and-retry text, so a custom
|
|
3287
|
+
connector's code cannot advertise a retry. Advice to send again (a rate
|
|
3288
|
+
limit's "back off before retrying", a token or OAuth preflight's "then
|
|
3289
|
+
retry authorization") is a segment of its own, attached with
|
|
3290
|
+
`resendAdvice` in `src/errors.ts` where it is written and never part of
|
|
3291
|
+
the error's message. Output composes it only once the outcome allows it:
|
|
3292
|
+
`classifyCallError` for a direct caller, unless the write is already
|
|
3293
|
+
unknown, and the invocation after its verdict (it classifies with
|
|
3294
|
+
`classifyWithoutAdvice`). So no redaction of a private argument, no
|
|
3295
|
+
wrapper's rewording, and no preflight failure after an earlier dispatch
|
|
3296
|
+
can leave it on an unknown write, while a read and a known refusal keep it.
|
|
3297
|
+
Key-reuse advice ("reuse this key", "cannot repeat the write") needs more:
|
|
3298
|
+
proof that its key protects the whole logical call
|
|
3299
|
+
(`keyProtectsLogicalCall`). Every resource request dispatched under the
|
|
3300
|
+
logical call, from any ledger, is recorded on it once per dispatch with
|
|
3301
|
+
its method and the genuine key it carried (`ProviderRequest.key`, set by
|
|
3302
|
+
the kit's guarded transport only for a key fetch sends as given). The advice stands only when
|
|
3303
|
+
exactly one boundary was entered, the invocation's object owns it as for
|
|
3304
|
+
`knownForLogicalCall`, that boundary tracks every request, and exactly one
|
|
3305
|
+
dispatched request was a write, carrying the named key; a request counts as
|
|
3306
|
+
a read only with positive operation/tool classification and a recorded
|
|
3307
|
+
GET or HEAD (`readRequest`); method-override headers make it a write. A composite, a
|
|
3308
|
+
wrapper, a second write, an earlier unrelated key, or a call interrupted
|
|
3309
|
+
after two dispatches gets no such advice; the key stays a diagnostic, in
|
|
3310
|
+
the message and `uncertainCall.recovery`. The REST write tool's
|
|
3311
|
+
generated-key advice is a segment of its own (`keyResendAdvice`), which the
|
|
3312
|
+
invocation composes only under that proof and `classifyCallError` composes
|
|
3313
|
+
for a direct caller of the one boundary that sent the key.
|
|
3314
|
+
An authorization or credential handoff on an
|
|
3315
|
+
unknown write says to re-read the target before any fresh call instead of
|
|
3316
|
+
"retry the original call" (`src/authorization-handoff.ts`). The guarded
|
|
3317
|
+
`HttpClient` owns transport attempts through `src/guarded-transport.ts`;
|
|
3318
|
+
Node uses the owned sender in `src/http/node-transport.ts`, installed by
|
|
3319
|
+
`/node` when `listen()` starts serving. Reads keep their retry advice. Every maintained
|
|
3320
|
+
provider declares (`tracksEveryRequest`) that its handlers send only through
|
|
3321
|
+
channels the ledger records; for an undeclared `api()` handler an empty
|
|
3322
|
+
ledger is no evidence, so its served write fails unknown once the handler
|
|
3323
|
+
has run. When the handler settles, `api()` closes the ledger before any
|
|
3324
|
+
decision: a resource request still waiting to leave (a sibling reading its
|
|
3325
|
+
credential) is refused before its credential read and again before send,
|
|
3326
|
+
and one in flight already counts as dispatched.
|
|
3327
|
+
`providerCallFailure` in `src/write-outcome.ts` finalizes the call once at the
|
|
3328
|
+
`api()` boundary, including handler-thrown typed errors. For a served write,
|
|
3329
|
+
no resource dispatch keeps the failure's own advice. A vendor's own 4xx
|
|
3330
|
+
refusal (a 4xx other than 408) is a known failure only when no other
|
|
3331
|
+
resource request was dispatched. Refusal provenance travels with the error independently of its
|
|
3332
|
+
code or wording, so a generic `connector_call_failed` refusal still counts
|
|
3333
|
+
as failed in program write accounting. Every other possibly executing
|
|
3334
|
+
failure is non-retryable with `writeOutcome: "unknown"`; authorization codes
|
|
3335
|
+
keep their handoff. A write answered 401 is one of these unless its
|
|
3336
|
+
transport declares `authBeforeExecution` evidence that matched, and it
|
|
3337
|
+
keeps an authorization code and handoff whatever failure surfaced after
|
|
3338
|
+
it, with no wait.
|
|
3339
|
+
A later refusal cannot make an earlier successful request safe to repeat.
|
|
3340
|
+
Provider recovery wrappers change wording through helpers that preserve
|
|
3341
|
+
verdicts and provenance; Google also retains its response phases and reasons.
|
|
3342
|
+
No operation hint (a whole-resource `PUT`, fixed cells, a content address)
|
|
3343
|
+
grants retry credit, direct or served. A served write requires
|
|
3344
|
+
reconciliation even with an idempotency key; direct transport calls
|
|
3345
|
+
without a served verdict can still prove repeatability, but only with a
|
|
3346
|
+
genuine key sent to a vendor that honors it (Stripe today, through
|
|
3347
|
+
`hatch.idempotency`): the REST write tool refuses an `idempotencyKey` that fetch
|
|
3348
|
+
would send empty after trimming or would not send at all (CR, LF, NUL, any
|
|
3349
|
+
other control character but tab, DEL, or a code point past Latin-1), as
|
|
3350
|
+
`invalid_args` before recovery is recorded, and the guarded transport marks a request
|
|
3351
|
+
repeatable only for a key fetch sends as given.
|
|
3352
|
+
`providerCallFailure` (`src/write-outcome.ts`) is the one finalizer for a
|
|
3353
|
+
direct write's failure: the `api()` handler boundary, a guarded transport
|
|
3354
|
+
outside a call ledger, and every `remoteMcp().callTool()` write, direct or
|
|
3355
|
+
served (with its `tools/call` dispatch record as the ledger and the
|
|
3356
|
+
endpoint's HTTP status as the served verdict) all hand it every write
|
|
3357
|
+
failure. Guarded transports
|
|
3358
|
+
inside a call (`ctx.fetch`, the shared REST layer, Google) defer to the
|
|
3359
|
+
`api()` boundary. No identity passthrough or code filter runs ahead of it
|
|
3360
|
+
for a direct write. It recognizes every timeout and abort before any code
|
|
3361
|
+
filter: the caller's own signal (an `AbortSignal.timeout` included, and
|
|
3362
|
+
however the runtime's fetch reports it), an SDK or connecta deadline
|
|
3363
|
+
(`code: "timeout"`, an HTTP 408), and a timeout errno. Without a genuine
|
|
3364
|
+
key, such a write is unknown and non-retryable: a timeout keeps its code
|
|
3365
|
+
and `details.code`; the caller's abort becomes `timeout` or
|
|
3366
|
+
`connector_call_failed` with its reason as `cause` (a redacted copy of the
|
|
3367
|
+
same class where output redaction applies, as on `remoteMcp()`) and its
|
|
3368
|
+
class and code in `details`. No invocation exists to report it as
|
|
3369
|
+
`write_outcome_unknown`. The caller's own abort reason still passes by
|
|
3370
|
+
identity for a read and for a served call, whose timeout passes through
|
|
3371
|
+
unchanged for the invocation to report.
|
|
3372
|
+
The catalog-driven `test/provider-failure-sweep.test.ts` checks every API
|
|
3373
|
+
provider/tool on Node, fails on missing provider/tool examples or a
|
|
3374
|
+
dispatched failure bypassing the helper, and covers served write/read
|
|
3375
|
+
overrides with HTTP 503 for every tool and a connection reset after send
|
|
3376
|
+
for each provider/verdict representative. HTTP 401 recovery uses those
|
|
3377
|
+
representatives plus the distinct composite, auxiliary, owner-lookup and
|
|
3378
|
+
POST-as-read paths.
|
|
3379
|
+
Shared sender and real-socket evidence below own status/reset permutations.
|
|
3380
|
+
Local metadata
|
|
3381
|
+
tools prove zero sends; composite calls prove each distinct request leaves
|
|
3382
|
+
once. The round-5 cases additionally cover Docs creation with initial text,
|
|
3383
|
+
composite success followed by refusal, GitHub preflight failures, typed
|
|
3384
|
+
handler errors after successful sends, Forms cursor conflicts after 200,
|
|
3385
|
+
generic-code vendor refusals, auxiliary failures after resource dispatch,
|
|
3386
|
+
redaction of refusal errors, and concurrent direct-call ledger isolation.
|
|
3387
|
+
The round-6 cases cover a composite write's early refusal while siblings
|
|
3388
|
+
await credentials or are in flight, requests started after the call ended,
|
|
3389
|
+
connect-phase errnos with each body kind (all unknown for a write, and a
|
|
3390
|
+
direct read stays retryable), a request aborted with a refusal-shaped
|
|
3391
|
+
reason, an errno outside fetch's network error, and
|
|
3392
|
+
undeclared handlers whose untracked send precedes a typed failure. Each
|
|
3393
|
+
catalog case also checks that the ledger recorded every resource request
|
|
3394
|
+
the network saw, and the inventory that every API connector declares it.
|
|
3395
|
+
`test/write-evidence.test.ts` uses real refused, reset, and held sockets on
|
|
3396
|
+
both runtimes: a refused write is unknown and a refused read retryable,
|
|
3397
|
+
including a write aborted with another fetch's real refusal as its reason
|
|
3398
|
+
after the server read its body; `test/write-evidence.node.test.ts` covers a
|
|
3399
|
+
Planning Center POST that undici resends after a 421 to a closed listener, a
|
|
3400
|
+
DELETE that a `RetryAgent` resends after a reset, and a streamed body and a
|
|
3401
|
+
`Blob` with an overridden `stream()` that undici partly sends before its
|
|
3402
|
+
producer fails. Each has the server holding the write and reports unknown.
|
|
3403
|
+
Additional tests:
|
|
3404
|
+
`test/remote-mcp-write-once.test.ts` (real SDK transport),
|
|
3405
|
+
`test/api-oauth.test.ts`, `test/google-workspace-delegation.test.ts`, and each
|
|
3406
|
+
provider's `INV-9` tests.
|
|
3407
|
+
- **Plain `ctx.fetch` is not confined to an origin, by design.** In an `api()`
|
|
3408
|
+
handler it is the global `fetch` plus sent-secret registration. It carries no
|
|
3409
|
+
grant: only `ctx.oauth.fetch` attaches the OAuth token, and only to
|
|
3410
|
+
`apiOrigins`, and every maintained provider sends through a guarded transport
|
|
3411
|
+
confined to its base URL. A custom handler is deployment code, which owns its
|
|
3412
|
+
destinations (goal 4); no agent program reaches `ctx.fetch`, so INV-3 holds
|
|
3413
|
+
below the sandbox, and a credential a handler sends is redacted from what the
|
|
3414
|
+
agent sees (INV-5). Confining it would need a declared origin list on every
|
|
3415
|
+
`api()` connector and would break handlers that follow a URL their API
|
|
3416
|
+
returned, such as a signed download. `test/api-oauth.test.ts` pins that the
|
|
3417
|
+
plain fetch carries no grant.
|
|
1582
3418
|
- **`close()` is idempotent and ordered.** Both admission pools, then the
|
|
1583
3419
|
connector limiters, then the executor; Node's `listen()` calls it on
|
|
1584
|
-
SIGTERM/SIGINT.
|
|
3420
|
+
SIGTERM/SIGINT. The listener stops accepting connections and closes idle
|
|
3421
|
+
keep-alive sockets plus accepted sockets that have not produced their first
|
|
3422
|
+
HTTP request, then waits for active responses, deferred work, and executor
|
|
3423
|
+
cleanup. `test/node.node.test.ts` covers shutdown after token adoption,
|
|
3424
|
+
rotation, and doctor with an unused connection and a live QuickJS execution.
|
|
1585
3425
|
- **Structural mistakes throw at construction.** A duplicate connector id, an
|
|
1586
3426
|
invalid admission rule or limit, an unknown option at any depth, the old
|
|
1587
3427
|
boolean `accessTokens` option, malformed or repeated `agents` or agents
|
|
@@ -1601,3 +3441,317 @@ The contract now lives in [Operator UI](./operator-ui.md#operator-data-contract)
|
|
|
1601
3441
|
including configuration provenance, live facts, activity disclosure, doctor,
|
|
1602
3442
|
and the browser implementation. [Optional deployment modules](#optional-deployment-modules)
|
|
1603
3443
|
keeps the module composition and request-lifetime rules here.
|
|
3444
|
+
|
|
3445
|
+
Vendor error declarations may review structural text presence at fixed body
|
|
3446
|
+
paths. The error boundary exposes only `present` and `nonBlank`, after
|
|
3447
|
+
sent-secret redaction, and never the text. Reviewed recovery-token lists can
|
|
3448
|
+
set a fixed `displayFacts` bound with a reason; other lists display eight
|
|
3449
|
+
entries. Both reviews are snapshotted at connection construction and never
|
|
3450
|
+
change classification or the operation's error-withholding policy.
|
|
3451
|
+
|
|
3452
|
+
Reviewed numeric error-body paths expose finite, nonnegative numbers only.
|
|
3453
|
+
Nonblank numeric strings may be converted; empty or blank strings are absent.
|
|
3454
|
+
The original text never reaches classification. These paths are also captured
|
|
3455
|
+
at construction and cannot change error-withholding policy.
|
|
3456
|
+
|
|
3457
|
+
Native pools resolve names through a shared claimant index for listing and
|
|
3458
|
+
calling, then use the existing invocation routes. Native recovery adapts only
|
|
3459
|
+
Connecta error envelopes in `src/native-recovery.ts`; provider dispatch and
|
|
3460
|
+
continuation enforcement stay in their existing owners.
|
|
3461
|
+
|
|
3462
|
+
### Contributor reference: owned Node transport dispatch
|
|
3463
|
+
|
|
3464
|
+
`src/guarded-transport.ts` is the platform-neutral runtime slot. `sendGuarded`
|
|
3465
|
+
invokes a dispatch callback before legacy/fake sends; a transport declaring
|
|
3466
|
+
`delaysDispatch` must invoke it exactly once at its last safe pre-send boundary.
|
|
3467
|
+
The Node sender does this before a direct send or after CONNECT/TLS establishment,
|
|
3468
|
+
before exposing the socket to undici's destination Client. A refusal there must
|
|
3469
|
+
never reach the destination or acquire dispatch provenance. Each send owns its
|
|
3470
|
+
Client/ProxyAgent and response lifetime, with HTTP/1.1, non-idempotent dispatch,
|
|
3471
|
+
no interceptors and no redirects. No dispatcher, response, callback or signal is
|
|
3472
|
+
cached across requests. This seam is transport policy; SDK protocol, auth and
|
|
3473
|
+
request/coordinator ownership remain with their existing adapters.
|
|
3474
|
+
|
|
3475
|
+
### Contributor reference: OAuth credential transport seam
|
|
3476
|
+
|
|
3477
|
+
`sendCredentialPost` in `src/guarded-transport.ts` is a Promise platform leaf for
|
|
3478
|
+
SDK-authenticated token, registration and revocation POSTs. It resolves the
|
|
3479
|
+
owned policy, forces manual redirects and withholds native exception text/cause,
|
|
3480
|
+
while preserving the caller's original abort reason. The SDK still constructs
|
|
3481
|
+
auth headers/forms and decodes the byte-safe response; this leaf supplies no
|
|
3482
|
+
OAuth retry, grant, response-decoding or ownership orchestration.
|
|
3483
|
+
|
|
3484
|
+
`OAuthRefreshCoordinator.coordinatedFetch` accepts an optional final synchronous
|
|
3485
|
+
`prepareCredentialRequest` hook. Production adapters resolve `guardedTransport(true)`
|
|
3486
|
+
there before claiming a code/refresh grant, so an unsupported Node host consumes
|
|
3487
|
+
neither. The base-fetch leaves in static OAuth and remote MCP call
|
|
3488
|
+
`sendCredentialPost`; remote registration and disconnect revocation do so too.
|
|
3489
|
+
D4 must preserve this preflight and the distinction between request-owned code
|
|
3490
|
+
exchange and coordinator-owned refresh after claim submission. The existing
|
|
3491
|
+
spent-token algorithm intentionally remains conservative after a submitted claim,
|
|
3492
|
+
even if a later CONNECT fails before destination dispatch. D6 must preserve the
|
|
3493
|
+
SDK JSON-RPC envelope distinction at the leaf: protocol POSTs are not credential
|
|
3494
|
+
POSTs; forms and registration JSON are. Discovery GETs remain native fetch.
|
|
3495
|
+
|
|
3496
|
+
### Contributor reference: Node response drain
|
|
3497
|
+
|
|
3498
|
+
The Node listener counts active ServerResponses per socket. Once shutdown starts,
|
|
3499
|
+
each finish/close removes its response; the last completion calls `destroySoon`
|
|
3500
|
+
to flush queued bytes and close the connection. This also covers SSE whose
|
|
3501
|
+
headers were sent before shutdown and pipelined responses waiting behind an
|
|
3502
|
+
inflight response. Normal keepalive remains Node-owned until shutdown. Idle and
|
|
3503
|
+
never-admitted sockets retain their separate close paths, and the configured
|
|
3504
|
+
shutdown deadline still forces exit 1 for work that never completes.
|
|
3505
|
+
|
|
3506
|
+
### Native auth programs (contributor)
|
|
3507
|
+
|
|
3508
|
+
Inbound verification, shared authentication admission, machine-token verification,
|
|
3509
|
+
static OAuth and the SDK-facing downstream OAuth provider compose private native
|
|
3510
|
+
programs. `auth/native.ts` selects registered implementations by the current
|
|
3511
|
+
function identity; an original Promise shell enters its own implementation
|
|
3512
|
+
without re-reading an overridden method. Class registrations retain their
|
|
3513
|
+
original method and prototype/key separately, so instance, inherited and
|
|
3514
|
+
prototype decorators can delegate to a saved original without recursion. Custom
|
|
3515
|
+
adapters and overridden methods retain their Promise boundary and original errors.
|
|
3516
|
+
|
|
3517
|
+
`auth/native-call.ts` provides the canonical `RequestScope` Context from the
|
|
3518
|
+
existing call view. Native grant preparation and storage reads execute inside
|
|
3519
|
+
that Context and refuse a closed owner; no second request-identity table is
|
|
3520
|
+
introduced. Keeping that call-only module separate also keeps downstream call
|
|
3521
|
+
services out of metadata and operator UI imports. Native storage uses D2's
|
|
3522
|
+
registered storage programs; custom stores remain one-operation Promise leaves.
|
|
3523
|
+
|
|
3524
|
+
`authFlow` composes yielded Effects while delivering original typed failures to
|
|
3525
|
+
local auth policy catches. Interruption remains interruption, runs yielded
|
|
3526
|
+
cleanup and never becomes a diagnostic Cause. A fetch's settlement observer
|
|
3527
|
+
cancels a late body. The resource sender records only retained status/refusal
|
|
3528
|
+
facts after cancellation; it cannot renew a call capability or send another
|
|
3529
|
+
request. Synchronous abort observers finish recording those facts before native
|
|
3530
|
+
wait cleanup removes them.
|
|
3531
|
+
|
|
3532
|
+
Admission charges track the actual settlement of SDK, crypto, fetch and custom
|
|
3533
|
+
Promise leaves. Submitted native storage work has independent settlement so a
|
|
3534
|
+
caller-facing deadline cannot release its charge early. Isolate-wide admission
|
|
3535
|
+
state remains scalar charges, counters and weak reachability; no request-owned
|
|
3536
|
+
client, signal, body or Promise is cached across requests. Organization cache
|
|
3537
|
+
TTLs, lookup ordering and rate-limit backoff remain unchanged.
|
|
3538
|
+
|
|
3539
|
+
Admission composes explicit native continuations rather than suspending a
|
|
3540
|
+
generator that contains its charge token. The workerd collection regression
|
|
3541
|
+
holds live middleware work charged, ends a different invocation, then proves
|
|
3542
|
+
its unreachable charges are collectable. Neither deadline nor abandonment
|
|
3543
|
+
releases a charge; only actual settlement or proven unreachability does.
|
|
3544
|
+
|
|
3545
|
+
Refresh claims still transfer ownership synchronously with claim-CAS submission,
|
|
3546
|
+
before its answer. The bounded native coordinator starts without request/call
|
|
3547
|
+
Context authority and may persist a rotation after its caller closes. A closed
|
|
3548
|
+
caller cannot consume that result or resume sending. The cross-request refresh
|
|
3549
|
+
join deliberately crosses a Promise boundary so workerd resumes each waiter in
|
|
3550
|
+
its own request. Code exchange remains request-owned and one-use. Epochs,
|
|
3551
|
+
permanent spent fingerprints, physical-attempt guards and pre-claim transport
|
|
3552
|
+
preflight are unchanged.
|
|
3553
|
+
|
|
3554
|
+
`authStorage(storage, key, args, beforeSubmit?)` admits the ownership hook and
|
|
3555
|
+
native/custom storage invocation in one JavaScript reaction. Its public
|
|
3556
|
+
Scheduler delegation suppresses automatic yield only during that initial
|
|
3557
|
+
reaction; a microtask restores parent scheduling before pending I/O resumes.
|
|
3558
|
+
The four hook sites are `updateGrant`'s guarded write and `claimRefresh`'s
|
|
3559
|
+
unknown-lease refusal, ambiguous spent-token write and new claim. A forced
|
|
3560
|
+
scheduler regression covers both native and custom stores; real pending TCP
|
|
3561
|
+
I/O, pre-submission cancellation, synchronous failure and unknown CAS outcomes
|
|
3562
|
+
retain their distinct ownership and exactly-once settlement rules.
|
|
3563
|
+
|
|
3564
|
+
Intentional leaves are the Clerk and MCP SDKs (including their callback methods),
|
|
3565
|
+
platform fetch/body readers and Web Crypto, custom identity/gate/sealer/storage
|
|
3566
|
+
hooks, and the reset-settlement observer required by the callback route. Native
|
|
3567
|
+
programs compose around those leaves directly. Public Promise APIs, SDK method
|
|
3568
|
+
signatures and published private class declarations remain unchanged; only
|
|
3569
|
+
`runtime/run.ts` starts fibers. SDK telemetry remains disabled, and raw SDK,
|
|
3570
|
+
body, storage or Cause diagnostics never reach operator records.
|
|
3571
|
+
|
|
3572
|
+
The declaration comparison retains 86 of 88 reachable files byte for byte.
|
|
3573
|
+
The two exact exceptions swap only adjacent `title` / `instructions` members
|
|
3574
|
+
in the inferred provider and Google Workspace schema declarations; both keep
|
|
3575
|
+
the same `Field<unknown, unknown, true>` type. Both owning source files are
|
|
3576
|
+
unchanged. A narrow machine check restores only those two exact adjacent
|
|
3577
|
+
pairs and requires byte equality, with no other type, export or reference
|
|
3578
|
+
change. All 88 declarations still exclude Effect and have complete references.
|
|
3579
|
+
|
|
3580
|
+
The superseding 2026-10-25 D3/D4 cost ruling authorizes only Worker
|
|
3581
|
+
`maxGzip: 785000` (previously 760000) and Clerk `maxGzip: 85000` (previously
|
|
3582
|
+
63087). Official cached Node 22.23.4 / zlib 1.3.1-e00f703 measured Clerk at
|
|
3583
|
+
47,688 -> 80,961 bytes gzip (+33,273): the generated SDK is unchanged, while
|
|
3584
|
+
native Effect adds 91.8 KB raw plus the native helper and small auth-source
|
|
3585
|
+
growth. The isolated D4 candidate measured Worker at 762,666, root at 377,177
|
|
3586
|
+
and D1 at 88,321 bytes gzip. Optional-graph optimization brought UI to 342,955,
|
|
3587
|
+
so the earlier unused UI allowance is revoked and its original 345000 cap
|
|
3588
|
+
remains. Historical baselines, root/D1 and all other caps, dependency pins and
|
|
3589
|
+
security/API gates remain unchanged. These independent measurements do not
|
|
3590
|
+
predict a combined gzip by addition; final artifacts must be rebuilt. Native
|
|
3591
|
+
registration, decorated fallback and request/native-storage semantics also
|
|
3592
|
+
require a smoke through an actual minified production bundle. Final post-hook
|
|
3593
|
+
official artifacts measure root 377,290, D1 88,321, Clerk 80,959, UI 342,959
|
|
3594
|
+
and Worker 762,774 bytes gzip, all within those caps.
|
|
3595
|
+
|
|
3596
|
+
### Native remote MCP programs (contributor)
|
|
3597
|
+
|
|
3598
|
+
Remote MCP connection joins, credential and epoch checks, catalog intake,
|
|
3599
|
+
negotiation, calls, resources, Skills, auth and status compose native programs in
|
|
3600
|
+
`connectors/remote-mcp.ts`. `catalog-cache.ts` owns native SDK cache reads,
|
|
3601
|
+
chunked publication, generation fencing, complete refresh acceptance and listing
|
|
3602
|
+
serialization; `connectors/negotiation-cache.ts` owns native negotiation storage.
|
|
3603
|
+
The published connector, cache and SDK callback methods remain Promise shells.
|
|
3604
|
+
Native consumers select those programs through `authCall` / `authInvoke` by
|
|
3605
|
+
current function identity. Each original shell enters its own immutable program,
|
|
3606
|
+
so a decorator or inherited delegate can call a saved original without recursion.
|
|
3607
|
+
`invocation-auth.ts` follows that same identity dispatch for local credential
|
|
3608
|
+
checks. The remote call registers its final original function with
|
|
3609
|
+
`runtime/call-entry.ts`, preserving invocation validation, policy and call services.
|
|
3610
|
+
A replaced function cannot inherit that native call proof.
|
|
3611
|
+
|
|
3612
|
+
Connector and cache state use the canonical `RequestScope` from the existing
|
|
3613
|
+
view services. The shared connection provides only that request owner, never the
|
|
3614
|
+
first waiter's call services, credentials or budget. SDK cache callback edges
|
|
3615
|
+
restore that same owner. Every operation captures its own generation, credential
|
|
3616
|
+
identity and listing binding before waiting; a later listing cannot adopt an
|
|
3617
|
+
abandoned publication. Request closure revokes the connector and cache scopes.
|
|
3618
|
+
No client, transport, response, signal, promise or live request Context is shared
|
|
3619
|
+
across inbound requests. D4's bounded refresh coordinator retains its separate
|
|
3620
|
+
ownership, synchronous submission gate and waiting-request handoff.
|
|
3621
|
+
|
|
3622
|
+
Two deliberate Promise waits remain: a connect waiter receives the Deferred's
|
|
3623
|
+
answer in its own workerd request continuation, and a serialized listing waits
|
|
3624
|
+
for a Promise turn before running its native program. Cancelling a queued
|
|
3625
|
+
listing releases its turn only after its predecessor, so it cannot unlock a
|
|
3626
|
+
listing still using the SDK cache binding. Cancellation after entry ends that
|
|
3627
|
+
listing's publication without starving a live sibling. Teardown starts native
|
|
3628
|
+
finalizers before checking caller ownership and joins their bounded independent
|
|
3629
|
+
settlement. Only `runtime/run.ts` starts fibers.
|
|
3630
|
+
|
|
3631
|
+
Physical sender Promise observers retain response status, write refusal and
|
|
3632
|
+
redirect facts even when a native waiter closes first. The call's abort path
|
|
3633
|
+
yields one scheduler reaction before settling those facts, including when the
|
|
3634
|
+
SDK rejects synchronously from the abort event. These observations grant no live
|
|
3635
|
+
authority: a closed caller cannot renew, send, publish a cache result or consume
|
|
3636
|
+
late output. Source/effective write classification, one physical attempt,
|
|
3637
|
+
withheld transport text, explicit downstream tool errors, input suspension,
|
|
3638
|
+
final output validation and original abort/error identity remain enforced.
|
|
3639
|
+
|
|
3640
|
+
Intentional leaves are the pinned MCP SDK's actual client/transport operations
|
|
3641
|
+
and schema validators, the owned one-attempt sender and platform fetch,
|
|
3642
|
+
Web Crypto fingerprints, bounded byte/SSE stream readers and body cancellation,
|
|
3643
|
+
and unregistered custom token, credential, storage, sealer or observation hooks.
|
|
3644
|
+
SDK `ResponseCacheStore` is the callback contract, not a replacement client or
|
|
3645
|
+
protocol implementation. Its storage and refresh algorithms are native programs;
|
|
3646
|
+
its Promise shells are required SDK edges. The connector keeps modern/legacy
|
|
3647
|
+
negotiation, header mirroring, cache partitions and TTL semantics. No SDK/Effect
|
|
3648
|
+
pin or supported protocol surface changes.
|
|
3649
|
+
|
|
3650
|
+
Native storage and activity selection in `runtime/native-storage.ts` checks the
|
|
3651
|
+
current methods against their original registered identity, and selected programs
|
|
3652
|
+
check again when executed. A replacement or decorator owns the Promise fallback;
|
|
3653
|
+
restoring originals restores native composition. Registry storage namespaces
|
|
3654
|
+
select the underlying current methods at dispatch, including namespaces created
|
|
3655
|
+
while a source method is decorated. Original SQL shells call their own native
|
|
3656
|
+
program directly. Tests cover current and previously selected programs, own and
|
|
3657
|
+
inherited saved-original delegates, get/set/CAS and activity record/list,
|
|
3658
|
+
restoration, typed failures and interruption. The D4 native proof fixtures install
|
|
3659
|
+
their intentional throwing Promise shells before registration, so they establish
|
|
3660
|
+
native composition without authorizing bypass of later replacements.
|
|
3661
|
+
|
|
3662
|
+
`remote-mcp-native.test.ts` runs native SDK/cache/request-boundary cases on Node
|
|
3663
|
+
and workerd. `remote-mcp-native-bundle.node.test.ts` additionally loads an actual
|
|
3664
|
+
minified production bundle to verify call/cache registration, request Context,
|
|
3665
|
+
native storage, saved-original delegates, custom replacements and closed-owner
|
|
3666
|
+
refusal. Existing wire, physical-attempt, cache and OAuth lifetime suites remain
|
|
3667
|
+
the compatibility evidence.
|
|
3668
|
+
|
|
3669
|
+
### Native upward MCP orchestration (contributor)
|
|
3670
|
+
|
|
3671
|
+
`routes/mcp.ts` composes server preparation, native catalog selection, exchange,
|
|
3672
|
+
output redaction and response-owned request cleanup as native programs. The
|
|
3673
|
+
pinned SDK's modern `createMcpHandler` and a fresh stateless legacy JSON transport
|
|
3674
|
+
remain the protocol implementations. Effect 4.0.5's public MCP adapters cannot
|
|
3675
|
+
serve the required fresh legacy contract; this implementation does not adopt
|
|
3676
|
+
Effect McpServer. Each inbound request constructs fresh servers and keeps its
|
|
3677
|
+
admission scope until its response body ends, fails, is cancelled or reaches its
|
|
3678
|
+
existing deadline. Authentication and endpoint admission precede body dispatch.
|
|
3679
|
+
The declared-agent observation stays after successful endpoint admission and
|
|
3680
|
+
before listen classification or body dispatch.
|
|
3681
|
+
|
|
3682
|
+
Meta tools, native tools, Skills/resources and upward auth/input continuations
|
|
3683
|
+
compose registered native programs. SDK registration signatures and Promise
|
|
3684
|
+
shells remain unchanged. Current-method identity governs consumer dispatch;
|
|
3685
|
+
each original shell enters its own immutable program, so decorators, inherited
|
|
3686
|
+
delegates and replacements retain authority. Catalog policy failures reach
|
|
3687
|
+
native consumers as ordinary typed failures. Invocation still owns validation,
|
|
3688
|
+
classification, read-only enforcement, dispatch, write settlement and accounting.
|
|
3689
|
+
Its input and result callbacks select native programs while custom synchronous
|
|
3690
|
+
or Promise callbacks retain their original behavior.
|
|
3691
|
+
|
|
3692
|
+
The canonical `RequestScope` travels through SDK callback edges and registry
|
|
3693
|
+
views. A continuation's durable nonce claim still precedes dispatch, retains its
|
|
3694
|
+
caller, endpoint, arguments, TTL and round bindings, and cannot authorize another
|
|
3695
|
+
write after an ambiguous result. Retained classification and settlement facts
|
|
3696
|
+
never reopen a closed request. A finished program can still shape its response
|
|
3697
|
+
and retained recovery facts after its connector authority closes; the inbound
|
|
3698
|
+
request signal still cancels that response work. Neither path grants another
|
|
3699
|
+
dispatch. Agent-output redaction, withheld diagnostics,
|
|
3700
|
+
secret registration and result bounds retain their existing pure algorithms.
|
|
3701
|
+
`upwardStep` is only a typed generator adapter: it lets `authFlow` deliver the
|
|
3702
|
+
original ordinary failure to a local catch without converting interruption.
|
|
3703
|
+
|
|
3704
|
+
Concurrent authorization failures share one request-owned producer and Deferred
|
|
3705
|
+
answer per connector, including failures and the unknown-write advice variant.
|
|
3706
|
+
The existing `forkRequest` seam starts that producer under the request Scope;
|
|
3707
|
+
its Context contains only the canonical request owner and scheduler. A waiting
|
|
3708
|
+
call's budget, credentials and cancellation do not own the producer. Closing the
|
|
3709
|
+
request revokes it, and a closed request cannot consume or restart its handoff.
|
|
3710
|
+
Only `runtime/run.ts` starts fibers. Admission and downstream refresh/connection
|
|
3711
|
+
waits retain their deliberate Promise handoffs for workerd's waiting-request I/O
|
|
3712
|
+
context.
|
|
3713
|
+
|
|
3714
|
+
Intentional leaves are actual pinned SDK handler/transport/server operations,
|
|
3715
|
+
SDK schema and request-state codecs, platform body readers and cancellation,
|
|
3716
|
+
Web Crypto hashes, vault owner/seal/open/sign/verify methods and unregistered custom
|
|
3717
|
+
identity, credential, storage, executor and tool callbacks. Connecta's OAuth-link
|
|
3718
|
+
binding, TTL, progress, closing and one-use claims are native orchestration around
|
|
3719
|
+
those leaves. The supported protocol and unsupported-feature refusals remain
|
|
3720
|
+
unchanged. Native consumer, failure, decorator, sharing, close and minified-bundle
|
|
3721
|
+
regressions live in `upward-native.test.ts` and
|
|
3722
|
+
`upward-native-bundle.node.test.ts`; existing wire, continuation, redaction,
|
|
3723
|
+
physical-attempt and body/admission suites remain compatibility evidence.
|
|
3724
|
+
|
|
3725
|
+
The combined native/operator graph has a measured contributor bundle allowance
|
|
3726
|
+
in PR #1060: the UI gzip cap is 346,000 bytes, with its historical baseline
|
|
3727
|
+
unchanged. The official full checker measured 345,228 bytes after regenerating
|
|
3728
|
+
the merged UI assets. Removing unused Invocation/Catalog registration retention
|
|
3729
|
+
returned the public Worker entry from 198,744 to 77,426 bytes; live minified
|
|
3730
|
+
Context, inherited-method, decorator and restoration proofs retain native
|
|
3731
|
+
composition. Root, example Worker, public Worker, D1 and Clerk caps remain
|
|
3732
|
+
unchanged. This cost decision grants no later request-body-limit allowance.
|
|
3733
|
+
|
|
3734
|
+
### Catalog operation vocabulary (contributor)
|
|
3735
|
+
|
|
3736
|
+
The standard hatch privately registers bounded vendor operation text against its
|
|
3737
|
+
ApiTool identity; `apiConnector` transfers those facts to the constructed
|
|
3738
|
+
connector. `operation-vocabulary.ts` contains only pure lexical/cache logic and
|
|
3739
|
+
types. Catalog discovery consumes those facts after loading and classifying a
|
|
3740
|
+
complete visible catalog. It never imports a provider or an operation index,
|
|
3741
|
+
parses operation contracts, invokes a handler, or authenticates. Matching requires
|
|
3742
|
+
all terms in a single operation and a visible destination hatch. Endpoint,
|
|
3743
|
+
connector and tool grants are checked on every query, outside the request-free
|
|
3744
|
+
cache. Exact authored names and complete authored matches precede API pointers;
|
|
3745
|
+
ordinary result limits, offsets, catalog failure disclosure and byte bounds apply.
|
|
3746
|
+
The published discovery contract states compilation, query and cache bounds.
|
|
3747
|
+
No request identity, signal, credential or promise enters a vocabulary cache.
|
|
3748
|
+
|
|
3749
|
+
### Authenticated body-bound cost (contributor)
|
|
3750
|
+
|
|
3751
|
+
The separate #860 change has a measured allowance only for the example Worker
|
|
3752
|
+
gzip cap, from 785,000 to 786,000 bytes, with its historical baseline unchanged.
|
|
3753
|
+
The official full checker measures 785,036 bytes against D7's exact 784,948-byte
|
|
3754
|
+
lower graph, an actual 88-byte increase. It reuses the bounded platform body
|
|
3755
|
+
inspector, preserves native orchestration and avoids a new reader lifecycle.
|
|
3756
|
+
Root, UI, public Worker, D1, Clerk and asset caps remain fixed. The cost ruling
|
|
3757
|
+
does not approve protocol, cancellation or authority semantics.
|