@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
@@ -18,9 +18,10 @@ export interface UiTool extends Pick<OperatorTool, "name" | "address" | "descrip
18
18
  * (`oauth_required`, which also covers an expired or revoked grant — the
19
19
  * status seam cannot tell those apart), an operator-managed credential slot
20
20
  * (`credential_required`), or a secret that lives in deployment configuration
21
- * (`auth_required`).
21
+ * (`auth_required`). Principal forwarding instead reports `caller_refused`,
22
+ * which has no secret-repair prompt.
22
23
  */
23
- export type UiProblem = "connector_unavailable" | "oauth_required" | "credential_required" | "auth_required" | "credential_mismatch" | "catalog_failed";
24
+ export type UiProblem = "connector_unavailable" | "oauth_required" | "credential_required" | "auth_required" | "caller_refused" | "credential_mismatch" | "catalog_failed";
24
25
  /** A stored credential that cannot be used, by cause. */
25
26
  export type UiCredentialProblem = "credential_mismatch" | "credential_unreadable";
26
27
  interface UiCredentialField {
@@ -57,6 +58,8 @@ interface UiCredential {
57
58
  notice?: string;
58
59
  }
59
60
  export interface UiConnector {
61
+ /** Opaque admitted viewer partition, present on HTTP detail responses. */
62
+ viewerKey?: string;
60
63
  /** Complete catalog and status from this detail request, when supported. */
61
64
  overlay?: OperatorConnectorOverlay;
62
65
  id: string;
@@ -99,6 +102,8 @@ export interface UiConnector {
99
102
  }
100
103
  export type CredentialManagementCapability = "available" | "requires_operator" | "vault_not_configured" | "no_slots";
101
104
  export interface UiData {
105
+ /** Opaque admitted viewer partition, present on HTTP summaries. */
106
+ viewerKey?: string;
102
107
  accessTokenManagement?: "available" | "requires_operator";
103
108
  serverInfo: Pick<ConnectaConfigDescription["server"], "name" | "version">;
104
109
  /** Version of the installed @zackbart/connecta package. */
@@ -1,8 +1,8 @@
1
+ import { toolError } from "./tool-error.js";
1
2
  // Budget and outcome accounting for writes dispatched by programs on writable endpoints.
2
3
  // Await each dispatched write once; never conceal an unknown outcome.
3
4
  import { CONFIG_DEFAULTS } from "./config-defaults.js";
4
5
  import { Effect } from "effect";
5
- import { jsonResult } from "./meta-tools.js";
6
6
  import { classifyWriteOutcome } from "./write-outcome.js";
7
7
  /** Writes one program may send (`execute.maxWrites`), unless configured. */
8
8
  export const DEFAULT_MAX_WRITES = CONFIG_DEFAULTS.execute.maxWrites;
@@ -125,7 +125,7 @@ export class ProgramWrites {
125
125
  // the result, as a direct call reports it: its code and handoff, never
126
126
  // retryable, with the write's outcome unknown.
127
127
  const handoff = this.handoff;
128
- return errorEnvelope({
128
+ return toolError({
129
129
  code: handoff?.code ?? "write_outcome_unknown",
130
130
  message: handoff
131
131
  ? "A write this program sent failed authorization after it left, so whether it took effect is unknown, and that is the result rather than what the program returned. It will not be sent again. Check its target before doing anything that depends on it, and give nextAction to the operator to recover the connector. Argument echoes may be partial or absent; use the original arguments if reconciliation requires another call."
@@ -153,7 +153,7 @@ export class ProgramWrites {
153
153
  function withWrites(result, writes) {
154
154
  const structured = result.structuredContent;
155
155
  const error = structured?.error;
156
- const annotated = errorEnvelope({
156
+ const annotated = toolError({
157
157
  ...(error !== null && typeof error === "object"
158
158
  ? error
159
159
  : {
@@ -171,8 +171,3 @@ function withWrites(result, writes) {
171
171
  }
172
172
  return annotated;
173
173
  }
174
- function errorEnvelope(error) {
175
- const result = jsonResult({ error });
176
- result.isError = true;
177
- return result;
178
- }
@@ -261,11 +261,4 @@ export function providerFetch(ctx, provider, auxiliary, send) {
261
261
  export function inLogicalCall(ctx) {
262
262
  return recordedService(ctx, LogicalLedger) !== undefined;
263
263
  }
264
- /**
265
- * Every resource request dispatched under `ctx`'s logical call, from any
266
- * ledger, nested ones included; undefined outside one.
267
- */
268
- export function logicalCallRequests(ctx) {
269
- return ctx ? recordedService(ctx, LogicalLedger)?.sent : undefined;
270
- }
271
264
  export { recordProviderResponse, tracksEveryRequest, tracksRequests, delegatesEachCall, providerBoundary, refusedStatus, vendorRefusal, authorizationFailure, unauthorizedAnswer, renewedAfterUnauthorized, refusalRequest, knownProviderFailure, isKnownProviderFailure, forgetProviderFailure, explainedUnauthorizedVerdict, explainsUnauthorized, settledWrite, isSettledWrite, } from "./provider-call-facts.js";
@@ -120,7 +120,7 @@ export interface ProviderGuideBlocks<O> {
120
120
  select(options: Readonly<O>): readonly string[];
121
121
  }
122
122
  /**
123
- * Facts the provider kit (decision 0010) declares for `providers:generate`:
123
+ * Facts the provider kit declares for `providers:generate`:
124
124
  * `drift.json`'s `generated.operations` and the guide's Conventions. Plain
125
125
  * data, never read at runtime. The kit's `defineApiProvider` sets them.
126
126
  */
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray } from "../json.js";
1
2
  // Google Workspace on the provider kit (decision 0010; C.7 step 1 under 0011).
2
3
  // Shared by every Workspace product; never its own export. `workspace.ts`
3
4
  // keeps shared options, connection parsing and subject resolution;
@@ -17,12 +18,6 @@ import { delegatedToken, forgetDelegatedToken } from "./service-account.js";
17
18
  import { subjectFor, workspaceConnection } from "./workspace.js";
18
19
  import { tagWorkspaceSubject } from "../../../workspace-subject.js";
19
20
  import { googleNamedOperation, requireGoogleIndex } from "./hatch.js";
20
- function asRecord(value) {
21
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
22
- }
23
- function asArray(value) {
24
- return Array.isArray(value) ? value : [];
25
- }
26
21
  function reasonsOf(tokens) {
27
22
  const error = asRecord(asRecord(tokens)["error"]);
28
23
  const reasons = new Set();
@@ -1,3 +1,4 @@
1
+ import { asRecord } from "../json.js";
1
2
  /**
2
3
  * A Google service account acting as itself or for Workspace users through
3
4
  * domain-wide delegation: the key, signed JWT-bearer assertions and short-lived
@@ -266,9 +267,6 @@ const tokenTransport = guardedFetch({
266
267
  // The assertion in the body is the authentication; there is no header.
267
268
  authenticate: () => ({}),
268
269
  });
269
- function asRecord(value) {
270
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
271
- }
272
270
  /** The token endpoint's OAuth `error` codes this module names back. */
273
271
  const TOKEN_ERROR_CODES = new Set([
274
272
  "invalid_request",
@@ -92,10 +92,10 @@ export declare const GOOGLE_WORKSPACE_OPTIONS: import("../../../config-schema.js
92
92
  };
93
93
  instructions: import("../../../config-schema.js").Field<unknown, unknown, true>;
94
94
  title: import("../../../config-schema.js").Field<unknown, unknown, true>;
95
+ baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
95
96
  purpose: import("../../../config-schema.js").Field<unknown, unknown, true>;
96
97
  maxResultBytes: import("../../../config-schema.js").Field<unknown, unknown, true>;
97
98
  readOnly: import("../../../config-schema.js").Field<unknown, unknown, true>;
98
- baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
99
99
  subject: import("../../../config-schema.js").Field<unknown, unknown, true>;
100
100
  };
101
101
  };
@@ -0,0 +1,3 @@
1
+ export function readOnlyNotice(readOnly) {
2
+ return readOnly ? ["This connection is read-only: every write is refused."] : [];
3
+ }
@@ -0,0 +1,12 @@
1
+ export function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ export function asRecord(value) {
5
+ return isRecord(value) ? value : {};
6
+ }
7
+ export function asArray(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+ export function compact(value) {
11
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
12
+ }
@@ -23,7 +23,7 @@ function rowText(packed, row) {
23
23
  return `[[${parameters.join(",")}],${body}]`;
24
24
  }
25
25
  /** Recreate the exact old JSON string, for semantic identity/cursor binding. */
26
- export function expandIndexDetails(packed) {
26
+ function expandIndexDetails(packed) {
27
27
  limit(packed.length);
28
28
  let length = packed.definitions.length + 13; // {"d":defs,"o":[]}
29
29
  limit(length);
@@ -1,12 +1,10 @@
1
+ import { isRecord } from "../json.js";
1
2
  // Shared REST call shapes, pinned-schema conversion and indexed vendor declarations.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../../errors.js";
4
5
  import { Refusal } from "../../../http/errors.js";
5
6
  import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
6
7
  import { vendor as declareVendor, get, head, post, put, patch, del, } from "../../../http/vendor.js";
7
- function isRecord(value) {
8
- return !!value && typeof value === "object" && !Array.isArray(value);
9
- }
10
8
  function invalid(message) {
11
9
  throw new ConnectorCallError("invalid_args", message);
12
10
  }
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "../json.js";
1
2
  import { canonicalJson } from "../../../canonical-json.js";
2
3
  import { parseIndexDetails } from "./compact-index.js";
3
4
  // The pinned operation index a REST connector validates against before it
@@ -20,9 +21,6 @@ function words(text) {
20
21
  .split(/[^a-z0-9]+/)
21
22
  .filter(Boolean);
22
23
  }
23
- function isRecord(value) {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
24
  function invalid(message, issues, repair) {
27
25
  throw new ConnectorCallError("invalid_args", message, {
28
26
  ...(issues ? { validation: { issues } } : {}),
@@ -1,4 +1,5 @@
1
- // Value safety for every REST vendor (decision 0005): no tool response,
1
+ import { isRecord } from "../json.js";
2
+ // Value safety for every REST vendor: no tool response,
2
3
  // success or error, returns a credential (API, service, or bypass tokens,
3
4
  // signing secrets, bearer URLs, private keys, temporary credentials, auth
4
5
  // headers, authorization verifiers) or a stored secret value (environment
@@ -34,7 +35,7 @@
34
35
  // Sent-secret redaction runs first, in the provider kit's parse pass, so a
35
36
  // rule that decides on a value can find the placeholder where the vendor
36
37
  // wrote what the rule matches. Every such decision input fails closed
37
- // (#703): an altered type discriminator applies every resource rule; an
38
+ //: an altered type discriminator applies every resource rule; an
38
39
  // altered label or type makes a secret record or a header rule; an altered
39
40
  // URL scheme or parameter name withholds the whole URL or its value; an
40
41
  // altered key withholds its value; a placeholder that may stand for a
@@ -204,10 +205,7 @@ export function credentialName(name) {
204
205
  const normalized = normalizedName(name);
205
206
  return containsCredential(normalized) || CREDENTIAL_SUFFIX.test(normalized);
206
207
  }
207
- function isRecord(value) {
208
- return typeof value === "object" && value !== null && !Array.isArray(value);
209
- }
210
- /** A rule's test over one decision input; an input redaction altered passes it (#703). */
208
+ /** A rule's test over one decision input; an input redaction altered passes it. */
211
209
  function decides(input, test) {
212
210
  return typeof input === "string" && (redactionAltered(input) || test(input));
213
211
  }
@@ -215,7 +213,7 @@ function decides(input, test) {
215
213
  const PLACEHOLDER_KEYS = new WeakSet();
216
214
  /**
217
215
  * Whether redaction altered one of a record's keys, so no key-based decision
218
- * about it can be read (#703). Parsed JSON keeps its keys; text the kit
216
+ * about it can be read. Parsed JSON keeps its keys; text the kit
219
217
  * redacted whole and a result hook parsed after it (NDJSON, log streams) can
220
218
  * carry the placeholder in one.
221
219
  */
@@ -226,7 +224,7 @@ function keysAltered(value) {
226
224
  const SCHEME_PREFIX = /^(?:[a-z][a-z0-9+.-]*(?::\/{0,2})?)?$/i;
227
225
  /**
228
226
  * Whether redaction altered the scheme a whole-string URL decision reads
229
- * (#703): the text is no longer URL-shaped, everything before its first
227
+ *: the text is no longer URL-shaped, everything before its first
230
228
  * placeholder could begin a URL, and more than placeholders follows it. A
231
229
  * URL a vendor sends holds no whitespace.
232
230
  */
@@ -434,7 +432,7 @@ function endsValue(text, at) {
434
432
  /**
435
433
  * Where a word at `start` holds a placeholder in place of a link's scheme
436
434
  * and host (`[redacted]/pkg?token=…`, `https:[redacted]…`), or `-1`:
437
- * redaction took the authority that makes text a link (#703). A Markdown
435
+ * redaction took the authority that makes text a link. A Markdown
438
436
  * label (`[redacted](…)`) is not one; its destination is read as a link.
439
437
  */
440
438
  function markerLed(text, start) {
@@ -524,7 +522,7 @@ function linkSpan(text, start, host, inline) {
524
522
  /**
525
523
  * Visit each destination once, respecting its CommonMark close and encoded
526
524
  * delimiters. Redaction (`failClosed`) also reads a link whose scheme and
527
- * host redaction took (#703); admission never does.
525
+ * host redaction took; admission never does.
528
526
  */
529
527
  function* linkSpans(text, failClosed = false) {
530
528
  const markers = failClosed && redactionAltered(text);
@@ -585,7 +583,7 @@ function credentialQuery(text) {
585
583
  const fragment = decoded.indexOf("#");
586
584
  if (fragment >= 0) {
587
585
  const words = decodeLinkText(decoded.slice(fragment + 1));
588
- // A fragment redaction altered cannot be read (#703).
586
+ // A fragment redaction altered cannot be read.
589
587
  if (/token|secret|key|code|auth/i.test(words) || alteredFragment(words))
590
588
  return true;
591
589
  }
@@ -597,7 +595,7 @@ function credentialQuery(text) {
597
595
  const equals = pair.indexOf("=");
598
596
  at = end + 1;
599
597
  // A component redaction altered with no readable name (its name, or its `=`, taken) may be a
600
- // credential parameter's value, however encoded (#703).
598
+ // credential parameter's value, however encoded.
601
599
  if (pair !== REDACTED && redactionAltered(equals < 0 ? pair : pair.slice(0, equals)))
602
600
  return true;
603
601
  if (equals < 0)
@@ -615,7 +613,7 @@ function withholdLinkQueries(text) {
615
613
  let out = "";
616
614
  let copied = 0;
617
615
  for (const { cut, open, end, start, host } of linkSpans(text, true)) {
618
- // A parameter name redaction altered cannot be read, so the whole link goes (#703).
616
+ // A parameter name redaction altered cannot be read, so the whole link goes.
619
617
  const whole = alteredLink(text.slice(host, end));
620
618
  if (!whole && (cut < 0 || !credentialQuery(text.slice(cut, end))))
621
619
  continue;
@@ -640,7 +638,7 @@ function withholdLinkQueries(text) {
640
638
  }
641
639
  return copied === 0 ? text : out + text.slice(copied);
642
640
  }
643
- /** Whether redaction altered a parameter name in a link's own path, query, or fragment (#703). */
641
+ /** Whether redaction altered a parameter name in a link's own path, query, or fragment. */
644
642
  function alteredLink(link) {
645
643
  return redactionAltered(link) && parameterNames(decodeLinkText(link)).some(redactionAltered);
646
644
  }
@@ -676,7 +674,7 @@ function withholdSigningParams(anchored) {
676
674
  }
677
675
  const NAME_CHAR = /[\w.%~;-]/;
678
676
  const NAME_ESCAPE = new RegExp(Array.from("abcdefghijklmnopqrstuvwxyz0123456789_.~-", signingName).join("|"), "iy");
679
- /** Where a parameter's value ends, reading placeholders and character references whole (#703). */
677
+ /** Where a parameter's value ends, reading placeholders and character references whole. */
680
678
  function valueEnd(text, at, inferred) {
681
679
  while (at < text.length) {
682
680
  const reference = Math.max(characterReferenceEnd(text, at, inferred), bareReferenceEnd(text, at, inferred));
@@ -693,7 +691,7 @@ function valueEnd(text, at, inferred) {
693
691
  }
694
692
  /**
695
693
  * The next placeholder that may hide a signing parameter, at or after `from`,
696
- * and its value boundary when a name is recognized (#703). Redaction may have taken a signing name, its
694
+ * and its value boundary when a name is recognized. Redaction may have taken a signing name, its
697
695
  * `=`, and the start of its value, so a placeholder followed by any of a
698
696
  * value's base64 or base64url characters (`+`, `/`, `=`, `-`, `_` included)
699
697
  * or `%` escapes goes with them, however short. References count when they
@@ -956,7 +954,7 @@ function labelsOf(value) {
956
954
  }
957
955
  /**
958
956
  * Whether a record's label names a credential, so its value or args are one.
959
- * A label redaction altered, or keys that may hide one, does too (#703).
957
+ * A label redaction altered, or keys that may hide one, does too.
960
958
  */
961
959
  function credentialLabelled(value) {
962
960
  if (!isRecord(value))
@@ -966,17 +964,17 @@ function credentialLabelled(value) {
966
964
  (isRecord(target) && keysAltered(target)) ||
967
965
  labelsOf(value).some((label) => decides(label, credentialName)));
968
966
  }
969
- /** A record typed as a secret (`secret_text`); a type redaction altered may have been one (#703). */
967
+ /** A record typed as a secret (`secret_text`); a type redaction altered may have been one. */
970
968
  function typedSecret(value) {
971
969
  return keysAltered(value) || decides(value["type"], (type) => /secret/i.test(type));
972
970
  }
973
- /** A list item typed as an environment variable; a type redaction altered may have been one (#703). */
971
+ /** A list item typed as an environment variable; a type redaction altered may have been one. */
974
972
  function envTyped(value) {
975
973
  if (!isRecord(value))
976
974
  return false;
977
975
  return keysAltered(value) || decides(String(value["type"]), (type) => /env/i.test(type));
978
976
  }
979
- /** A header, cookie, or query rule (`request.headers`, `header`, `cookie`, `query`, …); likewise (#703). */
977
+ /** A header, cookie, or query rule (`request.headers`, `header`, `cookie`, `query`, …); likewise. */
980
978
  function headerLike(value) {
981
979
  return isRecord(value) && (keysAltered(value) || decides(value["type"], (type) => /header|cookie|query/i.test(type)));
982
980
  }
@@ -989,7 +987,7 @@ function headerValues(value) {
989
987
  if (isRecord(value))
990
988
  return Object.fromEntries(Object.keys(value).map((name) => [name, REDACTED]));
991
989
  if (Array.isArray(value)) {
992
- // A pair whose keys redaction altered may hold its value under any of them (#703).
990
+ // A pair whose keys redaction altered may hold its value under any of them.
993
991
  return value.map((item) => !isRecord(item)
994
992
  ? item
995
993
  : keysAltered(item)
@@ -1120,7 +1118,7 @@ function scrubEnv(value, depth) {
1120
1118
  if (depth > 32)
1121
1119
  return REDACTED;
1122
1120
  if (Array.isArray(value)) {
1123
- // A `NAME=value` string, or one whose name and `=` redaction may have taken (#703).
1121
+ // A `NAME=value` string, or one whose name and `=` redaction may have taken.
1124
1122
  return value.map((item) => typeof item === "string" && (item.includes("=") || redactionAltered(item)) ? REDACTED : scrubEnv(item, depth + 1));
1125
1123
  }
1126
1124
  if (!isRecord(value))
@@ -1159,7 +1157,7 @@ function scrubValues(value, depth) {
1159
1157
  }
1160
1158
  return out;
1161
1159
  }
1162
- /** Every rule's paths and withheld fields: the rule for an object whose type redaction altered (#703). */
1160
+ /** Every rule's paths and withheld fields: the rule for an object whose type redaction altered. */
1163
1161
  const ANY_RULE = new WeakMap();
1164
1162
  function anyRule(resources) {
1165
1163
  let rule = ANY_RULE.get(resources);
@@ -1172,7 +1170,7 @@ function anyRule(resources) {
1172
1170
  }
1173
1171
  /**
1174
1172
  * What a record's type discriminator selects. A type redaction altered, or
1175
- * one an altered key may hide, selects every rule at once (#703).
1173
+ * one an altered key may hide, selects every rule at once.
1176
1174
  */
1177
1175
  function typed(record, key, rules, any) {
1178
1176
  const type = record[key];
@@ -1247,7 +1245,7 @@ function heuristic(value, exempt) {
1247
1245
  if (!exempt.reviewedUrls)
1248
1246
  return URL_SHAPE.test(current) ? sanitizeUrl(current) : current;
1249
1247
  // A placeholder's own brackets are not Markdown; a URL whose scheme redaction
1250
- // altered may still carry userinfo, so it is withheld whole (#703).
1248
+ // altered may still carry userinfo, so it is withheld whole.
1251
1249
  const altered = redactionAltered(current);
1252
1250
  if (TEXT.test(altered ? current.replaceAll(REDACTED, "") : current))
1253
1251
  return current;
@@ -1269,7 +1267,7 @@ function heuristic(value, exempt) {
1269
1267
  const normalized = normalizedName(key);
1270
1268
  const review = Object.hasOwn(exempt.fields, key) ? exempt.fields[key] : undefined;
1271
1269
  if (!placeholders && redactionAltered(key)) {
1272
- // A key redaction altered may have named a credential (#703).
1270
+ // A key redaction altered may have named a credential.
1273
1271
  out[key] = withheld(item);
1274
1272
  }
1275
1273
  else if (review?.verdict === "redact") {
@@ -1313,7 +1311,7 @@ function heuristic(value, exempt) {
1313
1311
  typeof item === "string" &&
1314
1312
  (URL_SHAPE.test(item) || alteredScheme(item))) {
1315
1313
  // An external destination can carry its secret in the path; relative paths stay.
1316
- // One whose scheme redaction altered does not parse, so it is withheld (#703).
1314
+ // One whose scheme redaction altered does not parse, so it is withheld.
1317
1315
  out[key] = originOnly(item);
1318
1316
  }
1319
1317
  else if (typeof item === "string" && verbatim?.has(key)) {
@@ -1367,7 +1365,7 @@ export function valueSafety(table, index) {
1367
1365
  }
1368
1366
  const resources = table.embeddedUrls.resources;
1369
1367
  if (resources) {
1370
- // A type redaction altered takes every resource's reviewed text paths (#703).
1368
+ // A type redaction altered takes every resource's reviewed text paths.
1371
1369
  const all = [...new Set(Object.values(resources.paths).flat())];
1372
1370
  const any = () => all;
1373
1371
  const scan = (value, depth) => {
@@ -1,3 +1,4 @@
1
+ import { asRecord } from "../_shared/json.js";
1
2
  // Breeze's reviewed GET-only vendor. HTTP methods do not establish safety: see tools.ts.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError, resendAdvice } from "../../errors.js";
@@ -9,12 +10,6 @@ import { openapi } from "./openapi.generated.js";
9
10
  export const DEFAULT_PAGE_SIZE = 100;
10
11
  export const MAX_PAGE_SIZE = 1000;
11
12
  export const BREEZE_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
12
- export function asRecord(value) {
13
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
14
- }
15
- export function asArray(value) {
16
- return Array.isArray(value) ? value : [];
17
- }
18
13
  // --- The read hatch's allowlist ----------------------------------------------
19
14
  /** Reviewed observational endpoints and their documented query names. GET alone establishes nothing. */
20
15
  export const READ_ENDPOINTS = new Map([
@@ -551,7 +546,7 @@ function shortText(value, secrets) {
551
546
  return value.trim() || undefined;
552
547
  if (value === undefined || value === null)
553
548
  return undefined;
554
- // The kit redacted the answer's values and kept its keys (#703); serialized, a key is text
549
+ // The kit redacted the answer's values and kept its keys; serialized, a key is text
555
550
  // the cut below could split, so the whole text is checked before it is cut.
556
551
  const text = secrets.redact(JSON.stringify(value));
557
552
  return text.length > 300 ? `${text.slice(0, 300)}…` : text;
@@ -1,8 +1,10 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
2
+ import { asRecord } from "../_shared/json.js";
1
3
  import { skill } from "./skill.generated.js";
2
4
  import { keys, optionsOf } from "../../config-schema.js";
3
5
  import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
4
6
  import { defineApiProvider } from "../../http/provider.js";
5
- import { accountSummary, admit, asRecord, authenticate, BreezeApi, breezeSafety, BREEZE_MAX_RESPONSE_BYTES, BREEZE_INDEX, BREEZE_VERDICTS, BREEZE_NAMED, breezePeoplePage, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, errors, result, } from "./api.js";
7
+ import { accountSummary, admit, authenticate, BreezeApi, breezeSafety, BREEZE_MAX_RESPONSE_BYTES, BREEZE_INDEX, BREEZE_VERDICTS, BREEZE_NAMED, breezePeoplePage, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, errors, result, } from "./api.js";
6
8
  import { breezeTools } from "./tools.js";
7
9
  export const BREEZE_HOST_SUFFIX = ".breezechms.com";
8
10
  const SUBDOMAIN_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -68,7 +70,7 @@ export const breeze = defineApiProvider({
68
70
  `Church database: ${normalizeSubdomain(options.subdomain)}${BREEZE_HOST_SUFFIX}.`,
69
71
  `Church purpose: ${options.purpose}`,
70
72
  "Calendar feed addresses and embed keys are always removed; profile details follow the requested vendor detail level.",
71
- ...(options.readOnly ? ["This connection is read-only: every write is refused."] : []),
73
+ ...readOnlyNotice(options.readOnly),
72
74
  ...(options.defaultPageSize !== undefined
73
75
  ? [`Paged lists return ${options.defaultPageSize} items by default on this connection.`]
74
76
  : []),
@@ -1,3 +1,5 @@
1
+ import { asArray, asRecord } from "../_shared/json.js";
2
+ import { compact } from "../_shared/json.js";
1
3
  // Breeze's curated surface: projections and profile encoders earn named tools; the standard hatch supplies coverage.
2
4
  import { Effect, Schema } from "effect";
3
5
  import { ConnectorCallError } from "../../errors.js";
@@ -6,12 +8,9 @@ import { Integer, isMinChars, isMaxChars } from "../../http/json-schema.js";
6
8
  import { RequestSecrets, Classification } from "../../http/services.js";
7
9
  import { fieldsInput, fieldsProblem, selectFields } from "../../http/fields.js";
8
10
  import { read, list, write as writeTool } from "../../http/tools.js";
9
- import { asArray, asRecord, READ_ENDPOINTS } from "./api.js";
11
+ import { READ_ENDPOINTS } from "./api.js";
10
12
  const DEFAULT_EVENT_LIMIT = 100;
11
13
  const ObjectValue = Schema.Record(Schema.String, Schema.Unknown);
12
- function compact(value) {
13
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
14
- }
15
14
  function timestamp(value) {
16
15
  if (value === undefined)
17
16
  return undefined;
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray } from "../_shared/json.js";
1
2
  import { CCB_MEDIA_TYPE, CCB_ENVIRONMENTS, CCB_READ_SCOPES, CCB_WRITE_SCOPES, CCB_SENSITIVE_FIELDS, } from "./lib/constants.js";
2
3
  // Pushpay ChMS v2 JSON contracts, OAuth, status meanings, admission and privacy.
3
4
  // The legacy per-church XML API is outside this provider's scope.
@@ -56,12 +57,6 @@ export const CCB_ADMISSION = {
56
57
  },
57
58
  ],
58
59
  };
59
- function asRecord(value) {
60
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
61
- }
62
- function asArray(value) {
63
- return Array.isArray(value) ? value : [];
64
- }
65
60
  // ---------------------------------------------------------------------------
66
61
  // Admission partitions
67
62
  // ---------------------------------------------------------------------------
@@ -1,3 +1,4 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
1
2
  import { CCB_ENVIRONMENTS, CCB_MEDIA_TYPE } from "./lib/constants.js";
2
3
  import { skill } from "./skill.generated.js";
3
4
  import { keys, optionsOf } from "../../config-schema.js";
@@ -70,7 +71,7 @@ export const ccb = defineApiProvider({
70
71
  ...(!writable(options)
71
72
  ? ["Configured scopes permit only reads: ccb_api_write is omitted and writes are refused."]
72
73
  : []),
73
- ...(options.readOnly ? ["This connection is read-only: every write is refused."] : []),
74
+ ...readOnlyNotice(options.readOnly),
74
75
  ...(options.defaultPerPage !== undefined
75
76
  ? [`Named lists default to ${options.defaultPerPage} rows on this connection.`]
76
77
  : []),
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray } from "../_shared/json.js";
1
2
  // CCB v2 curated projections, bound numbered pages and reviewed POST data search.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../errors.js";
@@ -7,12 +8,6 @@ import { Refusal } from "../../http/errors.js";
7
8
  import { Integer, isMinChars, isMaxChars } from "../../http/json-schema.js";
8
9
  import { read, list } from "../../http/tools.js";
9
10
  import { callCcb, DEFAULT_PER_PAGE, SEARCH_DOMAINS, CCB_INDEX, ccbPageInfo, } from "./api.js";
10
- function asRecord(value) {
11
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
12
- }
13
- function asArray(value) {
14
- return Array.isArray(value) ? value : [];
15
- }
16
11
  function compact(value) {
17
12
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined && entry !== null));
18
13
  }
@@ -1,3 +1,4 @@
1
+ import { decodeBase64 } from "../../bytes.js";
1
2
  // Cloudflare's pinned REST contracts, confinement, framing and failure semantics.
2
3
  import { responseMedia } from "../_shared/rest/response-media.js";
3
4
  import { EXCLUSIVE_PATH_IDS } from "./lib/path-bounds.js";
@@ -396,8 +397,7 @@ export class RawUpload {
396
397
  }
397
398
  export function bytesFromBase64(value, field) {
398
399
  try {
399
- const binary = atob(value);
400
- return Uint8Array.from(binary, (character) => character.charCodeAt(0));
400
+ return decodeBase64(value);
401
401
  }
402
402
  catch {
403
403
  return invalid(`${field} must be valid base64.`);
@@ -1,3 +1,4 @@
1
+ import { readOnlyNotice } from "../_shared/guide.js";
1
2
  import { skill } from "./skill.generated.js";
2
3
  import { keys, optionsOf, variants } from "../../config-schema.js";
3
4
  import { CALL_ADMISSION } from "../../provider.js";
@@ -90,7 +91,7 @@ function usageGuide(options) {
90
91
  : ["No additional deployment pin: the credential's vendor permissions define its reachable estate."]),
91
92
  ...(accountId ? [`Default account id: ${accountId}.`] : []),
92
93
  ...(zoneId ? [`Default zone id: ${zoneId}.`] : []),
93
- ...(options.readOnly ? ["This connection is read-only: every write is refused."] : []),
94
+ ...readOnlyNotice(options.readOnly),
94
95
  ],
95
96
  required: true,
96
97
  };
@@ -1,4 +1,4 @@
1
- // Cloudflare's reviewed value-safety table (#801), on the shared mechanism in
1
+ // Cloudflare's reviewed value-safety table, on the shared mechanism in
2
2
  // `../_shared/rest/value-safety.ts`: every operation the detector flags in
3
3
  // the pinned spec (`value-safety.candidates.json`) has one verdict here, and
4
4
  // `value-safety.node.test.ts` runs the shared harness over it.
@@ -16,7 +16,7 @@
16
16
  // Paths are dot paths into the data a tool returns (Cloudflare's `result`,
17
17
  // or the whole body when it is not an envelope); lists are traversed
18
18
  // implicitly and `*` matches every key of an object.
19
- // Embedded URL verdict (#812): reviewed paths + global signing parameters.
19
+ // Embedded URL verdict: reviewed paths + global signing parameters.
20
20
  // Evidence: digest-verified schema revision 843eae2fbf4fe774ef9c91021dd34017ac9c970a:
21
21
  // builds_BuildLogsResponse.lines (timestamp/log-message tuples),
22
22
  // pages_deployment_log.data.line and workers-observability_query_results
@@ -869,7 +869,7 @@ const OPERATIONS = {
869
869
  key,
870
870
  safe(DESCRIBED_ONLY, "geo_restrictions", "policy_restrictions", "certificates.geo_restrictions"),
871
871
  ])),
872
- // Round 3 (#801): candidates the consolidated detector surfaced (the union
872
+ // Round 3: candidates the consolidated detector surfaced (the union
873
873
  // of the two former detectors, with transfer, claim, invite, key, signing,
874
874
  // and webhook families).
875
875
  [`GET ${A}/abuse-reports/submitted/{report_id}`]: safe("An abuse report; the authorization statement is the reporter's text."),
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray, compact } from "../_shared/json.js";
1
2
  // Google Docs named projections and bounded writes over the provider kit.
2
3
  import { Effect, Option } from "effect";
3
4
  import { canonicalJson } from "../../canonical-json.js";
@@ -28,15 +29,6 @@ const DEFAULT_MAX_CHARS = 20_000;
28
29
  const ELEMENT_PAGE_BYTES = 64 * 1024;
29
30
  /** Characters of each index row's text preview. */
30
31
  const PREVIEW_CHARS = 80;
31
- function asRecord(value) {
32
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
33
- }
34
- function asArray(value) {
35
- return Array.isArray(value) ? value : [];
36
- }
37
- function compact(value) {
38
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
39
- }
40
32
  function text(value) {
41
33
  return typeof value === "string" && value !== "" ? value : undefined;
42
34
  }
@@ -368,7 +360,7 @@ function batchPath(documentId) {
368
360
  function revisionAfter(reply, dropped) {
369
361
  return keptId(asRecord(reply["writeControl"])["requiredRevisionId"], "revisionId", "revisionId", dropped);
370
362
  }
371
- // No cause: what it would carry is a transport, stream, or parser error that can quote the reply (#695).
363
+ // No cause: what it would carry is a transport, stream, or parser error that can quote the reply.
372
364
  function uncertain(message, ctx) {
373
365
  return providerCallFailure(ctx, { title: "Google Docs" }, new ConnectorCallError("connector_call_failed", message, { retryable: false }), {
374
366
  dispatched: true,