@zackbart/connecta 0.28.0 → 0.29.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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -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/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -0,0 +1,435 @@
1
+ // The combinators behind ConnectaConfig's one schema (src/config.ts).
2
+ //
3
+ // A schema here is a value that does three jobs at once, so they cannot drift
4
+ // apart: its type parameters produce the TypeScript type a deployment writes
5
+ // (`ConfigInput`) and the resolved shape core reads (`ConfigOutput`); its
6
+ // object shapes drive unknown-key rejection; and each field's `parse` and
7
+ // `absent` carry its validation and its default. There is one validation
8
+ // policy: a value that is present and wrong throws, with its path, before
9
+ // createConnecta does any work (INV-11). Nothing warns and falls back.
10
+ //
11
+ // Deliberately small. Opaque fields (connectors, modules, functions) are
12
+ // checked by identity or shape and passed through untouched, never cloned, so
13
+ // a private-field receiver or a frozen object reaches core as configured.
14
+ /** A structural configuration mistake, thrown before construction does work. */
15
+ export class ConfigError extends TypeError {
16
+ name = "ConfigError";
17
+ }
18
+ function fail(message) {
19
+ throw new ConfigError(message);
20
+ }
21
+ function isPlainObject(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function ownValue(value, key) {
25
+ return Object.prototype.hasOwnProperty.call(value, key) ? value[key] : undefined;
26
+ }
27
+ export function whole(options) {
28
+ const kind = options.min === 1 ? "positive" : "non-negative";
29
+ return {
30
+ optional: true,
31
+ parse: (value, path) => {
32
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < options.min) {
33
+ fail(`${path} must be a ${kind} whole number.`);
34
+ }
35
+ if (options.max !== undefined && value > options.max) {
36
+ fail(`${path} must be at most ${options.max.toLocaleString("en-US")}` + (options.unit ? ` ${options.unit}.` : "."));
37
+ }
38
+ return value;
39
+ },
40
+ absent: () => options.default,
41
+ };
42
+ }
43
+ /** A non-negative finite number, fractions allowed. */
44
+ export function seconds(options) {
45
+ return {
46
+ optional: true,
47
+ parse: (value, path) => {
48
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
49
+ fail(`${path} must be a non-negative number of seconds.`);
50
+ }
51
+ return value;
52
+ },
53
+ absent: () => options.default,
54
+ };
55
+ }
56
+ export function text() {
57
+ return {
58
+ optional: true,
59
+ parse: (value, path) => {
60
+ if (typeof value !== "string")
61
+ fail(`${path} must be a string.`);
62
+ return value;
63
+ },
64
+ absent: () => undefined,
65
+ };
66
+ }
67
+ /**
68
+ * A value core checks by shape and passes through untouched: a connector, a
69
+ * module, an executor. `check` throws its own message for a wrong value;
70
+ * `resolve` maps the accepted value (or `undefined`, when omitted) to what
71
+ * core reads.
72
+ */
73
+ export function opaque(options = {}) {
74
+ const resolve = options.resolve ?? ((value) => value);
75
+ return {
76
+ optional: true,
77
+ parse: (value, path) => {
78
+ options.check?.(value, path);
79
+ return resolve(value);
80
+ },
81
+ absent: () => resolve(undefined),
82
+ };
83
+ }
84
+ /** A function, never called, read, or serialized by configuration. */
85
+ export function fn() {
86
+ return opaque({
87
+ check: (value, path) => {
88
+ if (typeof value !== "function")
89
+ fail(`${path} must be a function.`);
90
+ },
91
+ });
92
+ }
93
+ /** The same field, but a deployment must supply it. */
94
+ export function required(field, message) {
95
+ return {
96
+ ...field,
97
+ optional: false,
98
+ parse: field.parse,
99
+ absent: (path) => fail(message ? message(path) : `${path} is required.`),
100
+ };
101
+ }
102
+ /**
103
+ * A closed object. Its keys are the shape's keys; anything else is refused
104
+ * by the unknown-key walk before any value is read. An omitted object
105
+ * resolves to its children's defaults.
106
+ */
107
+ export function object(shape) {
108
+ const resolve = (value, path) => {
109
+ const out = {};
110
+ for (const [key, field] of Object.entries(shape)) {
111
+ const child = value === undefined ? undefined : ownValue(value, key);
112
+ const resolved = child === undefined ? field.absent(`${path}.${key}`) : field.parse(child, `${path}.${key}`);
113
+ // An unset field without a default stays absent, as it was configured.
114
+ if (resolved !== undefined)
115
+ out[key] = resolved;
116
+ }
117
+ return Object.freeze(out);
118
+ };
119
+ return {
120
+ optional: true,
121
+ shape,
122
+ parse: (value, path) => {
123
+ if (!isPlainObject(value))
124
+ fail(`${path} must be an object.`);
125
+ return resolve(value, path);
126
+ },
127
+ absent: (path) => resolve(undefined, path),
128
+ };
129
+ }
130
+ /** A list whose items each satisfy `item`. */
131
+ export function array(item) {
132
+ return {
133
+ optional: true,
134
+ element: item,
135
+ parse: (value, path) => {
136
+ if (!Array.isArray(value))
137
+ fail(`${path} must be an array.`);
138
+ return Object.freeze(value.map((entry, index) => item.parse(entry, `${path}[${index}]`)));
139
+ },
140
+ absent: () => undefined,
141
+ };
142
+ }
143
+ /** An object keyed by caller-chosen names, each value satisfying `value`. */
144
+ export function record(value) {
145
+ return {
146
+ optional: true,
147
+ element: value,
148
+ keyed: true,
149
+ parse: (input, path) => {
150
+ if (!isPlainObject(input))
151
+ fail(`${path} must be an object.`);
152
+ // Keys are caller-chosen, so `__proto__`, `constructor`, and the like
153
+ // must stay ordinary entries: no prototype to assign through or inherit.
154
+ const out = Object.create(null);
155
+ for (const key of Object.keys(input)) {
156
+ // An explicitly undefined entry is an omitted one, as for any field.
157
+ if (input[key] === undefined)
158
+ continue;
159
+ out[key] = value.parse(input[key], `${path}.${key}`);
160
+ }
161
+ return Object.freeze(out);
162
+ },
163
+ absent: () => undefined,
164
+ };
165
+ }
166
+ /**
167
+ * A map of caller-chosen names to strings, such as static headers or extra
168
+ * OAuth parameters. The walk copies it through property descriptors, so an
169
+ * accessor is refused by path unrun and a non-string value by path unechoed.
170
+ */
171
+ export function strings() {
172
+ return { ...record(text()), strings: true };
173
+ }
174
+ /**
175
+ * A closed object that carries behaviour, such as an `api()` tool. The walk
176
+ * checks it like any closed object (own keys declared, no own accessor, and
177
+ * no inherited accessor at a declared key) but returns the original rather
178
+ * than a copy, so a prototype method keeps its receiver and a handler that
179
+ * reads `#private` fields still finds them. It may be a class instance.
180
+ */
181
+ export function instance(field) {
182
+ return { ...field, instance: true };
183
+ }
184
+ /**
185
+ * A closed object whose allowed keys depend on a discriminant, such as
186
+ * `remoteMcp({ auth: { type: "headers", headers } })`. An omitted
187
+ * discriminant selects `otherwise` when given; an omitted one without it, an
188
+ * unrecognized one, or a value that is not an object throws with its path and
189
+ * the valid values, so no union falls through to "no case". Only the walk
190
+ * reads variants; the factory validates the remaining values itself.
191
+ */
192
+ export function variants(key, cases, otherwise) {
193
+ return {
194
+ ...opaque(),
195
+ variants: { key, cases, ...(otherwise !== undefined ? { otherwise } : {}) },
196
+ };
197
+ }
198
+ /**
199
+ * A closed object for the unknown-key walk only: each named key may hold
200
+ * anything, which its factory validates. For the options of a built-in
201
+ * factory, where `object()` would resolve values the factory already owns.
202
+ */
203
+ export function keys(...names) {
204
+ const field = opaque();
205
+ return Object.fromEntries(names.map((name) => [name, field]));
206
+ }
207
+ /**
208
+ * The closed options of a built-in factory, checked against its options type:
209
+ * a key the type declares but the shape omits, or the reverse, fails to
210
+ * compile, so the runtime walk cannot drift from the published type.
211
+ */
212
+ export function optionsOf() {
213
+ return (shape) => ({ ...opaque(), shape });
214
+ }
215
+ /**
216
+ * Refuse a factory's unknown options and accessors before it reads any value,
217
+ * naming each path: `api("crm").maxResultByte`. Returns the options as a
218
+ * plain copy read through property descriptors, which the factory reads in
219
+ * place of what it was given, so no getter or Proxy trap of the caller's runs
220
+ * later either. Custom implementations a factory accepts (stores, handlers,
221
+ * executors) are `opaque()` in its shape and pass through untouched; an
222
+ * object that carries behaviour (an `api()` tool) is `instance()`, checked in
223
+ * place and passed through as given.
224
+ */
225
+ export function assertKnownOptions(value, path, field) {
226
+ const read = readPlain(value, path, field);
227
+ if (read.unknown.length === 0)
228
+ return read.value;
229
+ return fail(`Unknown option${read.unknown.length === 1 ? "" : "s"}: ${read.unknown.join(", ")}. ` +
230
+ "Check the spelling against the factory's options type.");
231
+ }
232
+ /**
233
+ * Own property descriptors, read without invoking any accessor. A config
234
+ * object that cannot be inspected (a Proxy whose trap throws, or a revoked
235
+ * one) is refused by path; the trap's own message never surfaces.
236
+ */
237
+ function inspect(value, path) {
238
+ try {
239
+ const proto = Reflect.getPrototypeOf(value);
240
+ return {
241
+ array: Array.isArray(value),
242
+ plain: proto === null || Reflect.getPrototypeOf(proto) === null,
243
+ proto,
244
+ own: Reflect.ownKeys(value).flatMap((key) => {
245
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
246
+ return descriptor ? [[key, descriptor]] : [];
247
+ }),
248
+ };
249
+ }
250
+ catch {
251
+ return fail(`${path} could not be read as plain configuration.`);
252
+ }
253
+ }
254
+ /** Whether `Array.isArray` holds, refusing by path a value that cannot say. */
255
+ function isArrayAt(value, path) {
256
+ try {
257
+ return Array.isArray(value);
258
+ }
259
+ catch {
260
+ return fail(`${path} could not be read as plain configuration.`);
261
+ }
262
+ }
263
+ const isAccessor = (descriptor) => "get" in descriptor || "set" in descriptor;
264
+ /** A data property's value, or `undefined` for an accessor, recorded by path and never run. */
265
+ function dataValue(descriptor, path, state) {
266
+ if (!isAccessor(descriptor))
267
+ return descriptor.value;
268
+ state.refused.push(`${path} must be a plain value, not a getter or setter.`);
269
+ return undefined;
270
+ }
271
+ /**
272
+ * The descriptor a declared key inherits, read along the prototype chain
273
+ * without invoking an accessor. A chain that cannot be inspected is refused
274
+ * by path like the object itself.
275
+ */
276
+ function inherited(proto, key, path) {
277
+ const seen = new Set();
278
+ try {
279
+ for (let at = proto; at !== null && !seen.has(at); at = Reflect.getPrototypeOf(at)) {
280
+ seen.add(at);
281
+ const descriptor = Reflect.getOwnPropertyDescriptor(at, key);
282
+ if (descriptor)
283
+ return descriptor;
284
+ }
285
+ return undefined;
286
+ }
287
+ catch {
288
+ return fail(`${path} could not be read as plain configuration.`);
289
+ }
290
+ }
291
+ /**
292
+ * Inspect a value a closed object or record slot holds. An array, or a
293
+ * class instance where only plain data belongs, is refused by path before
294
+ * any of its keys is read: `remoteMcp([...])` must not pass for options.
295
+ */
296
+ function inspectRecord(value, path, field) {
297
+ const inspected = inspect(value, path);
298
+ if (inspected.array)
299
+ fail(`${path} must be an object.`);
300
+ if (!inspected.plain && !field.instance)
301
+ fail(`${path} must be a plain object.`);
302
+ return inspected;
303
+ }
304
+ /**
305
+ * The case a discriminant selects. A missing one selects `otherwise` when the
306
+ * union has a default; any other missing, unrecognized, or computed
307
+ * discriminant throws with the valid values, so a misspelled
308
+ * `type: "header"` cannot construct as if no case applied. The value itself
309
+ * is never echoed.
310
+ */
311
+ function selectShape(inspected, path, variants) {
312
+ const at = `${path}.${variants.key}`;
313
+ const valid = Object.keys(variants.cases)
314
+ .map((name) => JSON.stringify(name))
315
+ .join(", ");
316
+ const descriptor = inspected.own.find(([key]) => key === variants.key)?.[1];
317
+ if (descriptor && isAccessor(descriptor))
318
+ fail(`${at} must be a plain value, not a getter or setter.`);
319
+ const discriminant = descriptor?.value;
320
+ if (discriminant === undefined) {
321
+ if (variants.otherwise !== undefined)
322
+ return variants.cases[variants.otherwise];
323
+ return fail(`${at} is required: one of ${valid}.`);
324
+ }
325
+ if (typeof discriminant !== "string" || !Object.hasOwn(variants.cases, discriminant)) {
326
+ return fail(`${at} must be one of ${valid}.`);
327
+ }
328
+ return variants.cases[discriminant];
329
+ }
330
+ /**
331
+ * Read a configuration value the way the schema sees it: every own key it
332
+ * does not declare, as paths, and the value as a plain copy built only from
333
+ * the property descriptors this walk inspected. Callers resolve that copy,
334
+ * never the original, so no property is read through `[[Get]]` twice or at
335
+ * all: no getter runs, a Proxy's `get` trap never fires, and no value is
336
+ * echoed.
337
+ *
338
+ * Unknown keys anywhere are reported first. With none, a declared key holding
339
+ * an accessor is refused by path, since a plain configuration object has no
340
+ * reason to compute a value, and a getter that throws or changes between
341
+ * reads would break the one-read guarantee. Only own string keys count, so a
342
+ * prototype contributes nothing. A closed object or record slot takes a
343
+ * plain object only; an array or class instance there is refused by path.
344
+ * Arrays are walked by the same rules, the items of an `opaque()` array
345
+ * included (`allowedOrigins`, `auth`); an `instance()` object (an `api()`
346
+ * tool) is checked the same way but returned as given; any other `opaque()`
347
+ * value (a connector, module, executor, storage, or logger) is never entered
348
+ * and passes through as configured. Configuration is operator-authored and
349
+ * trusted: the walk refuses mistakes by path without echoing a value; it is
350
+ * not a sandbox for hostile objects.
351
+ */
352
+ export function readPlain(value, path, field) {
353
+ const state = { unknown: [], refused: [] };
354
+ const plain = walk(value, path, field, state);
355
+ if (state.unknown.length === 0 && state.refused.length > 0)
356
+ fail(state.refused[0]);
357
+ return { unknown: state.unknown, value: plain };
358
+ }
359
+ function walk(value, path, field, state) {
360
+ if (field.variants &&
361
+ value !== undefined &&
362
+ (typeof value !== "object" || value === null || isArrayAt(value, path))) {
363
+ return fail(`${path} must be an object.`);
364
+ }
365
+ if (typeof value !== "object" || value === null)
366
+ return value;
367
+ if (field.shape || field.variants) {
368
+ const inspected = inspectRecord(value, path, field);
369
+ const shape = field.shape ?? selectShape(inspected, path, field.variants);
370
+ const unknown = inspected.own
371
+ .filter(([key]) => typeof key !== "string" || !Object.hasOwn(shape, key))
372
+ .map(([key]) => `${path}.${String(key)}`)
373
+ .sort();
374
+ if (unknown.length > 0) {
375
+ state.unknown.push(...unknown);
376
+ return undefined;
377
+ }
378
+ const out = {};
379
+ for (const [key, descriptor] of inspected.own) {
380
+ const child = `${path}.${String(key)}`;
381
+ out[key] = walk(dataValue(descriptor, child, state), child, shape[key], state);
382
+ }
383
+ if (!field.instance)
384
+ return out;
385
+ // A declared key the object inherits, such as a class's `handler()`, is
386
+ // read from its prototype when called; it must not be a computed one.
387
+ for (const key of Object.keys(shape)) {
388
+ if (Object.hasOwn(out, key))
389
+ continue;
390
+ const descriptor = inherited(inspected.proto, key, `${path}.${key}`);
391
+ if (descriptor)
392
+ dataValue(descriptor, `${path}.${key}`, state);
393
+ }
394
+ return value;
395
+ }
396
+ if (field.keyed) {
397
+ const inspected = inspectRecord(value, path, field);
398
+ // Keys are caller-chosen, so the copy has no prototype for `__proto__` to reach.
399
+ const out = Object.create(null);
400
+ for (const [key, descriptor] of inspected.own) {
401
+ if (typeof key !== "string" || !descriptor.enumerable)
402
+ continue;
403
+ const child = `${path}.${key}`;
404
+ const entry = dataValue(descriptor, child, state);
405
+ if (field.strings && !isAccessor(descriptor) && typeof entry !== "string") {
406
+ state.refused.push(`${child} must be a string.`);
407
+ }
408
+ out[key] = walk(entry, child, field.element, state);
409
+ }
410
+ return out;
411
+ }
412
+ if (!field.element && !isArrayAt(value, path))
413
+ return value;
414
+ const inspected = inspect(value, path);
415
+ if (!inspected.array)
416
+ return value;
417
+ const element = field.element ?? OPAQUE;
418
+ const out = [];
419
+ for (const [key, descriptor] of inspected.own) {
420
+ if (key === "length") {
421
+ const length = descriptor.value;
422
+ if (Number.isInteger(length) && length >= 0 && length <= 0xffff_ffff) {
423
+ out.length = length;
424
+ }
425
+ continue;
426
+ }
427
+ // Only indices are items; any other key on an array is never read.
428
+ if (typeof key !== "string" || String(Number(key) >>> 0) !== key)
429
+ continue;
430
+ const child = `${path}[${key}]`;
431
+ out[Number(key)] = walk(dataValue(descriptor, child, state), child, element, state);
432
+ }
433
+ return out;
434
+ }
435
+ const OPAQUE = opaque();
@@ -0,0 +1,102 @@
1
+ /** Walk only serialized keys. Stable connector/pool keys survive viewer filtering. */
2
+ export function configValuePaths(value, path = "config") {
3
+ if (value === null || typeof value !== "object")
4
+ return [path];
5
+ const entries = Object.entries(value);
6
+ if (!entries.length)
7
+ return [path];
8
+ if ("value" in value && "source" in value && entries.length === 2)
9
+ return [path];
10
+ return entries.flatMap(([key, item]) => {
11
+ const stableKey = Array.isArray(value) &&
12
+ (path === "config.connectors" || path === "config.pools") &&
13
+ item &&
14
+ typeof item === "object"
15
+ ? String(path === "config.connectors" ? item.id : item.name)
16
+ : key;
17
+ return configValuePaths(item, `${path}.${stableKey}`);
18
+ });
19
+ }
20
+ /** Read only provenance literals; custom describe hooks may throw or return unknown data. */
21
+ function factorySources(factory) {
22
+ try {
23
+ const description = factory?.describe?.();
24
+ if (!description || typeof description !== "object" || !("optionSources" in description))
25
+ return {};
26
+ const sources = description.optionSources;
27
+ if (!sources || typeof sources !== "object")
28
+ return {};
29
+ return Object.fromEntries(Object.entries(sources).filter((entry) => entry[1] === "default" || entry[1] === "config"));
30
+ }
31
+ catch {
32
+ return {};
33
+ }
34
+ }
35
+ function factorySource(sources, parts) {
36
+ for (let length = parts.length; length > 0; length--) {
37
+ const source = sources[parts.slice(0, length).join(".")];
38
+ if (source === "default" || source === "config")
39
+ return source;
40
+ }
41
+ return undefined;
42
+ }
43
+ /** Provenance is presence in code, not equality to a default value. No raw value leaves this function. */
44
+ export function describeConfigSources(snapshot, raw) {
45
+ const sources = {};
46
+ const connectorSources = new Map(raw.connectors.map((connector) => [connector.id, factorySources(connector)]));
47
+ const moduleSources = { accessTokens: factorySources(raw.accessTokens) };
48
+ const provided = (value) => (value === undefined ? "default" : "config");
49
+ for (const path of configValuePaths(snapshot)) {
50
+ const parts = path.split(".").slice(1);
51
+ const [group, key, field] = parts;
52
+ let source = "config";
53
+ if (group === "schemaVersion" || group === "connectaVersion")
54
+ source = "default";
55
+ else if (group === "server")
56
+ source = provided(raw.serverInfo?.[key]);
57
+ else if (group === "urls")
58
+ source = key === "mcpPath" ? "default" : provided(raw[key]);
59
+ else if (group === "trust")
60
+ source = provided(raw.trust);
61
+ else if (group === "identity")
62
+ source = provided(raw.identity?.[key]);
63
+ else if (group === "storage")
64
+ source = provided(raw.storage);
65
+ else if (group === "deploymentInfo")
66
+ source = provided(raw.deploymentInfo);
67
+ else if (group === "classification")
68
+ source = provided(raw.classification);
69
+ else if (group === "pools")
70
+ source = field === "trust" ? provided(raw.pools?.[key]?.trust) : provided(raw.pools);
71
+ else if (group === "modules") {
72
+ const module = key === "ui" ? raw.ui : key === "vault" ? raw.vault : key === "activity" ? raw.activity : raw.accessTokens;
73
+ const factory = moduleSources[key];
74
+ source = (factory ? factorySource(factory, parts.slice(2)) : undefined) ?? provided(module);
75
+ }
76
+ else if (group === "branding") {
77
+ const brand = raw.ui?.branding;
78
+ const brandKey = key === "faviconHref" ? "favicon" : key;
79
+ source =
80
+ key === "theme"
81
+ ? provided(brand?.theme?.[field])
82
+ : provided(brand?.[brandKey]);
83
+ }
84
+ else if (group === "connectors") {
85
+ const connector = raw.connectors.find((c) => c.id === key);
86
+ source = factorySource(connectorSources.get(key) ?? {}, parts.slice(2)) ?? source;
87
+ if (field === "authScope")
88
+ source = provided(connector?.authScope);
89
+ else if (field === "maxResultBytes")
90
+ source = provided(connector?.maxResultBytes ?? raw.calls?.maxResultBytes);
91
+ }
92
+ else if (group === "limits" || (group === "executor" && key === "admission")) {
93
+ let limit = snapshot;
94
+ for (const part of parts)
95
+ limit = limit && typeof limit === "object" ? limit[part] : undefined;
96
+ source =
97
+ limit && typeof limit === "object" && "source" in limit && limit.source === "default" ? "default" : "config";
98
+ }
99
+ sources[path] = source;
100
+ }
101
+ return sources;
102
+ }