@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
@@ -137,8 +137,8 @@ export function uiData(registry, baseUrl, options) {
137
137
  const fields = credentialFields();
138
138
  return Effect.succeed({
139
139
  ...card,
140
- ...(fields?.length ? { fields } : {}),
141
- configured: false,
140
+ ...(fields?.length ? { fields: fields.map((field) => ({ ...field, configured: null })) } : {}),
141
+ configured: null,
142
142
  removable: true,
143
143
  testable: testRule.mode !== null,
144
144
  error: "Stored credential could not be read.",
package/dist/routes/ui.js CHANGED
@@ -9,7 +9,7 @@ import { CONNECTA_VERSION } from "../version.js";
9
9
  import { CONNECTA_FAVICON_ICO } from "../favicon.js";
10
10
  import { CONNECTA_FAVICON_SVG, operatorPageForPath, renderUiHtml } from "../ui.js";
11
11
  import { authorized, refuse, scopeFor, serveOperator, visibleRegistry, } from "./operator.js";
12
- import { mayManageConnector, privateJson } from "./shared.js";
12
+ import { managesAgentTokens, mayManageConnector, privateJson, readsActivity } from "./shared.js";
13
13
  import { routeUiConfig } from "./ui-config.js";
14
14
  import { uiData } from "./ui-data.js";
15
15
  /**
@@ -84,7 +84,8 @@ export async function routeUi(context) {
84
84
  ? opts.config.auth.find((provider) => provider.uiAuth?.kind === "cloudflare-access")?.uiAuth
85
85
  : undefined;
86
86
  const uiAuth = ambient ??
87
- opts.config.auth.find((provider) => provider.uiAuth && provider.uiAuth.kind !== "cloudflare-access")?.uiAuth;
87
+ opts.config.auth.find((provider) => provider.uiAuth && provider.uiAuth.kind !== "cloudflare-access")?.uiAuth ??
88
+ opts.config.auth.find((provider) => provider.uiAuth?.kind === "cloudflare-access")?.uiAuth;
88
89
  const mcpUrl = new URL("/mcp", baseUrl).toString();
89
90
  const clerkOrigin = uiAuth?.kind === "clerk" && isSafeHttpsUrl(uiAuth.frontendApiUrl)
90
91
  ? new URL(uiAuth.frontendApiUrl).origin
@@ -115,14 +116,14 @@ function operatorView({ opts }, authz, registry) {
115
116
  const visible = registry.listConnectors();
116
117
  const mayManage = (id) => {
117
118
  const connector = registry.getConnector(id);
118
- return Boolean(connector && mayManageConnector(authz, connector));
119
+ return Boolean(connector && mayManageConnector(opts, authz, connector));
119
120
  };
120
121
  const permissions = (connector) => ({
121
122
  use: true,
122
123
  manageSharedAuth: connector.authScope !== "personal" && mayManage(connector.id),
123
124
  connectPersonal: connector.authScope === "personal" && mayManage(connector.id),
124
125
  });
125
- const activityEnabled = Boolean(opts.config.activity?.store.list) && authz.operator;
126
+ const activityEnabled = Boolean(opts.config.activity?.store.list) && readsActivity(opts, authz);
126
127
  const credentialManagement = visible.some((c) => c.credential && mayManage(c.id))
127
128
  ? opts.config.vault
128
129
  ? "available"
@@ -169,17 +170,9 @@ function summary(context) {
169
170
  const authz = yield* authorized(context);
170
171
  const registry = yield* visibleRegistry(context, authz);
171
172
  const { visible, mayManage, permissions, activityEnabled, credentialManagement } = operatorView(context, authz, registry);
172
- // Setup commands are offered per pool, but only for pools this identity's
173
- // grant admits: `/mcp/<name>` answers every other name with one flat 404 so
174
- // a credential cannot enumerate them, and the page must not undo that.
175
- const pools = [];
176
- for (const [name, pool] of opts.pools ?? []) {
177
- const granted = yield* Effect.tryPromise(async () => (await pool.grant(authz.identity)) === true).pipe(
178
- // A throwing grant is a refusal at the endpoint, and so a refusal here.
179
- Effect.orElseSucceed(() => false));
180
- if (granted)
181
- pools.push(name);
182
- }
173
+ // Only maintainers reach here (#837); setup commands are offered for every
174
+ // declared pool, since they administer them all.
175
+ const pools = [...opts.pools.keys()];
183
176
  return privateJson({
184
177
  ...(pools.length ? { pools } : {}),
185
178
  serverInfo: opts.config.serverInfo,
@@ -187,7 +180,7 @@ function summary(context) {
187
180
  activityEnabled,
188
181
  ...(opts.config.accessTokens
189
182
  ? {
190
- accessTokenManagement: authz.accessTokenManagement && authz.identity.principal ? "available" : "requires_operator",
183
+ accessTokenManagement: managesAgentTokens(opts, authz) ? "available" : "requires_operator",
191
184
  }
192
185
  : {}),
193
186
  credentialManagement,
@@ -12,6 +12,15 @@
12
12
  // shared queue between them was refused.
13
13
  import { Effect } from "effect";
14
14
  let program;
15
+ const budgetCharges = new WeakMap();
16
+ /** Bind budget-only admission to the partition already owned by this permit. */
17
+ export function provideCallBudgetCharge(permit, charge) {
18
+ budgetCharges.set(permit, charge);
19
+ }
20
+ /** Charge an internal request without acquiring another concurrency permit. */
21
+ export function chargeCallBudget(permit, signal) {
22
+ return Effect.clockWith((clock) => budgetCharges.get(permit)?.(clock.currentTimeMillisUnsafe(), signal) ?? Effect.void);
23
+ }
15
24
  // What the live Clock reads, looked up on every call rather than bound once,
16
25
  // so a Date faked after a caller queued (vitest's setSystemTime) still counts.
17
26
  const liveNow = () => Date.now();
package/dist/server.js CHANGED
@@ -2,6 +2,7 @@ import { Effect } from "effect";
2
2
  import { notFoundResponse } from "./branding.js";
3
3
  import { aggregateCallAdmissionSnapshots } from "./call-admission.js";
4
4
  import { isAdmittingExecutor } from "./executor-admission.js";
5
+ import { isAgentPath } from "./routes/agent-path.js";
5
6
  import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
6
7
  import { routeOAuthCallback } from "./routes/oauth.js";
7
8
  import { routeOAuthClientMetadata } from "./routes/oauth-client-metadata.js";
@@ -59,6 +60,10 @@ export function createFetchHandler(opts) {
59
60
  };
60
61
  const route = (context) => Effect.gen(function* () {
61
62
  const { request, path, baseUrl } = context;
63
+ // The agent namespace is routed before anything else, so no path
64
+ // Cloudflare Access bypasses as `/agent/*` can reach a human route.
65
+ if (isAgentPath(path))
66
+ return (yield* routeMcp.handle(context)) ?? notFoundResponse(request, opts.config);
62
67
  const clientMetadata = routeOAuthClientMetadata(context);
63
68
  if (clientMetadata)
64
69
  return clientMetadata;
@@ -164,7 +164,12 @@ export const oauthHandoffKeys = {
164
164
  },
165
165
  handoff: (connectorId, stateHash) => validateStorageKey(`oauth-handoff:v1:${connectorId}:${stateHash}`),
166
166
  };
167
- /** Connecta-issued `cta_` access tokens: records, digest lookups, capacity. */
167
+ /**
168
+ * Connecta-issued `cta_` access tokens: token records, digest lookups, one
169
+ * slot per declared agent naming its live record, and a never-deleted claim per
170
+ * legacy record an adoption has taken. `active` is the capacity
171
+ * ledger releases before 0.33 wrote; nothing writes it now.
172
+ */
168
173
  export const accessTokenKeys = {
169
174
  family: {
170
175
  name: "access-token",
@@ -178,6 +183,9 @@ export const accessTokenKeys = {
178
183
  recordPrefix: "access-token:v1:record:",
179
184
  record: (id) => validateStorageKey(`access-token:v1:record:${id}`),
180
185
  lookup: (tokenHash) => validateStorageKey(`access-token:v1:lookup:${tokenHash}`),
186
+ agent: (name) => validateStorageKey(`access-token:v1:agent:${name}`),
187
+ /** Write-once claim that a legacy token record has been taken by one agent's adoption. */
188
+ adopted: (legacyRecordId) => validateStorageKey(`access-token:v1:adopted:${legacyRecordId}`),
181
189
  active: "access-token:v1:active",
182
190
  };
183
191
  /**
package/dist/types.d.ts CHANGED
@@ -727,6 +727,11 @@ export type AuthResult = {
727
727
  sessionCookies?: readonly string[];
728
728
  /** Human owner represented by a non-interactive access credential. */
729
729
  principal?: IdentityReference;
730
+ /**
731
+ * Declared agent name. Only the built-in `accessTokens()` adapter may
732
+ * set it; a result from any other provider that carries it is refused.
733
+ */
734
+ agent?: string;
730
735
  } | {
731
736
  ok: false;
732
737
  response: Response;
@@ -747,6 +752,12 @@ export interface AuthenticatedIdentity {
747
752
  subject?: IdentityReference;
748
753
  /** Human owner of personal connector authentication. */
749
754
  principal?: IdentityReference;
755
+ /**
756
+ * Name of the code-declared agent this caller is, from `agents`. Set only
757
+ * when the built-in access-token adapter admitted a `cta_` token bound to
758
+ * a currently declared agent; an agent never has a `principal`.
759
+ */
760
+ agent?: string;
750
761
  interactive: boolean;
751
762
  }
752
763
  /** Public browser-auth configuration exposed to connecta's status UI. */
package/dist/ui.d.ts CHANGED
@@ -49,4 +49,4 @@ _nonce?: string, page?: OperatorPage): string;
49
49
  export declare function operatorUi(options?: {
50
50
  branding?: ConnectaBranding;
51
51
  }): OperatorSurface;
52
- export type { OperatorUiContract, OperatorConnectorOverlay, OperatorTool, OperatorViewer, OperatorGrant, OperatorPool, OperatorLastCall, } from "./operator-ui/contract.js";
52
+ export type { OperatorUiContract, OperatorUiContractV2, OperatorRole, OperatorMember, OperatorPoolV2, OperatorRootEndpoint, OperatorMemberConnector, OperatorMemberManage, OperatorAgent, OperatorSharedSource, OperatorRunsAs, OperatorConnectorPoolCell, OperatorConnectorOverlay, OperatorTool, OperatorGrant, OperatorLastCall, } from "./operator-ui/contract.js";
package/dist/ui.js CHANGED
@@ -4,33 +4,31 @@ import { routeCredentials } from "./routes/credentials.js";
4
4
  import { routeOAuthManagement } from "./routes/oauth-management.js";
5
5
  import { uiData } from "./routes/ui-data.js";
6
6
  import { runEdge } from "./runtime/run.js";
7
- import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH } from "./operator-ui/generated.js";
7
+ import { OPERATOR_UI_STYLE_PATH, OPERATOR_UI_SCHEME_PATH, OPERATOR_UI_SCRIPT_PATH, OPERATOR_UI_NOTICES_PATH, } from "./operator-ui/generated.js";
8
8
  export { filterUiConnectors, } from "./operator-ui/model.js";
9
9
  import { escapeHtml, isSafeHttpsUrl, renderPage, resolveBranding } from "./branding.js";
10
10
  export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
11
11
  const OPERATOR_PAGE_LABELS = {
12
- overview: "Overview",
12
+ endpoints: "Endpoints",
13
13
  connections: "Connectors",
14
14
  connector: "Connector",
15
- tools: "Tools",
16
- access: "Access",
17
- config: "Config",
18
- tokens: "Access tokens",
15
+ settings: "Settings",
16
+ tokens: "Tokens",
19
17
  activity: "Activity",
20
18
  };
21
19
  export function operatorPageForPath(path) {
22
20
  if (path === "/")
23
- return "overview";
21
+ return "endpoints";
24
22
  if (path === "/connectors")
25
23
  return "connections";
26
24
  if (/^\/connectors\/[a-z0-9_-]+$/.test(path))
27
25
  return "connector";
28
26
  if (path === "/tools")
29
- return "tools";
27
+ return "connections";
30
28
  if (path === "/access")
31
- return "access";
32
- if (path === "/config")
33
- return "config";
29
+ return "endpoints";
30
+ if (path === "/config" || path === "/settings")
31
+ return "settings";
34
32
  if (path === "/tokens")
35
33
  return "tokens";
36
34
  if (path === "/activity")
@@ -101,7 +99,7 @@ function jsonForInlineScript(value) {
101
99
  */
102
100
  export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding,
103
101
  /** @deprecated Scripts load from same-origin assets; ignored. */
104
- _nonce, page = "overview") {
102
+ _nonce, page = "endpoints") {
105
103
  const clerk = uiAuth?.kind === "clerk" ? uiAuth : undefined;
106
104
  // The Clerk loader's origin. A value that fails the gate is dropped rather
107
105
  // than escaped into the page: the loader tag is simply not emitted, the gate
@@ -130,13 +128,15 @@ _nonce, page = "overview") {
130
128
  : "";
131
129
  // The shared layout owns the head, scheme, theme, and masthead; the shell
132
130
  // adds its own bundle (which already imports the shared tokens), the nav
133
- // mount, and the app.
131
+ // mount, and the app. The scheme initializer blocks, ahead of the
132
+ // stylesheet, so a saved color scheme holds from the first paint.
134
133
  return renderPage(branding, {
135
134
  title: operatorPageTitle(page, brand.pageTitle),
136
135
  uiMounted: true,
137
136
  operatorShell: true,
138
137
  styles: "",
139
- head: `<link rel="stylesheet" href="${OPERATOR_UI_STYLE_PATH}"><link rel="license" href="${OPERATOR_UI_NOTICES_PATH}">` +
138
+ head: `<script src="${OPERATOR_UI_SCHEME_PATH}"></script>` +
139
+ `<link rel="stylesheet" href="${OPERATOR_UI_STYLE_PATH}"><link rel="license" href="${OPERATOR_UI_NOTICES_PATH}">` +
140
140
  clerkScript,
141
141
  skipTo: { id: "operatorContent", label: "Skip to operator page" },
142
142
  mastheadEnd: '<div id="operatorNav"></div>',
@@ -156,6 +156,7 @@ _nonce, page = "overview") {
156
156
  auth,
157
157
  mcpUrl,
158
158
  initialPage: page,
159
+ schemePinned: brand.theme.colorScheme !== "system",
159
160
  titleSuffix: brand.pageTitle,
160
161
  productName: brand.productName,
161
162
  productDescription: brand.description,
@@ -186,6 +187,7 @@ export function operatorUi(options = {}) {
186
187
  "/tools",
187
188
  "/access",
188
189
  "/config",
190
+ "/settings",
189
191
  "/ui",
190
192
  "/ui/*",
191
193
  "/favicon.svg",
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CONNECTA_VERSION = "0.32.0";
1
+ export declare const CONNECTA_VERSION = "0.33.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.32.0";
3
+ export const CONNECTA_VERSION = "0.33.0";
@@ -0,0 +1,9 @@
1
+ // The one rule for a Google Workspace address, shared by the Workspace
2
+ // providers' fixed `subject` and a pool's `owner` (#837, decisions/0006), so
3
+ // config accepts exactly the addresses a delegated token can be minted for.
4
+ /** A plausible mailbox: one `@`, no whitespace or control characters. */
5
+ const ADDRESS = /^[^\s@<>()",;:\\[\]]+@[^\s@<>()",;:\\[\]]+\.[^\s@<>()",;:\\[\]]+$/;
6
+ /** Whether `value`, already trimmed, is a Workspace email address. */
7
+ export function isWorkspaceAddress(value) {
8
+ return typeof value === "string" && ADDRESS.test(value);
9
+ }
@@ -0,0 +1,23 @@
1
+ // Whose account a Google Workspace connector acts as, for the operator
2
+ // matrix (#837, decisions/0006). The Workspace providers tag the connector
3
+ // they build; nothing else can, and nothing a request sends reaches this.
4
+ // Read only by the operator contract, which never shows a mapping function.
5
+ const tagged = new WeakMap();
6
+ /** Tag a Workspace connector with its subject kind, returning it unchanged. */
7
+ export function tagWorkspaceSubject(connector, subject) {
8
+ tagged.set(connector, typeof subject === "string" ? { kind: "fixed", subject } : { kind: "mapping" });
9
+ return connector;
10
+ }
11
+ /**
12
+ * The subject kind of a tagged connector, or undefined for any other. Walks
13
+ * the prototype chain, since provider construction may wrap a connector it
14
+ * cannot restamp in an object that inherits from it.
15
+ */
16
+ export function workspaceSubjectOf(connector) {
17
+ for (let current = connector; current; current = Object.getPrototypeOf(current)) {
18
+ const subject = tagged.get(current);
19
+ if (subject)
20
+ return subject;
21
+ }
22
+ return undefined;
23
+ }
@@ -1,34 +1,58 @@
1
1
  # Agent documentation
2
2
 
3
- Choose a route by the work you are doing. Each route identifies the task guide
4
- and contracts needed to operate an endpoint, set up a deployment, connect a
5
- service, or change Connecta itself.
3
+ Start here when operating a deployment installed with `@zackbart/connecta`.
4
+ All operator procedures below are available in the installed package; paths in
5
+ this directory are relative to `node_modules/@zackbart/connecta/documentation/`.
6
+ For an existing endpoint, start with [Operating an endpoint](./operating.md).
7
+ For a new deployment, start with [Deploying Connecta](./deploying.md).
6
8
 
7
- | Your task | Start here | Read next when needed |
8
- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
9
- | Use an existing endpoint to answer a question or perform a task | [Operating an endpoint](./operating.md) | [Meta-tools](./meta-tools.md), [guest API](./code-mode.md) |
10
- | Set up, configure, upgrade, or diagnose a deployment | [Deploying Connecta](./deploying.md) | [Auth](./auth.md), [operator UI](./operator-ui.md) |
11
- | Add a maintained integration, remote MCP, or HTTP API to a deployment | [Integrating services](./integrating.md) | Provider skill, [classification](./architecture.md#providers-and-reviewed-classification), [downstream auth](./auth.md#shared-and-personal-auth) |
12
- | Change Connecta's source, tests, or published contracts | [AGENTS.md](https://github.com/zackbart/connecta/blob/main/AGENTS.md) | [Principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md), [architecture](./architecture.md), then the affected contract |
9
+ ## Operator tasks
10
+
11
+ | Task | Self-contained procedure |
12
+ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
13
+ | Add or remove a connector | [Change the connector roster](./integrating.md#add-or-remove-a-connector) |
14
+ | Organize an agent's pools | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools) |
15
+ | Add a person (maintainer or pool member) | [Add a person](./auth.md#add-a-person), [member page](./operator-ui.md#use-the-member-page) |
16
+ | Connect a shared credential | [Connect a shared credential](./auth.md#connect-a-shared-credential) |
17
+ | Issue, rotate, or revoke an agent token | [Manage an agent token](./auth.md#issue-rotate-or-revoke-an-agent-token) |
18
+ | Upgrade between versions | [Upgrade an existing deployment](./deploying.md#upgrade-an-existing-deployment), including [migration to 0.33](./migration-0.33.md) |
19
+ | Diagnose with `connecta doctor` | [Verify and diagnose](./deploying.md#verify-and-diagnose) |
20
+
21
+ ## Audience rule
22
+
23
+ Shipped docs (`documentation/`, `templates/`, `examples/worker/`, and the root
24
+ `README.md`) are written first for an agent operating a deployment from the
25
+ installed package. Operator conventions must be self-contained rules plus a
26
+ neutral example. Decision records and issues explain why; no operator task
27
+ requires them, `spec/`, `.claude/`, or the Connecta source checkout. Those
28
+ three directories do not ship. Contributor source maps, INV citations, and
29
+ implementation/test evidence belong under clearly labeled contributor
30
+ headings and are optional for operators. Use `assistant`, `alice`, `bob`,
31
+ `crm`, `tickets`, `notes_alice`, and `alice@example.com` in examples; never
32
+ real deployment, client, person, or organization names. Maintained provider
33
+ product names stay.
13
34
 
14
35
  ## Contract reference
15
36
 
16
- | Guide | Owns |
17
- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
18
- | [Meta-tools](./meta-tools.md) | Six top-level tools, discovery, direct-call results, paging, skills, and recovery |
19
- | [Code mode](./code-mode.md) | JavaScript guest API, executor requirements, output, retries, cancellation, limits, and writes |
20
- | [Auth](./auth.md) | Inbound identity, grants, pools, machine tokens, credential ownership, and downstream OAuth |
21
- | [Architecture](./architecture.md) | Request lifetimes, routes, storage, configuration, import boundaries, and Effect implementation |
22
- | [Operator UI](./operator-ui.md) | Optional UI setup, scoped configuration and live-data contract, activity disclosure, and UI implementation |
23
- | [Operator UI tests](./operator-tests.md) | Browser state matrix, real-server flows, and snapshot maintenance |
24
- | [Provider migration for 0.29](./provider-migration-0.29.md) | Upgrade actions for Notion, Vercel, and Cloudflare; provider reconciliation tables supply exact name mappings |
25
- | [Provider auth migration](./provider-auth-migration.md) | Upgrade actions when `auth` selects hosted MCP or Connecta's REST connector, Stripe and Vercel |
37
+ | Guide | Owns |
38
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
39
+ | [Meta-tools](./meta-tools.md) | Six top-level tools, discovery, direct-call results, paging, skills, and recovery |
40
+ | [Code mode](./code-mode.md) | JavaScript guest API, executor requirements, output, retries, cancellation, limits, and writes |
41
+ | [Auth](./auth.md) | Inbound identity, maintainers, pools and members, agents, credential ownership, and downstream OAuth |
42
+ | [Architecture (contributor reference)](./architecture.md) | Request lifetimes, routes, storage, configuration, import boundaries, and Effect implementation |
43
+ | [Operator UI](./operator-ui.md) | Optional UI setup, scoped configuration and live-data contract, activity disclosure, and UI implementation |
44
+ | [Operator UI tests (contributor reference)](./operator-tests.md) | Browser state matrix, real-server flows, and snapshot maintenance |
45
+ | [Migration to 0.33](./migration-0.33.md) | Upgrade actions from 0.32 options to maintainers, members, agents, agent endpoints, and pool owners |
46
+ | [Provider migration for 0.29](./provider-migration-0.29.md) | Upgrade actions for Notion, Vercel, and Cloudflare; provider reconciliation tables supply exact name mappings |
47
+ | [Provider auth migration](./provider-auth-migration.md) | Upgrade actions when `auth` selects hosted MCP or Connecta's REST connector, Stripe and Vercel |
26
48
 
27
49
  Each long contract starts with task routes. Follow those section links before
28
- reading the whole guide. Source and test pointers identify where to check a
29
- claim or implement a change.
50
+ reading the whole guide. Contributor references at the end are optional
51
+ implementation evidence.
52
+
53
+ ## Contributor reference
30
54
 
31
- ## Documentation ownership
55
+ ### Documentation ownership
32
56
 
33
57
  The root [README](../README.md) explains the product for people. It owns what
34
58
  Connecta offers, how the pieces connect, deployment choices, and the setup