@zackbart/connecta 0.32.0 → 0.33.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 (137) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +15 -7
  3. package/bin/connecta.mjs +24 -6
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.d.ts +167 -23
  6. package/dist/access-tokens.js +676 -140
  7. package/dist/agent-identity.js +47 -0
  8. package/dist/auth/cloudflare-access.js +13 -5
  9. package/dist/call-admission-context.js +15 -0
  10. package/dist/call-admission.js +30 -43
  11. package/dist/call-recovery.js +6 -2
  12. package/dist/config-value-sources.js +11 -9
  13. package/dist/config.d.ts +37 -75
  14. package/dist/config.js +110 -36
  15. package/dist/connector-access.d.ts +4 -34
  16. package/dist/connector-access.js +15 -99
  17. package/dist/connector-caller.d.ts +7 -1
  18. package/dist/connector-caller.js +3 -2
  19. package/dist/connectors/api-connector.d.ts +27 -4
  20. package/dist/connectors/api-connector.js +2 -0
  21. package/dist/connectors/remote-mcp.js +2 -0
  22. package/dist/describe-config.d.ts +29 -7
  23. package/dist/describe-config.js +7 -17
  24. package/dist/index.d.ts +4 -3
  25. package/dist/index.js +29 -10
  26. package/dist/invocation.js +5 -3
  27. package/dist/membership.d.ts +51 -0
  28. package/dist/membership.js +121 -0
  29. package/dist/module-contracts.d.ts +23 -6
  30. package/dist/operator-record.d.ts +2 -2
  31. package/dist/operator-record.js +2 -4
  32. package/dist/operator-ui/agents.d.ts +74 -0
  33. package/dist/operator-ui/agents.js +104 -0
  34. package/dist/operator-ui/contract.d.ts +149 -28
  35. package/dist/operator-ui/endpoints.js +192 -0
  36. package/dist/operator-ui/fix-prompts.js +4 -4
  37. package/dist/operator-ui/generated.js +5 -4
  38. package/dist/operator-ui/member-page.js +112 -0
  39. package/dist/operator-ui/model.d.ts +14 -6
  40. package/dist/operator-ui/view.d.ts +84 -19
  41. package/dist/operator-ui/view.js +82 -21
  42. package/dist/page-styles.js +1 -1
  43. package/dist/provider.d.ts +61 -8
  44. package/dist/provider.js +46 -7
  45. package/dist/providers/_shared/google/workspace.d.ts +53 -16
  46. package/dist/providers/_shared/google/workspace.js +30 -8
  47. package/dist/providers/_shared/rest/tools.js +2 -2
  48. package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
  49. package/dist/providers/_shared/rest/value-safety.js +699 -7
  50. package/dist/providers/ccb/index.js +1 -1
  51. package/dist/providers/cloudflare/index.js +3 -3
  52. package/dist/providers/cloudflare/rest.js +25 -3
  53. package/dist/providers/cloudflare/skill.generated.js +1 -1
  54. package/dist/providers/cloudflare/value-safety.js +32 -0
  55. package/dist/providers/docs/index.js +3 -3
  56. package/dist/providers/docs/skill.generated.js +2 -2
  57. package/dist/providers/drive/index.js +3 -3
  58. package/dist/providers/drive/skill.generated.js +2 -2
  59. package/dist/providers/forms/index.js +3 -3
  60. package/dist/providers/forms/skill.generated.js +2 -2
  61. package/dist/providers/gmail/index.js +10 -8
  62. package/dist/providers/gmail/skill.generated.js +2 -2
  63. package/dist/providers/mixpanel/index.js +1 -1
  64. package/dist/providers/notion/index.d.ts +7 -0
  65. package/dist/providers/notion/index.js +100 -11
  66. package/dist/providers/notion/rest.js +45 -5
  67. package/dist/providers/notion/skill.generated.js +2 -2
  68. package/dist/providers/notion/value-safety.js +30 -5
  69. package/dist/providers/overflow/index.js +1 -1
  70. package/dist/providers/planning-center/index.js +1 -1
  71. package/dist/providers/sheets/index.d.ts +3 -0
  72. package/dist/providers/sheets/index.js +6 -3
  73. package/dist/providers/sheets/skill.generated.js +2 -2
  74. package/dist/providers/slides/index.js +3 -3
  75. package/dist/providers/slides/skill.generated.js +2 -2
  76. package/dist/providers/stripe/rest.js +9 -2
  77. package/dist/providers/stripe/skill.generated.js +1 -1
  78. package/dist/providers/stripe/value-safety.js +22 -0
  79. package/dist/providers/vercel/rest.js +20 -2
  80. package/dist/providers/vercel/skill.generated.js +1 -1
  81. package/dist/providers/vercel/value-safety.js +22 -0
  82. package/dist/registry.d.ts +2 -3
  83. package/dist/registry.js +8 -9
  84. package/dist/routes/access-tokens.js +82 -75
  85. package/dist/routes/activity-disclosure.js +17 -18
  86. package/dist/routes/activity.js +34 -54
  87. package/dist/routes/actor-labels.js +33 -4
  88. package/dist/routes/agent-path.js +43 -0
  89. package/dist/routes/connect.js +16 -12
  90. package/dist/routes/credentials.js +16 -40
  91. package/dist/routes/mcp.js +145 -95
  92. package/dist/routes/oauth-management.js +11 -5
  93. package/dist/routes/oauth.js +3 -9
  94. package/dist/routes/operator.js +24 -23
  95. package/dist/routes/shared.d.ts +60 -20
  96. package/dist/routes/shared.js +187 -96
  97. package/dist/routes/ui-config.js +437 -97
  98. package/dist/routes/ui-data.js +2 -2
  99. package/dist/routes/ui.js +9 -16
  100. package/dist/runtime/call-admission.js +9 -0
  101. package/dist/server.js +5 -0
  102. package/dist/storage/keys.js +9 -1
  103. package/dist/types.d.ts +11 -0
  104. package/dist/ui.d.ts +1 -1
  105. package/dist/ui.js +16 -14
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/workspace-address.js +9 -0
  109. package/dist/workspace-subject.js +23 -0
  110. package/documentation/README.md +46 -22
  111. package/documentation/architecture.md +355 -50
  112. package/documentation/auth.md +775 -243
  113. package/documentation/code-mode.md +14 -11
  114. package/documentation/deploying.md +121 -24
  115. package/documentation/integrating.md +92 -21
  116. package/documentation/meta-tools.md +14 -9
  117. package/documentation/migration-0.33.md +344 -0
  118. package/documentation/operating.md +3 -1
  119. package/documentation/operator-tests.md +115 -28
  120. package/documentation/operator-ui.md +326 -61
  121. package/documentation/provider-auth-migration.md +37 -23
  122. package/documentation/provider-migration-0.29.md +2 -2
  123. package/examples/worker/AGENTS.md +30 -8
  124. package/examples/worker/README.md +92 -53
  125. package/examples/worker/src/connecta.config.ts +36 -20
  126. package/examples/worker/wrangler.jsonc +3 -0
  127. package/package.json +3 -2
  128. package/templates/node/.env.example +3 -0
  129. package/templates/node/AGENTS.md +27 -6
  130. package/templates/node/Dockerfile +2 -2
  131. package/templates/node/README.md +67 -42
  132. package/templates/node/docker-compose.yml +3 -2
  133. package/templates/node/package.json +4 -3
  134. package/templates/node/src/adopt-token.ts +23 -0
  135. package/templates/node/src/connecta.config.ts +28 -12
  136. package/templates/node/src/rotate-token.ts +17 -0
  137. package/templates/node/src/provision-token.ts +0 -17
@@ -0,0 +1,47 @@
1
+ // Code-declared agents (#837): the names deployment code lists in `agents`,
2
+ // the identity an admitted agent carries, and the one provider trusted to
3
+ // assert it. Pure, so core reads it without reaching the token module.
4
+ import { ConfigError } from "./config-schema.js";
5
+ /** Agent names: lowercase letters, digits, `_` and `-`. */
6
+ const AGENT_NAME_RE = /^[a-z0-9_-]+$/u;
7
+ /** Longest accepted agent name, so a name always fits a storage key. */
8
+ const MAX_AGENT_NAME_LENGTH = 64;
9
+ /** Directory namespace of agent subjects and activity actors. */
10
+ export const AGENT_NAMESPACE = "connecta:access-tokens:v1";
11
+ /** Whether `value` is an acceptable agent name. */
12
+ export function isAgentName(value) {
13
+ return typeof value === "string" && value.length <= MAX_AGENT_NAME_LENGTH && AGENT_NAME_RE.test(value);
14
+ }
15
+ /**
16
+ * The subject id that partitions an agent's per-caller state, such as result
17
+ * handles. The `agent:` prefix keeps it apart from the UUID subjects older
18
+ * tokens used in the same namespace.
19
+ */
20
+ export function agentSubjectId(name) {
21
+ return `agent:${name}`;
22
+ }
23
+ /** Validate a declared agent list: an array of unique, well-formed names. */
24
+ export function assertAgentNames(value, path) {
25
+ if (!Array.isArray(value))
26
+ throw new ConfigError(`${path} must be an array of agent names.`);
27
+ const seen = new Set();
28
+ value.forEach((name, index) => {
29
+ if (!isAgentName(name)) {
30
+ throw new ConfigError(`${path}[${index}] must be an agent name: 1 to ${MAX_AGENT_NAME_LENGTH} characters of ` +
31
+ "lowercase letters, digits, _ and -.");
32
+ }
33
+ if (seen.has(name))
34
+ throw new ConfigError(`${path}[${index}] repeats the agent name "${name}".`);
35
+ seen.add(name);
36
+ });
37
+ }
38
+ const agentProviders = new WeakSet();
39
+ /** Mark the built-in access-token adapter as the one provider whose `agent` is trusted. */
40
+ export function trustAgentProvider(provider) {
41
+ agentProviders.add(provider);
42
+ return provider;
43
+ }
44
+ /** Whether `provider` is the built-in access-token adapter. */
45
+ export function assertsAgents(provider) {
46
+ return agentProviders.has(provider);
47
+ }
@@ -1,3 +1,4 @@
1
+ import { withDeadline } from "../timeout.js";
1
2
  import { authorizationCredential } from "../inbound-credential.js";
2
3
  import { validIdentityReference } from "../identity.js";
3
4
  function identityString(identity, field) {
@@ -9,7 +10,7 @@ function identityString(identity, field) {
9
10
  function unauthorized() {
10
11
  return {
11
12
  ok: false,
12
- response: Response.json({ error: "Cloudflare Access authentication required" }, { status: 401, headers: { "WWW-Authenticate": 'Bearer scope="openid email"' } }),
13
+ response: Response.json({ error: "Cloudflare Access authentication required" }, { status: 401, headers: { "WWW-Authenticate": 'Bearer scope="openid email"', "Cache-Control": "no-store" } }),
13
14
  };
14
15
  }
15
16
  /**
@@ -34,17 +35,24 @@ export function cloudflareAccessAuth() {
34
35
  return unauthorized();
35
36
  let identity;
36
37
  try {
37
- identity = await access.getIdentity();
38
+ identity = await withDeadline(() => access.getIdentity(), {
39
+ timeoutMs: 5000,
40
+ signal: request.signal,
41
+ timeoutError: new Error("Access identity lookup timed out"),
42
+ });
38
43
  }
39
44
  catch {
40
- return unauthorized();
45
+ return {
46
+ ok: false,
47
+ response: Response.json({ error: "Cloudflare Access identity unavailable", problem: "identity_unavailable" }, { status: 503, headers: { "Cache-Control": "no-store", "Retry-After": "1" } }),
48
+ };
41
49
  }
42
50
  // Access binds ctx.access.aud to its Worker application at the edge.
43
51
  // Service credentials can pass that edge, but connecta machines must
44
52
  // present a cta_ token to the machine provider instead.
45
53
  const forbidden = () => ({
46
54
  ok: false,
47
- response: Response.json({ error: "Cloudflare Access human identity required" }, { status: 403 }),
55
+ response: Response.json({ error: "Cloudflare Access human identity required" }, { status: 403, headers: { "Cache-Control": "no-store" } }),
48
56
  });
49
57
  if (!identity || typeof identity !== "object" || Array.isArray(identity))
50
58
  return forbidden();
@@ -56,7 +64,7 @@ export function cloudflareAccessAuth() {
56
64
  if (!userId) {
57
65
  return {
58
66
  ok: false,
59
- response: Response.json({ error: "Cloudflare Access user identity required" }, { status: 403 }),
67
+ response: Response.json({ error: "Cloudflare Access user identity required" }, { status: 403, headers: { "Cache-Control": "no-store" } }),
60
68
  };
61
69
  }
62
70
  return { ok: true, userId, subjectId: userId };
@@ -0,0 +1,15 @@
1
+ import { chargeCallBudget } from "./runtime/call-admission.js";
2
+ import { runEdge } from "./runtime/run.js";
3
+ const permits = new WeakMap();
4
+ export function attachCallAdmission(ctx, permit) {
5
+ permits.set(ctx, permit);
6
+ }
7
+ /**
8
+ * Charge one sequential auxiliary request using the enclosing concurrency slot.
9
+ * Await before dispatch and propagate refusal. Contexts outside an invocation
10
+ * have no registry policy to charge.
11
+ */
12
+ export function chargeConnectorCall(ctx) {
13
+ const permit = permits.get(ctx);
14
+ return permit ? runEdge(chargeCallBudget(permit, ctx.signal)) : Promise.resolve();
15
+ }
@@ -1,6 +1,7 @@
1
1
  import { Deferred, Duration, Effect } from "effect";
2
+ import { CALL_ADMISSION } from "./provider.js";
2
3
  import { ConnectorCallError } from "./errors.js";
3
- import { provideCallAdmissionProgram, startCallAdmission } from "./runtime/call-admission.js";
4
+ import { provideCallAdmissionProgram, provideCallBudgetCharge, startCallAdmission } from "./runtime/call-admission.js";
4
5
  import { fromSignal, runEdge } from "./runtime/run.js";
5
6
  const DEFAULT_MAX_QUEUE_SIZE = 32;
6
7
  const DEFAULT_QUEUE_TIMEOUT_MS = 5_000;
@@ -50,18 +51,6 @@ export function aggregateCallAdmissionSnapshots(snapshots) {
50
51
  }
51
52
  return aggregate;
52
53
  }
53
- function positiveWhole(value, name) {
54
- if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
55
- throw new TypeError(`${name} must be a positive whole number.`);
56
- }
57
- return value;
58
- }
59
- function nonNegativeWhole(value, name) {
60
- if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
61
- throw new TypeError(`${name} must be a non-negative whole number.`);
62
- }
63
- return value;
64
- }
65
54
  /**
66
55
  * Per-runtime, per-connector call admission. State contains only bounded
67
56
  * partition keys, counters, timestamps, signals, and promise continuations;
@@ -88,36 +77,14 @@ export class ConnectorCallAdmissionController {
88
77
  queueWaitMaxMs = 0;
89
78
  constructor(connectorId, policy) {
90
79
  this.connectorId = connectorId;
91
- if (!Array.isArray(policy.rules) || policy.rules.length !== 1) {
92
- throw new TypeError(`connector "${connectorId}" callAdmission.rules must contain exactly one rule in this release.`);
93
- }
80
+ policy = CALL_ADMISSION.parse(policy, `connector "${connectorId}" callAdmission`);
94
81
  const rule = policy.rules[0];
95
- if (rule.maxConcurrency === undefined && rule.budget === undefined) {
96
- throw new TypeError(`connector "${connectorId}" callAdmission rule must declare maxConcurrency or budget.`);
97
- }
98
- this.maxConcurrency =
99
- rule.maxConcurrency === undefined
100
- ? undefined
101
- : positiveWhole(rule.maxConcurrency, `connector "${connectorId}" callAdmission maxConcurrency`);
102
- if (this.maxConcurrency === undefined &&
103
- (rule.maxQueueSize !== undefined || rule.queueTimeoutMs !== undefined || rule.retryAfterMs !== undefined)) {
104
- throw new TypeError(`connector "${connectorId}" callAdmission queue settings require maxConcurrency.`);
105
- }
106
- this.maxQueueSize = nonNegativeWhole(rule.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, `connector "${connectorId}" callAdmission maxQueueSize`);
107
- this.queueTimeoutMs = positiveWhole(rule.queueTimeoutMs ?? DEFAULT_QUEUE_TIMEOUT_MS, `connector "${connectorId}" callAdmission queueTimeoutMs`);
108
- this.retryAfterMs = nonNegativeWhole(rule.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS, `connector "${connectorId}" callAdmission retryAfterMs`);
109
- this.maxPartitions = positiveWhole(policy.maxPartitions ?? DEFAULT_MAX_PARTITIONS, `connector "${connectorId}" callAdmission maxPartitions`);
110
- if (rule.budget) {
111
- if (rule.budget.kind !== "rolling-window") {
112
- throw new TypeError(`connector "${connectorId}" callAdmission budget kind must be "rolling-window".`);
113
- }
114
- const maxCalls = positiveWhole(rule.budget.maxCalls, `connector "${connectorId}" callAdmission budget.maxCalls`);
115
- const windowMs = positiveWhole(rule.budget.windowMs, `connector "${connectorId}" callAdmission budget.windowMs`);
116
- this.budget = { maxCalls, windowMs };
117
- }
118
- else {
119
- this.budget = undefined;
120
- }
82
+ this.maxConcurrency = rule.maxConcurrency;
83
+ this.maxQueueSize = rule.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
84
+ this.queueTimeoutMs = rule.queueTimeoutMs ?? DEFAULT_QUEUE_TIMEOUT_MS;
85
+ this.retryAfterMs = rule.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS;
86
+ this.maxPartitions = policy.maxPartitions ?? DEFAULT_MAX_PARTITIONS;
87
+ this.budget = rule.budget ? { maxCalls: rule.budget.maxCalls, windowMs: rule.budget.windowMs } : undefined;
121
88
  this.partitionKey = rule.partitionKey;
122
89
  }
123
90
  acquire(input) {
@@ -179,7 +146,7 @@ export class ConnectorCallAdmissionController {
179
146
  this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
180
147
  }
181
148
  let released = false;
182
- return {
149
+ const permit = {
183
150
  waitMs,
184
151
  release: () => {
185
152
  if (released)
@@ -190,6 +157,26 @@ export class ConnectorCallAdmissionController {
190
157
  this.pump(state);
191
158
  },
192
159
  };
160
+ provideCallBudgetCharge(permit, (chargedAt, signal) => {
161
+ if (this.closed || released) {
162
+ return Effect.fail(new CallAdmissionError("closed", "unavailable", `Connector "${this.connectorId}" call admission ${this.closed ? "is closed" : "permit is released"}.`));
163
+ }
164
+ if (signal?.aborted) {
165
+ this.cancelledTotal++;
166
+ return Effect.fail(this.cancelled(signal));
167
+ }
168
+ this.pruneBudget(state, chargedAt);
169
+ const retryAfterMs = this.budgetRetryAfterMs(state, chargedAt);
170
+ if (retryAfterMs !== undefined) {
171
+ this.rateLimitedTotal++;
172
+ return Effect.fail(this.budgetLimited(retryAfterMs));
173
+ }
174
+ if (this.budget)
175
+ state.admittedAt.push(chargedAt);
176
+ this.admittedTotal++;
177
+ return Effect.void;
178
+ });
179
+ return permit;
193
180
  }
194
181
  pump(state) {
195
182
  if (this.closed || this.maxConcurrency === undefined)
@@ -1,18 +1,22 @@
1
1
  const MAX_FACTS = 4;
2
2
  const MAX_TEXT = 128;
3
+ // Shared with the REST write schema so every accepted key survives recovery.
4
+ export const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
3
5
  const NAME = /^[A-Za-z][A-Za-z0-9]*$/;
4
6
  const facts = new WeakMap();
5
7
  /**
6
8
  * Record facts that let the caller reconcile or safely retry this call's
7
9
  * write. Bounded: at most four string facts with short camelCase names; a fact
8
- * outside the bound is dropped rather than truncated.
10
+ * outside the bound is dropped rather than truncated. Idempotency keys allow
11
+ * 255 characters to match the REST write schema; other values allow 128.
9
12
  */
10
13
  export function recordRecovery(ctx, recovery) {
11
14
  const kept = { ...facts.get(ctx) };
12
15
  for (const [name, value] of Object.entries(recovery)) {
13
16
  if (Object.keys(kept).length >= MAX_FACTS && !Object.hasOwn(kept, name))
14
17
  break;
15
- if (!NAME.test(name) || name.length > 32 || typeof value !== "string" || value.length > MAX_TEXT)
18
+ const maxLength = name === "idempotencyKey" ? IDEMPOTENCY_KEY_MAX_LENGTH : MAX_TEXT;
19
+ if (!NAME.test(name) || name.length > 32 || typeof value !== "string" || value.length > maxLength)
16
20
  continue;
17
21
  kept[name] = value;
18
22
  }
@@ -44,7 +44,6 @@ function factorySource(sources, parts) {
44
44
  export function describeConfigSources(snapshot, raw) {
45
45
  const sources = {};
46
46
  const connectorSources = new Map(raw.connectors.map((connector) => [connector.id, factorySources(connector)]));
47
- const moduleSources = { accessTokens: factorySources(raw.accessTokens) };
48
47
  const provided = (value) => (value === undefined ? "default" : "config");
49
48
  for (const path of configValuePaths(snapshot)) {
50
49
  const parts = path.split(".").slice(1);
@@ -56,12 +55,12 @@ export function describeConfigSources(snapshot, raw) {
56
55
  source = provided(raw.serverInfo?.[key]);
57
56
  else if (group === "urls")
58
57
  source = key === "mcpPath" ? "default" : provided(raw[key]);
59
- else if (group === "hasGrant")
60
- source = provided(raw.grant);
58
+ else if (group === "maintainers")
59
+ source = provided(raw.maintainers);
61
60
  else if (group === "readOnly")
62
61
  source = provided(raw.readOnly);
63
- else if (group === "identity")
64
- source = provided(raw.identity?.[key]);
62
+ else if (group === "agents")
63
+ source = provided(raw.agents);
65
64
  else if (group === "storage")
66
65
  source = provided(raw.storage);
67
66
  else if (group === "deploymentInfo")
@@ -70,11 +69,14 @@ export function describeConfigSources(snapshot, raw) {
70
69
  source = provided(raw.classification);
71
70
  else if (group === "pools")
72
71
  // An inherited value comes from wherever the deployment's came from.
73
- source = field === "readOnly" ? provided(raw.pools?.[key]?.readOnly ?? raw.readOnly) : provided(raw.pools);
72
+ source =
73
+ field === "readOnly"
74
+ ? provided(raw.pools?.[key]?.readOnly ?? raw.readOnly)
75
+ : field === "owner"
76
+ ? provided(raw.pools?.[key]?.owner)
77
+ : provided(raw.pools);
74
78
  else if (group === "modules") {
75
- const module = key === "ui" ? raw.ui : key === "vault" ? raw.vault : key === "activity" ? raw.activity : raw.accessTokens;
76
- const factory = moduleSources[key];
77
- source = (factory ? factorySource(factory, parts.slice(2)) : undefined) ?? provided(module);
79
+ source = provided(key === "ui" ? raw.ui : key === "vault" ? raw.vault : key === "activity" ? raw.activity : raw.accessTokens);
78
80
  }
79
81
  else if (group === "branding") {
80
82
  const brand = raw.ui?.branding;
package/dist/config.d.ts CHANGED
@@ -1,10 +1,8 @@
1
- import type { ConnectorGrant } from "./connector-access.js";
2
1
  import type { CredentialVault } from "./credential-contract.js";
3
2
  import { type ConfigInput, type ConfigOutput } from "./config-schema.js";
3
+ import type { MemberRef } from "./membership.js";
4
4
  import type { AccessTokensModule, ActivityModule, OperatorSurface } from "./module-contracts.js";
5
- import type { AuthenticatedIdentity, Connector, Executor, IdentityReference, InboundAuth, KVStorage, Logger } from "./types.js";
6
- /** Config-owned identity rules for one deployment and tenant. */
7
- export type ConnectorPermission = "all" | "none" | readonly string[];
5
+ import type { Connector, Executor, InboundAuth, KVStorage, Logger } from "./types.js";
8
6
  declare const discovery: {
9
7
  /**
10
8
  * Maximum connector catalogs/status probes fetched at once by discovery
@@ -162,26 +160,6 @@ declare const admission: {
162
160
  };
163
161
  };
164
162
  };
165
- declare const identity: {
166
- /**
167
- * What this admitted identity may discover and call: `"all"`, or a list
168
- * whose entries are connector ids (the whole connector) and `connector.tool`
169
- * addresses (that tool only), or `{ tool: "connector.tool",
170
- * requireReadOnly: true }` for an exact tool only while its loaded catalog
171
- * explicitly classifies it read-only. Grants are additive: an unrestricted
172
- * connector or address grant wins over the guarded form. An address naming
173
- * a tool the catalog lacks is unreachable and warned once, never widened.
174
- */
175
- connectorAccess: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>, ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined, true>;
176
- /** Global payload-free activity reads. Defaults to interactive humans. */
177
- activityAccess: import("./config-schema.js").Field<(principal: Readonly<IdentityReference>) => boolean | Promise<boolean>, ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined, true>;
178
- /** Shared credential and OAuth administration. Defaults to none. */
179
- credentialAdministration: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
180
- /** Client-token lifecycle management by interactive humans. Defaults to false. */
181
- accessTokenManagement: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
182
- /** Connecting or changing the caller's personal account. Defaults to none. */
183
- personalConnection: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
184
- };
185
163
  declare const pool: {
186
164
  /**
187
165
  * Whether this pool's endpoint refuses every write: write tools are absent
@@ -193,66 +171,52 @@ declare const pool: {
193
171
  /** Connector ids and exact `connector.tool` addresses in this pool. */
194
172
  tools: import("./config-schema.js").Field<readonly string[], readonly string[], false>;
195
173
  /**
196
- * Whether this admitted identity may open the pool. Denied by default:
197
- * a pool with no grant serves nobody. A false return, a throw, and an
198
- * undeclared pool name are the same 404.
174
+ * Who may open `/mcp/<name>`: principal ids in the namespace of the one
175
+ * interactive auth provider, `{ namespace, id }`, or `"agent:<name>"` for a
176
+ * declared agent. Required and non-empty; the pool serves exactly its
177
+ * `tools` to these members and the same 404 to everyone else.
199
178
  */
200
- grant: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
179
+ members: import("./config-schema.js").Field<readonly MemberRef[], readonly MemberRef[], false>;
180
+ /**
181
+ * The Google Workspace address agents in this pool act as, on a Workspace
182
+ * connector whose `subject` is a mapping function. Humans always act as
183
+ * themselves through that mapping, and a fixed `subject` address applies to
184
+ * everyone. Without an owner, an agent's Workspace calls fail
185
+ * `auth_required`.
186
+ */
187
+ owner: import("./config-schema.js").Field<string, string | undefined, true>;
201
188
  };
202
189
  declare const connectaConfig: {
203
190
  connectors: import("./config-schema.js").Field<Connector[], readonly Connector[], false>;
204
191
  /** Inbound auth adapters, such as clerkAuth(...); omit for open (dev). */
205
192
  auth: import("./config-schema.js").Field<InboundAuth | InboundAuth[], readonly InboundAuth[], true>;
206
- /** Code-derived connection visibility and independent management permissions. */
207
- identity: import("./config-schema.js").Field<{
208
- connectorAccess?: ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined;
209
- activityAccess?: ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined;
210
- credentialAdministration?: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
211
- accessTokenManagement?: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
212
- personalConnection?: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
213
- }, {
214
- readonly connectorAccess: ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined;
215
- readonly activityAccess: ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined;
216
- readonly credentialAdministration: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
217
- readonly accessTokenManagement: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
218
- readonly personalConnection: ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined;
219
- }, true> & {
220
- readonly shape: {
221
- /**
222
- * What this admitted identity may discover and call: `"all"`, or a list
223
- * whose entries are connector ids (the whole connector) and `connector.tool`
224
- * addresses (that tool only), or `{ tool: "connector.tool",
225
- * requireReadOnly: true }` for an exact tool only while its loaded catalog
226
- * explicitly classifies it read-only. Grants are additive: an unrestricted
227
- * connector or address grant wins over the guarded form. An address naming
228
- * a tool the catalog lacks is unreachable and warned once, never widened.
229
- */
230
- connectorAccess: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>, ((identity: Readonly<AuthenticatedIdentity>) => "all" | readonly ConnectorGrant[] | Promise<"all" | readonly ConnectorGrant[]>) | undefined, true>;
231
- /** Global payload-free activity reads. Defaults to interactive humans. */
232
- activityAccess: import("./config-schema.js").Field<(principal: Readonly<IdentityReference>) => boolean | Promise<boolean>, ((principal: Readonly<IdentityReference>) => boolean | Promise<boolean>) | undefined, true>;
233
- /** Shared credential and OAuth administration. Defaults to none. */
234
- credentialAdministration: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
235
- /** Client-token lifecycle management by interactive humans. Defaults to false. */
236
- accessTokenManagement: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
237
- /** Connecting or changing the caller's personal account. Defaults to none. */
238
- personalConnection: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>, ((identity: Readonly<AuthenticatedIdentity>) => ConnectorPermission | Promise<ConnectorPermission>) | undefined, true>;
239
- };
240
- };
241
193
  /**
242
- * Whether this admitted identity may open plain `/mcp`. Omitted admits
243
- * everyone; only literal true admits when configured. Other returns and
244
- * throws receive the same 404 as a refused or undeclared pool.
194
+ * Agents, declared by name: lowercase letters, digits, `_` and `-`, each
195
+ * unique. Each has one `cta_` token slot that operators issue, rotate, and
196
+ * revoke; nothing outside code adds, renames, or rebinds an agent. Removing
197
+ * a name stops its token on the next request. Requires `accessTokens`.
198
+ * An admitted agent carries `identity.agent` and has no principal, so
199
+ * personal connectors are absent for it. Defaults to none.
200
+ */
201
+ agents: import("./config-schema.js").Field<readonly string[], readonly string[], true>;
202
+ /**
203
+ * The people who may use plain `/mcp`, which serves the whole catalog with
204
+ * the deployment's `readOnly`, and who administer the deployment. Same
205
+ * reference forms as pool `members`, people only. Required with inbound
206
+ * auth; a maintainer opens no pool unless listed among its members.
245
207
  */
246
- grant: import("./config-schema.js").Field<(identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>, ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined, true>;
247
- /** Named tool pools, each served at `/mcp/<name>` to identities its grant admits. */
208
+ maintainers: import("./config-schema.js").Field<readonly MemberRef[], readonly MemberRef[] | undefined, true>;
209
+ /** Named tool pools, each served at `/mcp/<name>` to its members only. */
248
210
  pools: import("./config-schema.js").Field<Record<string, {
249
211
  readOnly?: boolean | undefined;
250
- grant?: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
212
+ owner?: string | undefined;
251
213
  tools: readonly string[];
214
+ members: readonly MemberRef[];
252
215
  }>, Readonly<Record<string, {
253
216
  readonly readOnly: boolean | undefined;
254
217
  readonly tools: readonly string[];
255
- readonly grant: ((identity: Readonly<AuthenticatedIdentity>) => boolean | Promise<boolean>) | undefined;
218
+ readonly members: readonly MemberRef[];
219
+ readonly owner: string | undefined;
256
220
  }>> | undefined, true>;
257
221
  /** Exact connector id -> tool name -> verdict. Unknown names fail at catalog publication. */
258
222
  classification: import("./config-schema.js").Field<Record<string, Record<string, "read" | "write">>, Readonly<Record<string, Readonly<Record<string, "read" | "write">>>> | undefined, true>;
@@ -281,7 +245,7 @@ declare const connectaConfig: {
281
245
  vault: import("./config-schema.js").Field<CredentialVault, CredentialVault | undefined, true>;
282
246
  /** Optional connection UI, created by operatorUi() from /ui. */
283
247
  ui: import("./config-schema.js").Field<OperatorSurface, OperatorSurface | undefined, true>;
284
- /** Optional managed client tokens, created by accessTokens() from /auth/access-tokens. */
248
+ /** Optional agent tokens for the declared `agents`, created by accessTokens() from /auth/access-tokens. */
285
249
  accessTokens: import("./config-schema.js").Field<AccessTokensModule, AccessTokensModule | undefined, true>;
286
250
  /** Tool-catalog TTL bounds and probe deadlines. */
287
251
  discovery: import("./config-schema.js").Field<{
@@ -581,11 +545,9 @@ export type AdmissionPoolConfig = ConfigInput<typeof codeAdmission>;
581
545
  export type RequestAdmissionConfig = ConfigInput<typeof requestAdmission>;
582
546
  /** Runtime-portable server-memory boundaries for `/mcp` and fallback code-mode work. */
583
547
  export type ConnectaAdmissionConfig = ConfigInput<typeof admission>;
584
- export type ConnectaIdentityConfig = ConfigInput<typeof identity>;
585
548
  /**
586
- * A named tool pool served at `/mcp/<name>`. The pool is the slice a client
587
- * pointed at that endpoint may see; the identity's own `connectorAccess`
588
- * remains its ceiling and the pool can only narrow it.
549
+ * A named tool pool served at `/mcp/<name>`: exactly its `tools`, to its
550
+ * `members` only.
589
551
  */
590
552
  export type ConnectaPoolConfig = ConfigInput<typeof pool>;
591
553
  type Parsed = ConfigOutput<typeof connectaConfig>;