@zackbart/connecta 0.28.1 → 0.30.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 (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -1,6 +1,11 @@
1
1
  import type { ActivityModule } from "./module-contracts.js";
2
- import type { CatalogDriftCounts, Logger } from "./types.js";
3
- export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
2
+ import { type ClassificationCode } from "./errors.js";
3
+ import type { Logger } from "./types.js";
4
+ /** One policy for the record builder, SQL write/read boundaries, and UI. */
5
+ export declare function activityClientFact(value: unknown, field: "name" | "version"): string | undefined;
6
+ /** Stored package versions use a bounded release-version grammar, including prereleases. */
7
+ export declare function activityPackageVersion(value: unknown): string | undefined;
8
+ export type ActivityCallSource = "catalog_refresh" | "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
4
9
  export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
5
10
  /** Read-only history: how much one `resume_execution` approved (#672). */
6
11
  export type ActivityApproval = "call" | "tool";
@@ -28,11 +33,37 @@ export interface ActivityActor {
28
33
  * by construction. Deployments that need a safe human summary can add a
29
34
  * separate, explicit connector-level feature later.
30
35
  */
36
+ export interface ActivityCatalogChange {
37
+ kind: "catalog_changed";
38
+ addedTools: number;
39
+ removedTools: number;
40
+ changedTools: number;
41
+ }
42
+ /** Integer metadata is checked at every write and read boundary. */
43
+ export declare function activityCount(value: unknown): number | undefined;
44
+ export declare function activityBehaviorFacts(event: {
45
+ classification?: unknown;
46
+ resultBytes?: unknown;
47
+ kind?: unknown;
48
+ drift?: unknown;
49
+ pool?: unknown;
50
+ actorBasis?: unknown;
51
+ }): Pick<ToolCallActivityEvent, "classification" | "resultBytes" | "kind" | "drift" | "pool" | "actorBasis">;
31
52
  export interface ToolCallActivityEvent {
32
53
  schemaVersion: 1;
33
54
  id: string;
34
55
  occurredAt: string;
35
56
  requestId: string;
57
+ /** Absent on legacy tool rows. Catalog changes use the same paging envelope. */
58
+ kind?: "catalog_drift";
59
+ drift?: ActivityCatalogChange;
60
+ /** The final registry verdict captured when resolution succeeded. */
61
+ classification?: "read" | "write";
62
+ /** UTF-8 bytes of the downstream value before result paging or truncation. */
63
+ resultBytes?: number;
64
+ pool?: string;
65
+ /** Personal ownership is provable only for rows recorded from the admitted principal. */
66
+ actorBasis?: "principal";
36
67
  actor: ActivityActor;
37
68
  connectorId: string;
38
69
  toolName: string;
@@ -55,28 +86,13 @@ export interface ToolCallActivityEvent {
55
86
  * here — the approved arguments were never recorded.
56
87
  */
57
88
  approval?: ActivityApproval;
89
+ /** Real build version. Absent only in history written before telemetry existed. */
90
+ packageVersion?: string;
58
91
  serverName: string;
59
92
  serverVersion: string;
60
- deploymentId?: string;
61
- }
62
- /**
63
- * A downstream catalog moved away from the manifest a release reviewed.
64
- *
65
- * Four integers and an id: the type has nowhere to put a tool name, a schema,
66
- * an argument, a result, or a downstream error string, which is the same
67
- * construction guarantee `ToolCallActivityEvent` makes. Which tool drifted is
68
- * deliberately absent — the runtime reports that something did, and the
69
- * maintainer-run check with a live catalog in front of it names names
70
- * ([#343](https://github.com/zackbart/connecta/issues/343),
71
- * [#351](https://github.com/zackbart/connecta/issues/351)).
72
- */
73
- export interface CatalogDriftActivityEvent extends CatalogDriftCounts {
74
- schemaVersion: 1;
75
- id: string;
76
- occurredAt: string;
77
- connectorId: string;
78
- serverName: string;
79
- serverVersion: string;
93
+ /** Self-declared client identity checked by activityClientFact; invalid facts are absent. */
94
+ clientName?: string;
95
+ clientVersion?: string;
80
96
  deploymentId?: string;
81
97
  }
82
98
  export interface ActivityPage {
@@ -97,13 +113,6 @@ export interface ActivityReadPage {
97
113
  /** Write-only deployments can implement only this small, vendor-neutral seam. */
98
114
  export interface ActivitySink {
99
115
  record(event: ToolCallActivityEvent): void | Promise<void>;
100
- /**
101
- * Optional catalog-drift channel. Optional rather than a widened `record`,
102
- * because a store written before drift existed already decided what a row
103
- * looks like: a sink that does not implement this simply never hears about
104
- * drift, and the operator still reads the same counts from connector status.
105
- */
106
- recordCatalogDrift?(event: CatalogDriftActivityEvent): void | Promise<void>;
107
116
  }
108
117
  /** Optional read side used by Connecta's authenticated Activity UI. */
109
118
  export interface ActivityReader {
@@ -114,6 +123,14 @@ export interface ActivityReader {
114
123
  }
115
124
  export interface ActivityStore extends ActivitySink {
116
125
  list?: ActivityReader["list"];
126
+ /**
127
+ * The adapter and its retention, for `Connecta.describeConfig()`. Kinds
128
+ * other than the shipped `"d1"` and `"sqlite"` are described as `"custom"`.
129
+ */
130
+ describe?(): {
131
+ kind: string;
132
+ retentionDays?: number;
133
+ };
117
134
  }
118
135
  /** Reader implementations throw this for an opaque cursor they cannot decode. */
119
136
  export declare class InvalidActivityCursorError extends Error {
@@ -122,17 +139,21 @@ export declare class InvalidActivityCursorError extends Error {
122
139
  }
123
140
  export type ActivityReadGate = (actor: ActivityActor) => boolean | Promise<boolean>;
124
141
  /**
125
- * Deployment-scoped destination for drift observations. Not request-scoped:
126
- * a drifting catalog is something the deployment saw, not something a caller
127
- * did, so it carries no actor and no request id to attribute it to one.
142
+ * Default destination for catalog changes. Request attribution overrides it
143
+ * when a publication belongs to an admitted caller.
128
144
  */
129
145
  export interface CatalogDriftActivityContext {
130
- recordDrift?: typeof recordCatalogDriftActivity;
146
+ recordChange?: typeof recordCatalogChangeActivity;
147
+ defer?: (promise: Promise<unknown>) => void;
131
148
  sink: ActivitySink;
132
149
  serverInfo: {
133
150
  name: string;
134
151
  version: string;
135
152
  };
153
+ clientInfo?: {
154
+ name: string;
155
+ version: string;
156
+ };
136
157
  deploymentId?: string;
137
158
  logger: Logger;
138
159
  }
@@ -141,34 +162,47 @@ export interface ActivityRequestContext {
141
162
  recordTool?: typeof recordToolActivity | undefined;
142
163
  sink: ActivitySink;
143
164
  actor: ActivityActor;
165
+ /** Personal rows identify their admitted owner in the existing actor fields. */
166
+ principalActor?: ActivityActor;
144
167
  requestId: string;
168
+ pool?: string;
145
169
  serverInfo: {
146
170
  name: string;
147
171
  version: string;
148
172
  };
173
+ clientInfo?: {
174
+ name: string;
175
+ version: string;
176
+ };
149
177
  deploymentId?: string;
150
178
  defer?: (promise: Promise<unknown>) => void;
151
179
  logger: Logger;
152
180
  }
153
- export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction">;
181
+ export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "friction" | "classification" | "resultBytes" | "kind" | "drift"> & {
182
+ /**
183
+ * A code connecta assigns. Rows written by older deployments may hold
184
+ * others, so the stored event keeps `string`; a new row never does.
185
+ */
186
+ errorCode?: ClassificationCode;
187
+ personal?: boolean;
188
+ privateCatalog?: boolean;
189
+ };
154
190
  /**
155
191
  * Best-effort by design: activity storage can never change a tool result.
156
192
  * Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
157
193
  * Engine complete inline; Node promises remain detached from the response.
158
194
  */
159
195
  export declare function recordToolActivity(context: ActivityRequestContext | undefined, input: ActivityEventInput): void;
160
- /**
161
- * Record one catalog-drift observation, best-effort like every other activity
162
- * write: a store that is down, or a sink that never implemented the channel,
163
- * can never change what a refresh returned to the caller who paid for it.
164
- */
165
- export declare function recordCatalogDriftActivity(context: CatalogDriftActivityContext | undefined, input: {
196
+ /** A discrete catalog change, with no catalog names, descriptions or schemas. */
197
+ export declare function recordCatalogChangeActivity(context: CatalogDriftActivityContext | undefined, input: {
166
198
  connectorId: string;
167
- } & CatalogDriftCounts): void;
199
+ drift: ActivityCatalogChange;
200
+ personal?: boolean;
201
+ privateCatalog?: boolean;
202
+ }, request?: ActivityRequestContext): void;
168
203
  export interface ActivityHistoryOptions {
169
204
  store: ActivityStore;
170
205
  deploymentId?: string;
171
206
  readGate?: ActivityReadGate;
172
207
  }
173
- /** Attach payload-free history without changing tool results on store failure. */
174
208
  export declare function activityHistory(options: ActivityHistoryOptions): ActivityModule;
package/dist/activity.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { CONNECTA_VERSION } from "./version.js";
2
+ import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
1
3
  import { routeActivity } from "./routes/activity.js";
2
- import { boundedEchoText } from "./errors.js";
4
+ import { boundedEchoText, classificationCode } from "./errors.js";
5
+ import { failureRecord, logFailure } from "./operator-record.js";
3
6
  /**
4
7
  * How long an identity field may be before the store stops believing it.
5
8
  *
@@ -15,10 +18,72 @@ import { boundedEchoText } from "./errors.js";
15
18
  * 40,000 would. The `…` marker keeps a clamped value from reading as a real one.
16
19
  */
17
20
  const MAX_ACTIVITY_NAME_BYTES = 128;
21
+ /**
22
+ * Client facts use bounded ASCII grammars, never truncation or escaping.
23
+ * Names are 1 to 64 characters: alphanumeric or @ first (scoped package names
24
+ * such as `@modelcontextprotocol/inspector`), then alphanumerics, space, dot,
25
+ * underscore, @, /, + or -. Versions are 1 to 32 characters: alphanumeric
26
+ * first, then alphanumerics, dot, underscore, + or -. Prototype-key names are
27
+ * withheld too. The end assertion rejects even a final CR/LF, unlike `$`.
28
+ */
29
+ const CLIENT_FACT_GRAMMARS = {
30
+ name: /^[A-Za-z0-9@][A-Za-z0-9 ._@/+-]{0,63}(?![\s\S])/,
31
+ version: /^[A-Za-z0-9][A-Za-z0-9._+-]{0,31}(?![\s\S])/,
32
+ };
33
+ const RESERVED_CLIENT_FACTS = new Set(["__proto__", "constructor", "prototype"]);
34
+ /** One policy for the record builder, SQL write/read boundaries, and UI. */
35
+ export function activityClientFact(value, field) {
36
+ return typeof value === "string" && !RESERVED_CLIENT_FACTS.has(value) && CLIENT_FACT_GRAMMARS[field].test(value)
37
+ ? value
38
+ : undefined;
39
+ }
40
+ /** Stored package versions use a bounded release-version grammar, including prereleases. */
41
+ export function activityPackageVersion(value) {
42
+ return typeof value === "string" &&
43
+ value.length <= 128 &&
44
+ /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?(?![\s\S])/.test(value)
45
+ ? value
46
+ : undefined;
47
+ }
18
48
  /** Two names and the dot between them. */
19
49
  const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
20
50
  import { agentFrictionForCode } from "./activity-friction.js";
21
51
  export { agentFrictionForCode } from "./activity-friction.js";
52
+ /** Integer metadata is checked at every write and read boundary. */
53
+ export function activityCount(value) {
54
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
55
+ }
56
+ export function activityBehaviorFacts(event) {
57
+ const classification = event.classification === "read" || event.classification === "write" ? event.classification : undefined;
58
+ const resultBytes = activityCount(event.resultBytes);
59
+ const change = event.drift;
60
+ const drift = event.kind === "catalog_drift" &&
61
+ change?.kind === "catalog_changed" &&
62
+ activityCount(change.addedTools) !== undefined &&
63
+ activityCount(change.removedTools) !== undefined &&
64
+ activityCount(change.changedTools) !== undefined
65
+ ? {
66
+ kind: "catalog_changed",
67
+ addedTools: change.addedTools,
68
+ removedTools: change.removedTools,
69
+ changedTools: change.changedTools,
70
+ }
71
+ : undefined;
72
+ // Pool names come from matched deployment config, whose grammar has no size
73
+ // ceiling. Invalid stored scope stays withheld rather than becoming root scope.
74
+ const pool = event.pool === undefined || event.pool === null
75
+ ? undefined
76
+ : typeof event.pool === "string" && /^[a-z0-9_-]+(?![\s\S])/.test(event.pool)
77
+ ? event.pool
78
+ : "<withheld>";
79
+ return {
80
+ ...(event.actorBasis === "principal" ? { actorBasis: "principal" } : {}),
81
+ ...(classification !== undefined ? { classification } : {}),
82
+ ...(resultBytes !== undefined ? { resultBytes } : {}),
83
+ ...(drift ? { kind: "catalog_drift", drift } : {}),
84
+ ...(pool !== undefined ? { pool } : {}),
85
+ };
86
+ }
22
87
  /** Reader implementations throw this for an opaque cursor they cannot decode. */
23
88
  export class InvalidActivityCursorError extends Error {
24
89
  name = "InvalidActivityCursorError";
@@ -36,13 +101,24 @@ export function recordToolActivity(context, input) {
36
101
  return;
37
102
  // A caller-supplied class wins because it knows something the code table
38
103
  // cannot: friction that belongs to a call which did not fail.
39
- const friction = input.friction ?? agentFrictionForCode(input.errorCode);
104
+ // Checked here as well as by type: a handler can put any string in a
105
+ // `ConnectorCallError`'s code, and a downstream's own code is not recorded.
106
+ const errorCode = classificationCode(input.errorCode);
107
+ const friction = input.friction ?? agentFrictionForCode(errorCode);
108
+ // Re-check client grammars at the record boundary; never spread the envelope.
109
+ const clientName = activityClientFact(context.clientInfo?.name, "name");
110
+ const clientVersion = activityClientFact(context.clientInfo?.version, "version");
40
111
  const event = {
41
112
  schemaVersion: 1,
42
113
  id: crypto.randomUUID(),
43
114
  occurredAt: new Date().toISOString(),
44
115
  requestId: context.requestId,
45
- actor: context.actor,
116
+ actor: input.personal || input.privateCatalog ? (context.principalActor ?? { kind: context.actor.kind }) : context.actor,
117
+ ...activityBehaviorFacts({
118
+ ...input,
119
+ pool: context.pool,
120
+ actorBasis: input.privateCatalog || (input.personal && context.principalActor) ? "principal" : undefined,
121
+ }),
46
122
  connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
47
123
  toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
48
124
  address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
@@ -50,13 +126,14 @@ export function recordToolActivity(context, input) {
50
126
  outcome: input.outcome,
51
127
  durationMs: Math.max(0, Math.trunc(input.durationMs)),
52
128
  attempts: Math.max(1, Math.trunc(input.attempts)),
53
- ...(input.errorCode ? { errorCode: input.errorCode } : {}),
129
+ ...(errorCode ? { errorCode } : {}),
54
130
  ...(friction ? { friction } : {}),
131
+ packageVersion: CONNECTA_VERSION,
55
132
  serverName: context.serverInfo.name,
56
133
  serverVersion: context.serverInfo.version,
57
- ...(context.deploymentId
58
- ? { deploymentId: context.deploymentId }
59
- : {}),
134
+ ...(clientName !== undefined ? { clientName } : {}),
135
+ ...(clientVersion !== undefined ? { clientVersion } : {}),
136
+ ...(context.deploymentId ? { deploymentId: context.deploymentId } : {}),
60
137
  };
61
138
  try {
62
139
  const result = context.sink.record(event);
@@ -64,51 +141,42 @@ export function recordToolActivity(context, input) {
64
141
  return;
65
142
  }
66
143
  const pending = Promise.resolve(result).catch((error) => {
67
- context.logger.warn("[connecta] activity record failed", error);
144
+ logFailure(context.logger, "activity record failed", failureRecord({}, error));
68
145
  });
69
146
  if (context.defer)
70
147
  context.defer(pending);
71
148
  }
72
149
  catch (error) {
73
- context.logger.warn("[connecta] activity record failed", error);
150
+ logFailure(context.logger, "activity record failed", failureRecord({}, error));
74
151
  }
75
152
  }
76
- /**
77
- * Record one catalog-drift observation, best-effort like every other activity
78
- * write: a store that is down, or a sink that never implemented the channel,
79
- * can never change what a refresh returned to the caller who paid for it.
80
- */
81
- export function recordCatalogDriftActivity(context, input) {
82
- if (!context?.sink.recordCatalogDrift)
153
+ /** A discrete catalog change, with no catalog names, descriptions or schemas. */
154
+ export function recordCatalogChangeActivity(context, input, request) {
155
+ if (!context)
83
156
  return;
84
- const event = {
85
- schemaVersion: 1,
86
- id: crypto.randomUUID(),
87
- occurredAt: new Date().toISOString(),
88
- connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
89
- unclassifiedTools: input.unclassifiedTools,
90
- unservedTools: input.unservedTools,
91
- annotationConflicts: input.annotationConflicts,
92
- schemaChanges: input.schemaChanges,
93
- serverName: context.serverInfo.name,
94
- serverVersion: context.serverInfo.version,
95
- ...(context.deploymentId ? { deploymentId: context.deploymentId } : {}),
96
- };
97
- try {
98
- const result = context.sink.recordCatalogDrift(event);
99
- if (!result || typeof result.then !== "function") {
100
- return;
101
- }
102
- void Promise.resolve(result).catch((error) => {
103
- context.logger.warn("[connecta] catalog drift record failed", error);
104
- });
105
- }
106
- catch (error) {
107
- context.logger.warn("[connecta] catalog drift record failed", error);
108
- }
157
+ recordToolActivity(request ?? {
158
+ ...context,
159
+ actor: { kind: "system" },
160
+ requestId: crypto.randomUUID(),
161
+ }, {
162
+ connectorId: input.connectorId,
163
+ toolName: "<catalog>",
164
+ address: `${input.connectorId}.<catalog>`,
165
+ source: "catalog_refresh",
166
+ outcome: "success",
167
+ durationMs: 0,
168
+ attempts: 1,
169
+ kind: "catalog_drift",
170
+ drift: input.drift,
171
+ ...(input.personal ? { personal: true } : {}),
172
+ ...(input.privateCatalog ? { privateCatalog: true } : {}),
173
+ });
109
174
  }
110
175
  /** Attach payload-free history without changing tool results on store failure. */
176
+ /** The closed options activityHistory() accepts; see `assertKnownOptions`. */
177
+ const ACTIVITY_HISTORY_OPTIONS = optionsOf()(keys("store", "deploymentId", "readGate"));
111
178
  export function activityHistory(options) {
179
+ options = assertKnownOptions(options, "activityHistory()", ACTIVITY_HISTORY_OPTIONS);
112
180
  if (!options || typeof options.store?.record !== "function") {
113
181
  throw new Error("activityHistory.store must implement record(event)");
114
182
  }
@@ -116,6 +184,6 @@ export function activityHistory(options) {
116
184
  ...options,
117
185
  handle: routeActivity,
118
186
  recordTool: recordToolActivity,
119
- recordDrift: recordCatalogDriftActivity,
187
+ recordChange: recordCatalogChangeActivity,
120
188
  };
121
189
  }
@@ -0,0 +1,230 @@
1
+ const PUBLIC = {};
2
+ const OMIT = { omit: true };
3
+ const OMITTED = Symbol("omitted argument");
4
+ const MAX_DEPTH = 32;
5
+ const MAX_SCHEMAS = 2048;
6
+ function object(value) {
7
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function sensitive(plan) {
10
+ return Object.keys(plan).length > 0;
11
+ }
12
+ function equal(left, right) {
13
+ return JSON.stringify(left) === JSON.stringify(right);
14
+ }
15
+ function item(plan, index) {
16
+ return plan.prefix?.[index] ?? plan.items ?? PUBLIC;
17
+ }
18
+ /** Combine restrictions from references, siblings, and allOf without losing array indices. */
19
+ function merge(left, right) {
20
+ if (left.omit || right.omit)
21
+ return OMIT;
22
+ const names = new Set([...Object.keys(left.properties ?? {}), ...Object.keys(right.properties ?? {})]);
23
+ const properties = Object.fromEntries([...names]
24
+ .sort()
25
+ .map((name) => [
26
+ name,
27
+ merge(Object.hasOwn(left.properties ?? {}, name) ? left.properties[name] : PUBLIC, Object.hasOwn(right.properties ?? {}, name) ? right.properties[name] : PUBLIC),
28
+ ]));
29
+ const items = mergeItems(left.items ?? PUBLIC, right.items ?? PUBLIC);
30
+ const prefix = Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => mergeItems(item(left, index), item(right, index)));
31
+ // Equivalent plans compare equally even when one schema spells out public prefixes.
32
+ while (prefix.length && equal(prefix.at(-1), items))
33
+ prefix.pop();
34
+ return {
35
+ ...(names.size ? { properties } : {}),
36
+ ...(prefix.length ? { prefix } : {}),
37
+ ...(sensitive(items) ? { items } : {}),
38
+ };
39
+ }
40
+ function mergeItems(left, right) {
41
+ if (!sensitive(left))
42
+ return right;
43
+ if (!sensitive(right))
44
+ return left;
45
+ return merge(left, right);
46
+ }
47
+ /** Compile a bounded plan. Unknown sensitivity refuses the entire echo, never just a branch. */
48
+ function compile(root, unionAlternatives = false) {
49
+ let visited = 0;
50
+ const active = new Set();
51
+ const unresolved = () => {
52
+ throw new Error("Unresolved argument sensitivity");
53
+ };
54
+ function reference(ref) {
55
+ if (typeof ref !== "string" || !ref.startsWith("#"))
56
+ return unresolved();
57
+ const pointer = decodeURIComponent(ref.slice(1));
58
+ if (pointer === "")
59
+ return root;
60
+ if (!pointer.startsWith("/"))
61
+ return unresolved();
62
+ let target = root;
63
+ for (const encoded of pointer.slice(1).split("/")) {
64
+ if (/~[^01]|~$/.test(encoded))
65
+ return unresolved();
66
+ const key = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
67
+ if (target === null || typeof target !== "object" || !Object.hasOwn(target, key))
68
+ return unresolved();
69
+ target = target[key];
70
+ }
71
+ return target;
72
+ }
73
+ function walk(schema, depth) {
74
+ if (++visited > MAX_SCHEMAS || depth > MAX_DEPTH)
75
+ return unresolved();
76
+ if (typeof schema === "boolean")
77
+ return PUBLIC;
78
+ if (!object(schema) || active.has(schema))
79
+ return unresolved();
80
+ if (schema["writeOnly"] !== undefined && typeof schema["writeOnly"] !== "boolean")
81
+ return unresolved();
82
+ if (schema["writeOnly"] === true)
83
+ return OMIT;
84
+ if ((schema !== root && schema["$id"] !== undefined) ||
85
+ schema["$dynamicRef"] !== undefined ||
86
+ schema["$recursiveRef"] !== undefined)
87
+ return unresolved();
88
+ active.add(schema);
89
+ try {
90
+ let plan = PUBLIC;
91
+ const child = (value) => walk(value, depth + 1);
92
+ if (schema["$ref"] !== undefined)
93
+ plan = merge(plan, child(reference(schema["$ref"])));
94
+ if (schema["properties"] !== undefined) {
95
+ if (!object(schema["properties"]))
96
+ return unresolved();
97
+ const properties = Object.entries(schema["properties"])
98
+ .map(([name, value]) => [name, child(value)])
99
+ .filter(([, value]) => sensitive(value))
100
+ .sort(([a], [b]) => a.localeCompare(b));
101
+ plan = merge(plan, properties.length ? { properties: Object.fromEntries(properties) } : PUBLIC);
102
+ }
103
+ const prefixes = schema["prefixItems"] ?? (Array.isArray(schema["items"]) ? schema["items"] : undefined);
104
+ if (prefixes !== undefined && !Array.isArray(prefixes))
105
+ return unresolved();
106
+ if (schema["prefixItems"] !== undefined && Array.isArray(schema["items"]))
107
+ return unresolved();
108
+ const tail = Array.isArray(schema["items"]) ? schema["additionalItems"] : schema["items"];
109
+ const arrayPlan = {
110
+ ...(prefixes ? { prefix: prefixes.map(child) } : {}),
111
+ ...(tail !== undefined ? { items: child(tail) } : {}),
112
+ };
113
+ plan = merge(plan, arrayPlan);
114
+ for (const keyword of ["allOf", "oneOf", "anyOf"]) {
115
+ const branches = schema[keyword];
116
+ if (branches === undefined)
117
+ continue;
118
+ if (!Array.isArray(branches) || !branches.length)
119
+ return unresolved();
120
+ const plans = branches.map(child);
121
+ if (keyword === "allOf" || unionAlternatives)
122
+ for (const branch of plans)
123
+ plan = merge(plan, branch);
124
+ else {
125
+ if (plans.some((branch) => !equal(branch, plans[0])))
126
+ return unresolved();
127
+ plan = merge(plan, plans[0]);
128
+ }
129
+ }
130
+ // These applicators need matching/evaluation rules we do not implement.
131
+ // Public-only schemas are harmless; sensitivity or an unresolved reference fails closed.
132
+ for (const keyword of [
133
+ "additionalProperties",
134
+ "unevaluatedProperties",
135
+ "unevaluatedItems",
136
+ "contains",
137
+ "not",
138
+ "if",
139
+ "then",
140
+ "else",
141
+ ])
142
+ if (schema[keyword] !== undefined && sensitive(child(schema[keyword])))
143
+ return unresolved();
144
+ for (const keyword of ["patternProperties", "dependentSchemas", "dependencies"]) {
145
+ if (schema[keyword] === undefined)
146
+ continue;
147
+ if (!object(schema[keyword]))
148
+ return unresolved();
149
+ for (const value of Object.values(schema[keyword])) {
150
+ if (keyword === "dependencies" && Array.isArray(value))
151
+ continue;
152
+ if (sensitive(child(value)))
153
+ return unresolved();
154
+ }
155
+ }
156
+ return plan;
157
+ }
158
+ finally {
159
+ active.delete(schema);
160
+ }
161
+ }
162
+ return walk(root, 0);
163
+ }
164
+ /** Omit whole array values when private elements cannot be removed without shifting indices. */
165
+ function apply(value, plan, mark, depth = 0) {
166
+ if (!sensitive(plan))
167
+ return value;
168
+ if (depth > MAX_DEPTH)
169
+ throw new Error("Argument redaction depth exceeded");
170
+ if (plan.omit) {
171
+ mark(value);
172
+ return OMITTED;
173
+ }
174
+ if (Array.isArray(value)) {
175
+ const values = value.map((entry, index) => apply(entry, item(plan, index), mark, depth + 1));
176
+ return values.includes(OMITTED) ? OMITTED : values;
177
+ }
178
+ if (!object(value))
179
+ return value;
180
+ return Object.fromEntries(Object.entries(value).flatMap(([name, entry]) => {
181
+ const field = Object.hasOwn(plan.properties ?? {}, name) ? plan.properties[name] : PUBLIC;
182
+ const filtered = apply(entry, field, mark, depth + 1);
183
+ return filtered === OMITTED ? [] : [[name, filtered]];
184
+ }));
185
+ }
186
+ /** Output redaction protects the union of private fields across alternatives.
187
+ * Other sensitivity rules and unresolved-schema handling follow echo redaction. */
188
+ export function visitPrivateCallArguments(args, schema, visit) {
189
+ // Public-only schemas need no sensitivity compilation, regardless of size.
190
+ // Iterative traversal visits each object once, including definitions and cycles.
191
+ const pending = [schema];
192
+ const seen = new Set();
193
+ let privateSchema = false;
194
+ while (pending.length) {
195
+ const value = pending.pop();
196
+ if (value === null || typeof value !== "object" || seen.has(value))
197
+ continue;
198
+ seen.add(value);
199
+ if (Object.hasOwn(value, "writeOnly") && value["writeOnly"] === true) {
200
+ privateSchema = true;
201
+ break;
202
+ }
203
+ for (const entry of Object.values(value))
204
+ pending.push(entry);
205
+ }
206
+ if (!privateSchema)
207
+ return;
208
+ try {
209
+ apply(args, compile(schema, true), visit);
210
+ }
211
+ catch {
212
+ // When a private annotation exists but its location cannot be resolved,
213
+ // treat every submitted value as private, just as the echo is withheld.
214
+ visit(args);
215
+ }
216
+ }
217
+ /** Internal schema filter; the single echo helper owns snapshotting and byte budgeting. */
218
+ export function redactCallArguments(args, schema) {
219
+ try {
220
+ const plan = compile(schema);
221
+ let redacted = false;
222
+ const value = apply(args, plan, () => {
223
+ redacted = true;
224
+ });
225
+ return { ...(value !== OMITTED ? { value } : {}), redacted };
226
+ }
227
+ catch {
228
+ return { redacted: true };
229
+ }
230
+ }