@zackbart/connecta 0.37.0 → 0.38.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.
Files changed (183) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +2 -2
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/auth/clerk-sdk/generated.js +2 -1
  8. package/dist/auth/cloudflare-access.d.ts +3 -1
  9. package/dist/auth/cloudflare-access.js +22 -11
  10. package/dist/auth/downstream-oauth.js +195 -270
  11. package/dist/auth/downstream-revocation.js +1 -1
  12. package/dist/auth/oauth-grant.js +11 -0
  13. package/dist/auth/oauth-token-response.js +1 -1
  14. package/dist/auth/vercel-agent.js +2 -1
  15. package/dist/auth-elicitation.js +2 -2
  16. package/dist/branding.d.ts +1 -15
  17. package/dist/branding.js +2 -2
  18. package/dist/bytes.js +42 -0
  19. package/dist/call-admission-context.js +2 -5
  20. package/dist/call-admission.js +5 -0
  21. package/dist/call-recovery.js +0 -3
  22. package/dist/catalog-cache.js +26 -7
  23. package/dist/catalog-fingerprint.js +2 -18
  24. package/dist/catalog-revision.js +9 -0
  25. package/dist/catalog-service.js +51 -20
  26. package/dist/config-defaults.d.ts +1 -0
  27. package/dist/config-defaults.js +1 -0
  28. package/dist/config-schema.d.ts +1 -1
  29. package/dist/config-schema.js +6 -2
  30. package/dist/config.d.ts +41 -14
  31. package/dist/config.js +21 -71
  32. package/dist/connector-caller.js +1 -1
  33. package/dist/connectors/api-connector.js +4 -3
  34. package/dist/connectors/guarded-fetch.d.ts +0 -6
  35. package/dist/connectors/guarded-fetch.js +1 -11
  36. package/dist/connectors/remote-mcp.js +93 -69
  37. package/dist/connectors/resource-uri.js +0 -4
  38. package/dist/credentials.js +3 -0
  39. package/dist/d1.d.ts +0 -89
  40. package/dist/d1.js +0 -34
  41. package/dist/downstream-input.js +2 -2
  42. package/dist/execute.js +161 -116
  43. package/dist/executor-result.js +2 -1
  44. package/dist/executors/quickjs-runtime.js +2 -25
  45. package/dist/executors/quickjs.js +1 -1
  46. package/dist/guest-runtime.js +5 -3
  47. package/dist/guest-types.d.ts +5 -5
  48. package/dist/http/client.js +1 -1
  49. package/dist/http/errors.js +2 -2
  50. package/dist/http/json-schema.js +1 -1
  51. package/dist/http/node-transport.js +0 -1
  52. package/dist/http/oauth.js +1 -1
  53. package/dist/http/paging.js +6 -9
  54. package/dist/http/provider.js +1 -1
  55. package/dist/http/safety.js +1 -1
  56. package/dist/http/services.js +1 -8
  57. package/dist/http/tools.js +1 -7
  58. package/dist/http/vendor.js +7 -7
  59. package/dist/identity.js +2 -6
  60. package/dist/index.js +1 -2
  61. package/dist/invocation-auth.js +2 -1
  62. package/dist/invocation.js +8 -2
  63. package/dist/meta-tools.js +27 -27
  64. package/dist/native-tools.js +2 -1
  65. package/dist/oauth-handoff.js +2 -2
  66. package/dist/operation-vocabulary.js +1 -1
  67. package/dist/operator-record.d.ts +8 -2
  68. package/dist/operator-record.js +18 -50
  69. package/dist/operator-ui/contract.d.ts +4 -2
  70. package/dist/operator-ui/generated.js +3 -3
  71. package/dist/operator-ui/model.d.ts +7 -2
  72. package/dist/program-writes.js +3 -8
  73. package/dist/provider-call.js +0 -7
  74. package/dist/provider.d.ts +1 -1
  75. package/dist/providers/_shared/google/kit.js +1 -6
  76. package/dist/providers/_shared/google/service-account.js +1 -3
  77. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  78. package/dist/providers/_shared/guide.js +3 -0
  79. package/dist/providers/_shared/json.js +12 -0
  80. package/dist/providers/_shared/rest/compact-index.js +1 -1
  81. package/dist/providers/_shared/rest/kit.js +1 -3
  82. package/dist/providers/_shared/rest/operation-index.js +1 -3
  83. package/dist/providers/_shared/rest/value-safety.js +26 -28
  84. package/dist/providers/breeze/api.js +2 -7
  85. package/dist/providers/breeze/index.js +4 -2
  86. package/dist/providers/breeze/tools.js +3 -4
  87. package/dist/providers/ccb/api.js +1 -6
  88. package/dist/providers/ccb/index.js +2 -1
  89. package/dist/providers/ccb/tools.js +1 -6
  90. package/dist/providers/cloudflare/api.js +2 -2
  91. package/dist/providers/cloudflare/index.js +2 -1
  92. package/dist/providers/cloudflare/safety.js +3 -3
  93. package/dist/providers/docs/tools.js +2 -10
  94. package/dist/providers/drive/tools.js +12 -25
  95. package/dist/providers/forms/tools.js +1 -9
  96. package/dist/providers/gmail/tools.js +5 -21
  97. package/dist/providers/overflow/api.js +1 -3
  98. package/dist/providers/overflow/index.js +2 -1
  99. package/dist/providers/overflow/tools.js +3 -7
  100. package/dist/providers/planning-center/api.js +2 -1
  101. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  102. package/dist/providers/planning-center/lib/json-api.js +1 -9
  103. package/dist/providers/planning-center/tools.js +2 -1
  104. package/dist/providers/sheets/tools.js +1 -9
  105. package/dist/providers/slides/tools.js +4 -13
  106. package/dist/providers/stripe/index.js +2 -1
  107. package/dist/providers/stripe/safety.js +2 -2
  108. package/dist/providers/tithely/api.js +2 -7
  109. package/dist/providers/tithely/index.js +3 -2
  110. package/dist/providers/tithely/tools.js +7 -5
  111. package/dist/providers/vercel/api.js +1 -6
  112. package/dist/providers/vercel/lib/pin.js +1 -1
  113. package/dist/providers/vercel/safety.js +1 -1
  114. package/dist/providers/vercel/tools.js +2 -1
  115. package/dist/redaction-marker.js +2 -2
  116. package/dist/registry.d.ts +4 -1
  117. package/dist/registry.js +35 -22
  118. package/dist/request-principal.js +15 -2
  119. package/dist/request-state.js +2 -2
  120. package/dist/routes/access-tokens.js +1 -1
  121. package/dist/routes/activity-disclosure.js +1 -1
  122. package/dist/routes/connect.js +2 -2
  123. package/dist/routes/credentials.js +1 -1
  124. package/dist/routes/dispatch.js +10 -0
  125. package/dist/routes/mcp.js +23 -21
  126. package/dist/routes/oauth-management.js +1 -1
  127. package/dist/routes/operator.js +53 -7
  128. package/dist/routes/shared.d.ts +8 -8
  129. package/dist/routes/shared.js +22 -15
  130. package/dist/routes/ui-config.js +6 -6
  131. package/dist/routes/ui-data.js +2 -2
  132. package/dist/routes/ui.js +9 -4
  133. package/dist/runtime/call-admission.js +1 -1
  134. package/dist/runtime/call.js +1 -1
  135. package/dist/runtime/settlement.js +1 -1
  136. package/dist/runtime/shared-read.js +2 -15
  137. package/dist/sent-secrets.d.ts +14 -6
  138. package/dist/sent-secrets.js +231 -85
  139. package/dist/server.js +4 -4
  140. package/dist/skills.js +8 -35
  141. package/dist/sqlite.d.ts +0 -19
  142. package/dist/sqlite.js +2 -83
  143. package/dist/storage/keys.js +29 -124
  144. package/dist/storage/sql-native.js +14 -111
  145. package/dist/storage/sql.d.ts +1 -24
  146. package/dist/storage/sql.js +1 -12
  147. package/dist/tool-error.js +4 -0
  148. package/dist/types.d.ts +4 -3
  149. package/dist/ui.js +3 -0
  150. package/dist/url-safety.js +0 -4
  151. package/dist/usage-guide.js +2 -2
  152. package/dist/version.d.ts +1 -1
  153. package/dist/version.js +1 -1
  154. package/dist/worker.js +1 -1
  155. package/documentation/README.md +6 -42
  156. package/documentation/architecture.md +190 -507
  157. package/documentation/auth.md +172 -58
  158. package/documentation/code-mode.md +98 -66
  159. package/documentation/deploying.md +19 -40
  160. package/documentation/integrating.md +8 -19
  161. package/documentation/meta-tools.md +23 -6
  162. package/documentation/migration-0.33.md +5 -5
  163. package/documentation/migration-0.34.md +2 -4
  164. package/documentation/migration-retired-layouts.md +74 -0
  165. package/documentation/operator-tests.md +19 -119
  166. package/documentation/operator-ui.md +46 -8
  167. package/documentation/provider-auth-migration.md +4 -10
  168. package/documentation/provider-migration-0.29.md +4 -15
  169. package/examples/worker/AGENTS.md +4 -6
  170. package/examples/worker/README.md +20 -115
  171. package/guides/connecting-agents.md +7 -30
  172. package/guides/how-it-works.md +2 -2
  173. package/guides/safety-and-trust.md +4 -1
  174. package/package.json +19 -19
  175. package/templates/node/README.md +9 -26
  176. package/templates/node/package.json +1 -1
  177. package/dist/auth/oauth-v2-migration.js +0 -135
  178. package/dist/provider-call-facts.d.ts +0 -115
  179. package/dist/provider-call.d.ts +0 -112
  180. package/dist/storage/d1-copy.js +0 -255
  181. package/dist/storage/sql-schema.d.ts +0 -6
  182. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  183. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
@@ -1286,16 +1286,13 @@ connector](#downstream-oauth-on-api) stores no client. A vault without the
1286
1286
  optional `seal`/`open` members keeps the body plaintext with a startup
1287
1287
  warning; no vault, plaintext as always.
1288
1288
 
1289
- **From 0.28 (layout 2).** The first read of an owner's grant that finds no
1290
- record reads layout 2 once: the active generation's issuer-stamped client,
1291
- tokens, and discovery (sealed values open under their old keys), kept only
1292
- when every credential carries a stamp and the stamps agree with each other
1293
- and with discovery. It writes them as the grant record by compare-and-set,
1294
- then deletes every layout-2 key, cleanup lineage included. A disconnected
1295
- connector stays disconnected; a cancelled request migrates nothing. Grants
1296
- from before issuer binding (v0.8.1 and earlier) and pending consents are not
1297
- carried, so those connectors need Connect again. Rolling back past this
1298
- release finds no layout-2 grant and needs consent.
1289
+ **Pre-layout-3 grants.** The layout-2 migration and historical-key cleanup
1290
+ are removed. An owner with only old keys is treated as unauthorized; a warning
1291
+ names the unsupported layout and the need to Connect again or migrate on
1292
+ 0.37.1. The check inspects key names only, never old credential values, and
1293
+ leaves them untouched. Existing layout-3 records remain readable even with an
1294
+ obsolete cleanup marker. Complete every owner's grant migration on 0.37.1
1295
+ before upgrading, or reconnect afterward; follow [Retired layout migrations](./migration-retired-layouts.md).
1299
1296
 
1300
1297
  ## Starting, restarting, and disconnecting
1301
1298
 
@@ -1349,12 +1346,15 @@ with the gate a call passes:
1349
1346
  | Tokens (any expiry) | Connects; `ok`, or `auth_required` when a 401 needs consent | Connects; `ok` without starting a flow when healthy |
1350
1347
 
1351
1348
  A start without a challenge runs the same SDK flow as a 401 without
1352
- `resource_metadata`. Issuer and resource checks, consent state, PKCE, and
1349
+ `resource_metadata`. An explicit start after a status check or call in the same
1350
+ request scope can start consent in the connection's original epoch; the status
1351
+ check and call remain passive. Issuer and resource checks, consent state, PKCE, and
1353
1352
  the CIMD, registration, and static-client order are unchanged, and nothing is
1354
1353
  sent to the MCP endpoint. A call never starts consent. A call's 401 that names
1355
1354
  per-tool metadata, such as `/.well-known/oauth-protected-resource/<tool>`,
1356
- renews through it when the grant holds no discovery. That metadata must name
1357
- the configured resource, as any other must. When renewal is refused, the grant
1355
+ renews through it when the grant holds no discovery. The challenged metadata
1356
+ URL must share the configured connector URL's origin, and its metadata must
1357
+ name the configured resource, as any other must. When renewal is refused, the grant
1358
1358
  loses its tokens and the next start hands out consent. Restart (`force: true`)
1359
1359
  removes the tokens first, so it starts consent unless the downstream or
1360
1360
  discovery fails.
@@ -1394,12 +1394,8 @@ exchange. Both built-in `finishAuth` adapters require `callbackParams` with a
1394
1394
  nonempty `state`, even with PKCE disabled or after a separate `verifyState` call.
1395
1395
  A missing state is refused before discovery, registration, or token dispatch.
1396
1396
 
1397
- Layout-2 migration commits `cleanupPending` in the new grant before deleting
1398
- historical keys. Every later grant read retries deletion, then clears the
1399
- marker by CAS only when deletion succeeds. Restart preserves this obligation.
1400
- Disconnect commits a tombstone with the marker and always removes historical
1401
- keys for the connector and owner, including when a modern grant already exists.
1402
- A crash during cleanup cannot restore the disconnected grant.
1397
+ Disconnect commits a layout-3 tombstone. Restart and Disconnect never read or
1398
+ restore historical layout-2 keys.
1403
1399
 
1404
1400
  Disconnect first removes the grant and fences its epoch with CAS. When that
1405
1401
  removed grant's saved authorization-server metadata advertises a revocation
@@ -1431,7 +1427,7 @@ lease's `claimed` to `dispatched` CAS and a separate fingerprint CAS at
1431
1427
  `oauth:refresh-spent:<sha256(refresh_token)>`. The latter must succeed before
1432
1428
  any HTTP request leaves. Connector and owner storage namespaces partition both
1433
1429
  records. Spent records have **no TTL**, contain no token, and are never deleted
1434
- by completion, failure, migration cleanup, Restart, Disconnect, or epoch sweeps.
1430
+ by completion, failure, Restart, Disconnect, or epoch sweeps.
1435
1431
  Copy these durable records during storage migration.
1436
1432
 
1437
1433
  Each fingerprint record names its dispatch epoch, holder, and resolution state:
@@ -1475,13 +1471,13 @@ body contains tokens. Fetch has already sent the request body; a refresh
1475
1471
  fingerprint is resolved as a definitive failure and the grant requires re-consent. A
1476
1472
  redirected code exchange is refused and its SDK retry cannot resend the code.
1477
1473
 
1478
- | Outcome after dispatch | Result |
1479
- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1480
- | Valid tokens durably committed | Mark the fingerprint resolved and release waiters with committed tokens. Within the epoch only a new fingerprint can be dispatched next. |
1481
- | Definitive provider failure | `auth_required`. Mark the fingerprint resolved and conditionally remove its grant tokens. Includes every 3xx without following it and complete failures such as 5xx, 408, 425, and 429. |
1482
- | Network or response-body loss, malformed or oversized success, deadline expiry, or process crash | `auth_required`. Mark the fingerprint ambiguous and conditionally remove its grant tokens. Identical-token re-consent cannot reopen it. |
1483
- | Valid rotation whose grant commit retries are exhausted | `auth_required`. Record the definitive commit failure as resolved and conditionally remove its grant tokens. Never return uncommitted tokens to the SDK. |
1484
- | Epoch changed during commit | Drop the response tokens and record the definitive failure as resolved. Restart, Disconnect, or issuer replacement determines the newer grant. A consent that completed before this resolution cannot reuse its fingerprint. |
1474
+ | Outcome after dispatch | Result |
1475
+ | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1476
+ | Valid tokens durably committed | Mark the fingerprint resolved and release waiters with committed tokens. Within the epoch only a new fingerprint can be dispatched next. |
1477
+ | Definitive provider failure | `auth_required`. Mark the fingerprint resolved and conditionally remove its grant tokens. Includes every 3xx without following it and complete failures such as 5xx, 408, 425, and 429. |
1478
+ | Network or response-body loss, malformed or oversized success, deadline expiry, or process crash | `auth_required`. Mark the fingerprint ambiguous and conditionally remove its grant tokens when storage answers within the cleanup bound. An outstanding fingerprint remains fenced if storage stalls. Identical-token re-consent cannot reopen it. |
1479
+ | Valid rotation whose grant commit retries are exhausted | `auth_required`. Record the definitive commit failure as resolved and conditionally remove its grant tokens. Never return uncommitted tokens to the SDK. |
1480
+ | Epoch changed during commit | Drop the response tokens and record the definitive failure as resolved. Restart, Disconnect, or issuer replacement determines the newer grant. A consent that completed before this resolution cannot reuse its fingerprint. |
1485
1481
 
1486
1482
  Refreshes coalesce per owner and epoch within a runtime. Across isolates, a
1487
1483
  shared-storage record at `oauth:refresh:<epoch>:<sha256(refresh_token)>` is
@@ -1494,7 +1490,10 @@ storage-owned 120-second TTL. SQLite and D1 create and check expiry with the
1494
1490
  database clock inside each statement. Custom shared stores must also use a
1495
1491
  storage-owned clock. Cross-isolate clock comparisons never permit takeover of
1496
1492
  a dispatched record. The refresh HTTP request and response body have a
1497
- 20-second deadline. Contenders wait at most 35 seconds with 10 ms to 250 ms
1493
+ 20-second deadline. After the response or a dispatched exchange failure,
1494
+ grant commit and cleanup share a separate 20-second deadline, including all
1495
+ storage reads, CAS retries, fingerprint resolution, and lease release.
1496
+ Contenders wait at most 35 seconds with 10 ms to 250 ms
1498
1497
  backoff. A waiter deadline returns retryable `unavailable`, keeps the grant,
1499
1498
  and starts no consent. It never permits resending a spent token.
1500
1499
 
@@ -1538,7 +1537,7 @@ preparation recorded the fingerprint as spent, it stays spent even if the send
1538
1537
  was subsequently cancelled or a local destination guard refused it.
1539
1538
 
1540
1539
  After dispatch, caller and SDK cancellation end only that caller's wait. The
1541
- exchange and grant commit continue under the HTTP deadline. The runtime passes
1540
+ exchange and grant commit continue under their own deadlines. The runtime passes
1542
1541
  completion to Workers `waitUntil`; paths without that hook keep a background
1543
1542
  promise. The holder re-reads the grant and retries the commit with its in-memory
1544
1543
  response tokens up to 32 times after CAS contention or storage errors. Each
@@ -1547,6 +1546,16 @@ drops the response tokens. Another consent's credentials remain intact. A lost
1547
1546
  commit answer can be recovered by reading the committed grant. Exhausting the
1548
1547
  commit retries requires re-consent.
1549
1548
 
1549
+ Expiry of the commit/cleanup deadline reports `OAuth refresh outcome is unknown`
1550
+ and requires authorization. Every exit settles the local flight and releases its
1551
+ owner partition without awaiting further storage. Late storage answers cannot
1552
+ resume the expired coordinator or start another commit or completion write.
1553
+ An already submitted CAS may still land; unconfirmed grant commits leave the
1554
+ durable spent fingerprint outstanding, so the consumed token cannot be sent
1555
+ again. Later callers read durable state rather than joining an expired local
1556
+ flight. If cleanup could not finish, the storage-owned holder TTL allows them
1557
+ to record an ambiguous outcome and conditionally discard the old grant.
1558
+
1550
1559
  Lease release uses CAS after the commit or re-consent decision. Completion
1551
1560
  records include a SHA-256 digest of the committed token response, excluding
1552
1561
  the local issuer stamp. Waiters can adopt that committed response even when
@@ -1649,13 +1658,37 @@ program returns, logs, and emits, and relayed input prompts. Each redaction
1649
1658
  projects the whole value: string and number leaves in serialization order,
1650
1659
  JSON objects, arrays, and string literals parsed into their own leaves whether
1651
1660
  they are wire text or a string inside a result, and errors' own fields. The
1652
- joined projection is matched once, so a value split across adjacent leaves,
1661
+ joined projection is matched, so a value split across adjacent leaves,
1653
1662
  such as text blocks or array items, is found; each match is replaced only in
1654
1663
  the leaves it touches, and the containers around them keep their shape. A
1655
- number that a match touches becomes the string `[redacted]`, so the JSON stays
1656
- valid. Text is reserialized only when something changed; otherwise its original
1657
- bytes remain. Serialized JSON whose keys, syntax, and leaves together hold a run
1664
+ number touched by a match is first checked again beside the replaced string
1665
+ pieces, with adjacent numbers checked together: a number stays numeric when
1666
+ string replacement breaks the match. Every replacement is then checked in the
1667
+ lane as it will be returned, including partial string replacements and whole
1668
+ numbers, typed binary, decoded binary, whole-field private values and unchecked
1669
+ runs. Omitted error numbers contribute empty text; a changed error name
1670
+ contributes one whole placeholder, including when its JSON text had several
1671
+ leaves. Whole JSON withholding and scalars withheld during the walk contribute
1672
+ their placeholders before settling. A new match touching a kept piece withholds
1673
+ that whole part. The lane is checked again until nothing changes, including
1674
+ runs formed from `[redacted]`
1675
+ and a kept string piece or number, within a string or across leaves. A span
1676
+ wholly inside a placeholder remains the placeholder.
1677
+ Each changing round withholds at least one more part, so there are at most as
1678
+ many changing rounds as parts. Every round, including its bookkeeping, shares
1679
+ the call's work budget; an unaffordable leaf or seam check withholds every
1680
+ participant, including previously untouched neighbours and every originally
1681
+ touched number still awaiting retention. A marker that could not itself be
1682
+ scanned remains in its seam windows. Unchanged lanes pay for no recheck.
1683
+ Joined and leaf-by-leaf checks follow the same rule; large lanes check
1684
+ changed parts and the seams touching them without rereading unchanged leaf
1685
+ interiors. Bounded lanes and deep escapes withhold changed numbers whole.
1686
+ Text is reserialized only when something changed; otherwise its original bytes
1687
+ remain. Serialized JSON whose keys, syntax, and leaves together hold a run
1658
1688
  that no single leaf holds, as a value spanning tokens does, is withheld whole.
1689
+ Settling covers the joined leaves and whole replacement markers; object keys,
1690
+ JSON punctuation and fixed framing do not join that lane. Keys are checked
1691
+ alone, and serialized token-spanning matches use the whole-JSON rule above.
1659
1692
  Matching runs on the raw text and through one more layer of JSON string escapes
1660
1693
  at a time while the last layer still decodes an escape, at most eight layers;
1661
1694
  text still holding escapes past that is withheld. Each decoded layer is scanned
@@ -1682,7 +1715,8 @@ fields that are refused instead of rewritten. Object keys are checked on their
1682
1715
  own, and a key holding a match is renamed. Image, audio, resource-blob, or
1683
1716
  `format: "base64"` content, or a REST body's `base64` beside its `contentType`,
1684
1717
  is checked as text and also decoded whole and checked as bytes and as UTF-8,
1685
- then withheld whole as the base64 of `[redacted]` on a match; content that is
1718
+ then withheld whole as the base64 of `[redacted]` on a match, or empty if
1719
+ that replacement encoding cannot be returned safely; content that is
1686
1720
  not valid base64 as it stands is still checked through every text view. Base64
1687
1721
  or base64url in any string is decoded and checked in each of the string's text
1688
1722
  views: the text itself, the text with one layer of JSON escapes decoded
@@ -1771,10 +1805,15 @@ Every other value becomes the data JSON shows for it, checked, or is withheld:
1771
1805
 
1772
1806
  Typed binary is withheld as `[redacted]` when its bytes hold a match, checked as
1773
1807
  decoded binary content is, or when one of the numbers JSON shows for it does:
1774
- its elements and, for the `Uint8Array` returned, its bytes, each checked as a
1775
- JSON number and joined with its neighbours. A one-to-three-character numeric
1776
- private value, such as `7`, therefore withholds a typed array holding that
1777
- element, as it withholds an equal JSON number. Each of those numbers is an entry
1808
+ the returned `Uint8Array`'s byte numbers join neighbouring leaves directly.
1809
+ For non-byte typed arrays, a separate projection substitutes the original
1810
+ element numbers for the byte numbers, keeping the same neighbours. Neither
1811
+ numeric view interrupts the other; the raw byte view is checked on its own.
1812
+ A match in either numeric projection withholds the binary as one whole part,
1813
+ so its placeholder is checked beside its neighbours before return. Each number
1814
+ is also checked as a whole field: a one-to-three-character numeric private
1815
+ value, such as `7`, withholds a typed array holding that element, as it
1816
+ withholds an equal JSON number. Each of those numbers is an entry
1778
1817
  of the walk's 1,048,576-entry budget, so binary past it is withheld whole.
1779
1818
  Connecta returns downstream binary as base64 text, which none of this limits;
1780
1819
  only in-process code can return a typed array. With nothing registered for a
@@ -1857,7 +1896,33 @@ structured value, and before result paging, emits, and program outputs/errors/lo
1857
1896
 
1858
1897
  `remoteMcp()` learns server-side fetch URLs from a 401's `resource_metadata`,
1859
1898
  its `authorization_servers`, and advertised token and registration endpoints.
1860
- To prevent a downstream from targeting the host's private network:
1899
+ A challenged `resource_metadata` URL must have the same origin as the
1900
+ configured connector `url`: scheme, host, and port after normal URL parsing.
1901
+ This applies during status, authorization starts, and tool calls. A different
1902
+ origin is refused before SDK discovery, with non-retryable
1903
+ `connector_call_failed` and a fixed message that includes no challenged URL
1904
+ or host; there is no allowlist.
1905
+
1906
+ Before every SDK discovery read, including authorization callbacks and flows
1907
+ without a challenge, a cached `resourceMetadataUrl` outside the configured
1908
+ origin (or a malformed URL) and its cached resource document are discarded
1909
+ from the discovery state given to the SDK. The stored grant is not changed by
1910
+ that read. The SDK rediscovers resource metadata using the current same-origin
1911
+ challenge, or the configured resource's same-origin well-known locations when
1912
+ there is no challenge. The cached authorization-server binding is retained so
1913
+ callback issuer checks still apply. If rediscovery changes a pending consent's
1914
+ discovery binding, its callback refuses before code exchange; start a new
1915
+ consent. If same-origin resource metadata returns 404, the cached
1916
+ authorization-server fallback can still complete consent against its stored
1917
+ binding. A foreign cached metadata URL is never requested, including when an older SDK flow saved only the URL after a 404.
1918
+
1919
+ Paths may differ: a resource at `https://crm.example.com/mcp` may challenge with
1920
+ `https://crm.example.com/oauth/resources/tickets`. Google's per-tool shape,
1921
+ `https://bigquery.googleapis.com/.well-known/oauth-protected-resource/<tool>`,
1922
+ is likewise accepted for a resource on `https://bigquery.googleapis.com`.
1923
+
1924
+ Other advertised OAuth URLs follow these rules to prevent a downstream from
1925
+ targeting the host's private network:
1861
1926
 
1862
1927
  - A URL on the connector's configured origin is trusted; the operator wrote it.
1863
1928
  - Any other URL must be `https` and must not name a private host: `localhost`
@@ -1869,8 +1934,9 @@ To prevent a downstream from targeting the host's private network:
1869
1934
  working. LAN and link-local addresses stay refused.
1870
1935
 
1871
1936
  A refused URL is never requested. Discovery, registration, and code exchange
1872
- fail with a non-retryable `connector_call_failed` naming the host and nothing
1873
- else from the URL. A refused refresh never reaches the token endpoint. If the
1937
+ fail with a non-retryable `connector_call_failed`. Resource-metadata origin
1938
+ refusals include no advertised host; other URL refusals name only the host. A
1939
+ refused refresh never reaches the token endpoint. If the
1874
1940
  send gate already recorded its fingerprint, that fingerprint remains blocked
1875
1941
  and the grant requires re-consent. Redirects cannot route around the
1876
1942
  rule; token requests never follow them, and resource redirects follow only
@@ -1908,8 +1974,8 @@ anything, so SDK hook order cannot mix servers or cross a reset.
1908
1974
 
1909
1975
  Management follows `maintainers` and pool `members`; no resolver configures it. The former `identity.credentialAdministration`,
1910
1976
  `identity.personalConnection`, `identity.accessTokenManagement`, and
1911
- `identity.activityAccess` each fail construction naming their replacement,
1912
- and so does the `identity` section itself, even empty.
1977
+ `identity.activityAccess` fail construction as unknown options under `identity`,
1978
+ including an empty `identity` section.
1913
1979
 
1914
1980
  | Action | Who may |
1915
1981
  | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -1998,16 +2064,35 @@ identity, or unusable user ids refuse. Access service credentials can admit the
1998
2064
  request at the edge, but connecta requires a `cta_` token at `/agent/<pool>`
1999
2065
  for machine identity.
2000
2066
 
2001
- It never reads `Cf-Access-Jwt-Assertion`, fetches signing keys, or accepts a
2002
- caller's JWT. Missing or malformed trusted context returns 401. A rejected
2003
- human identity, including a service identity or an unusable user id, returns 403. A thrown identity lookup or its five-second deadline returns retryable
2067
+ It never parses or verifies `Cf-Access-Jwt-Assertion`, fetches signing keys, or
2068
+ accepts a caller's JWT. The assertion header's presence is only an untrusted
2069
+ hint that Access context was expected: without `ctx.access`, that request
2070
+ returns retryable 503 with `problem: "access_context_unavailable"`, including
2071
+ the first request on a fresh instance. It grants no identity and never falls
2072
+ back to another provider. Missing context without an assertion still returns 401. An unusable trusted AUD returns retryable 503 with
2073
+ `problem: "access_audience_invalid"`; it never proceeds to identity lookup.
2074
+ A rejected human identity, including a service identity or an unusable user
2075
+ id, returns 403. A thrown identity lookup or its five-second deadline returns retryable
2004
2076
  503 with `problem: "identity_unavailable"` and `Retry-After: 1`. Every refusal
2005
- carries `Cache-Control: no-store`. None of these failures admits an identity,
2077
+ carries `Cache-Control: no-store`; every unavailable-verification 503 carries
2078
+ `Retry-After: 1` and no `WWW-Authenticate` challenge. None of these failures admits an identity,
2006
2079
  and an unavailable lookup never falls back to another provider. The operator
2007
2080
  UI can re-request admission after a failure without a browser reload. It does not support Node, a `cloudflared` origin, or a Service Binding
2008
2081
  hop.
2009
2082
  Connecta configuration still decides connector access and management: `maintainers` and pool `members`.
2010
2083
 
2084
+ Every operator-surface 401 emits `[connecta] operator_refused` through the
2085
+ deployment logger. Its checked record contains `accessContext` (a boolean),
2086
+ `authorizationHeader` (`absent`, `bearer`, or `invalid`), and a closed `reason`
2087
+ code. Access codes distinguish `access_context_missing` from
2088
+ `access_authorization_header_rejected`; `authentication_provider_missing`
2089
+ identifies the generic route fallthrough, `authorization_header_invalid` a
2090
+ malformed header, and `authentication_failed` an unclassified provider refusal.
2091
+ Access verification 503s emit `[connecta] Cloudflare Access verification unavailable`
2092
+ with the same facts and `access_context_unavailable`, `access_audience_invalid`,
2093
+ or `access_identity_unavailable`. These records contain no header values,
2094
+ assertions, cookies, identities, URLs, response bodies, or exception text.
2095
+
2011
2096
  Protect the Worker with a Worker-level Access application whose destination is
2012
2097
  `{ "type": "worker", "worker_id": "<the Worker script tag>" }`. A traditional
2013
2098
  hostname-level application blocks the URL but does not attach `ctx.access` to
@@ -2438,6 +2523,15 @@ need `consumeAuthError` to accept error callbacks, sharing `finishAuth`'s atomic
2438
2523
  consent claim. Reissue
2439
2524
  pending consent links after upgrading; callbacks without a saved initiating user cannot complete.
2440
2525
 
2526
+ A connector declares code-exchange support by implementing `finishAuth`. Omit
2527
+ that hook when the connector cannot exchange a code; `remoteMcp()` exposes it
2528
+ only for `auth: { type: "oauth" }`. A code-exchange connector without
2529
+ `verifyState` logs a construction warning naming the missing hook, and its
2530
+ browser callback refuses every request before exchanging a code. Implement
2531
+ `verifyState` to enable completion; credential, request-token (including Google
2532
+ service-account tokens), principal-forwarding, and header auth need no callback
2533
+ or state hook.
2534
+
2441
2535
  Custom connectors may implement `verifyCallbackParameters(params, ctx)` for
2442
2536
  provider-specific callback assertions. The route calls it after successful
2443
2537
  state verification and before issuer validation, handoff consumption, error
@@ -2678,6 +2772,15 @@ transport, are not stored or described, and a changed token replaces the client.
2678
2772
  This mode refuses cleartext non-loopback origins. Optional `auth.headers` carry
2679
2773
  static protocol/catalog controls and cannot set Authorization.
2680
2774
 
2775
+ Status reports `auth_required` when a request-token or principal resolver returns
2776
+ an empty or invalid token or throws a typed authentication failure, including
2777
+ authentication failures from `googleServiceAccountToken`. Resolution failure
2778
+ sends no request to the MCP endpoint. Status uses the existing fixed authorization
2779
+ notice and never includes the resolver's error message. Typed outage and
2780
+ permission failures, and a resolver that throws anything other than a typed
2781
+ `ConnectorCallError`, remain error statuses; such an untyped failure still fails
2782
+ calls with `auth_required`.
2783
+
2681
2784
  Return the same token for the same caller while it remains valid, and cache a
2682
2785
  minted token until shortly before it expires. A value that changes on every
2683
2786
  resolution replaces the client each time; a change that lands while a call
@@ -2736,11 +2839,26 @@ auth: {
2736
2839
  returns the same token. `token.invalidate(rejected, ctx, { principal })`
2737
2840
  receives the principal the rejected token was minted for.
2738
2841
  - Catalog listing needs an admitted caller like any call, and its cache stays
2739
- partitioned per caller. Operator status and catalog checks carry no caller,
2740
- so they report `auth_required` without connecting.
2842
+ partitioned per caller. An operator status/catalog check forwards that
2843
+ request's admitted caller under the same `accept` policy and refusal rules
2844
+ as a call. It checks the resolver, custom `fetch` and downstream connection
2845
+ and catalog as that operator; it never substitutes another identity.
2846
+ - Without an admitted caller, status returns the non-error `per_caller` state
2847
+ with "Authenticates as each caller; verified on use." It does not resolve a
2848
+ token or contact the downstream. Calls and catalog reads still refuse a
2849
+ missing caller with `auth_required`. An admitted caller who fails `accept`
2850
+ receives the same refusal from status as from a call. The operator page
2851
+ offers no secret-configuration diagnosis or fix prompt for this refusal.
2852
+ `per_caller` is an additive member of the public `ConnectorStatus["state"]`
2853
+ union; consumers with exhaustive state handling must account for it.
2741
2854
  - The connector stays shared. Connecta keeps no per-principal credential state
2742
2855
  and never stores, logs or describes the principal or the token. `describe()`
2743
2856
  reports `mode: "request"` as for any request resolver.
2857
+ - The resolver's returned token is registered as a credential: every eight-unit
2858
+ run of it is withheld from all agent output in that request. An unsigned
2859
+ principal reference must therefore be opaque, such as the principal id or a
2860
+ random handle. Never return an email such as `alice@example.com` or another
2861
+ domain-bearing string that also appears in the deployment's own names.
2744
2862
 
2745
2863
  #### Over a Workers service binding
2746
2864
 
@@ -2777,9 +2895,11 @@ could name any person. A signature verified with a deployment-held key is also
2777
2895
  safe on a public route.
2778
2896
 
2779
2897
  `fetch` carries every MCP request, including handshakes, catalog reads, calls,
2780
- writes and session close. Global fetch is never used for that connector. The
2781
- configured `url` stays the logical origin: its HTTPS rule, cleartext refusal,
2782
- redirect policy and same-origin redirect checks apply unchanged. A request for
2898
+ writes and session close. Global fetch is never used for that connector. RPC
2899
+ responses returned by this fetch are disposed when the owning request ends,
2900
+ including responses that arrive after cancellation; their bodies remain
2901
+ available during that request. The configured `url` stays the logical origin:
2902
+ its HTTPS rule, cleartext refusal, redirect policy and same-origin redirect checks apply unchanged. A request for
2783
2903
  any other origin is refused before it reaches `fetch`. Each request, writes
2784
2904
  included, is handed to `fetch` once with `redirect: "manual"`, and a dispatched
2785
2905
  write is never sent again, so `fetch` must not retry or follow redirects. On
@@ -3000,10 +3120,6 @@ The encoding lattice is exactly this, and anything outside it is out of scope:
3000
3120
  lattice, so redaction is not a defense against a downstream that reshapes a
3001
3121
  value on purpose.
3002
3122
 
3003
- `test/sent-secret-output-boundary.test.ts` holds the evidence, including its
3004
- `review round 3`, `review round 4` and `review round 5` blocks for these limits;
3005
- `test/writeonly-wire-redaction.node.test.ts` runs the QuickJS guest cases.
3006
-
3007
3123
  ### Source and tests
3008
3124
 
3009
3125
  [Decision 0007](https://github.com/zackbart/connecta/blob/main/decisions/0007-agent-pool-layout.md)
@@ -3026,8 +3142,6 @@ Downstream OAuth implementation and evidence are in
3026
3142
  [downstream-oauth.ts](https://github.com/zackbart/connecta/blob/main/src/auth/downstream-oauth.ts),
3027
3143
  [static-oauth.ts](https://github.com/zackbart/connecta/blob/main/src/auth/static-oauth.ts),
3028
3144
  [OAuth connect tests](https://github.com/zackbart/connecta/blob/main/test/oauth-connect.test.ts),
3029
- [callback fencing](https://github.com/zackbart/connecta/blob/main/test/oauth-callback-fencing.test.ts),
3030
- [refresh leases](https://github.com/zackbart/connecta/blob/main/test/oauth-refresh-lease.test.ts),
3031
3145
  and [API OAuth tests](https://github.com/zackbart/connecta/blob/main/test/api-oauth.test.ts).
3032
3146
  Use the test pointers within each section for narrower failure cases.
3033
3147