@zackbart/connecta 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +855 -5
- package/README.md +125 -116
- package/bin/connecta.mjs +121 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/argument-redaction.js +230 -0
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +288 -0
- package/dist/authorization-handoff.js +111 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +409 -225
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +633 -0
- package/dist/config.js +508 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +87 -38
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +128 -0
- package/dist/downstream-input.js +325 -0
- package/dist/errors.d.ts +132 -31
- package/dist/errors.js +175 -87
- package/dist/execute.js +798 -318
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +30 -13
- package/dist/executors/quickjs-child.js +15 -9
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +152 -59
- package/dist/executors/quickjs.js +49 -39
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +238 -0
- package/dist/guest-types.d.ts +218 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +262 -79
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +61 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +419 -272
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +194 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-result.js +5 -0
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +60 -19
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +110 -171
- package/dist/registry.js +588 -927
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +103 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +365 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +93 -0
- package/dist/sent-secrets.js +1013 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +416 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +181 -0
- package/dist/storage/keys.js +443 -0
- package/dist/storage/memory.js +16 -4
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +543 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +336 -56
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +146 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +435 -0
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +696 -231
- package/documentation/auth.md +1069 -546
- package/documentation/code-mode.md +662 -356
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +570 -216
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +97 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +65 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +293 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +126 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +140 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
package/dist/storage/file.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { KVStorage, Logger } from "../types.js";
|
|
2
|
-
export interface FileStorageOptions {
|
|
3
|
-
/** Destination for the corrupt-state-file recovery report. Default console. */
|
|
4
|
-
logger?: Logger;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* JSON-file-backed KVStorage for Node. Loads once, persists on every write via
|
|
8
|
-
* an exclusive temp-file + rename. Refuses a second holder of the same path.
|
|
9
|
-
* Call close() when finished to release its lock; process exit also releases it.
|
|
10
|
-
* Only reachable via the "@zackbart/connecta/node"
|
|
11
|
-
* subpath so the main entry stays Workers-clean.
|
|
12
|
-
*/
|
|
13
|
-
export declare function fileStorage(path: string, opts?: FileStorageOptions): KVStorage & {
|
|
14
|
-
close(): void;
|
|
15
|
-
};
|
package/dist/storage/file.js
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
import { chmodSync, closeSync, existsSync, futimesSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync, } from "node:fs";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { hostname } from "node:os";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
5
|
-
function validEntry(value) {
|
|
6
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) &&
|
|
7
|
-
"value" in value && typeof value.value === "string" &&
|
|
8
|
-
(!("exp" in value) || (typeof value.exp === "number" && Number.isFinite(value.exp)));
|
|
9
|
-
}
|
|
10
|
-
function makeEntry(value, ttlSeconds) {
|
|
11
|
-
if (ttlSeconds !== undefined && !Number.isFinite(ttlSeconds)) {
|
|
12
|
-
throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
|
|
13
|
-
}
|
|
14
|
-
if (!ttlSeconds)
|
|
15
|
-
return { value };
|
|
16
|
-
const exp = Date.now() + ttlSeconds * 1000;
|
|
17
|
-
if (!Number.isFinite(exp)) {
|
|
18
|
-
throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
|
|
19
|
-
}
|
|
20
|
-
return { value, exp };
|
|
21
|
-
}
|
|
22
|
-
const HEARTBEAT_MS = 15_000;
|
|
23
|
-
const STALE_LOCK_MS = 60_000;
|
|
24
|
-
const localLocks = new Map();
|
|
25
|
-
// A pid is meaningful only in its own host/namespace. Container hostnames may
|
|
26
|
-
// be shared, so Linux uses the kernel boot id and the actual PID namespace.
|
|
27
|
-
const pidScope = (() => {
|
|
28
|
-
if (process.platform !== "linux")
|
|
29
|
-
return hostname();
|
|
30
|
-
try {
|
|
31
|
-
return `${readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim()}:${readlinkSync("/proc/self/ns/pid")}`;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return undefined; // Without namespace evidence, rely on the heartbeat.
|
|
35
|
-
}
|
|
36
|
-
})();
|
|
37
|
-
function stale(path) {
|
|
38
|
-
return Date.now() - statSync(path).mtimeMs > STALE_LOCK_MS;
|
|
39
|
-
}
|
|
40
|
-
// One listener per module, removed when the last store closes. Node's listen()
|
|
41
|
-
// drains requests before process.exit(), so the lock lasts through those writes.
|
|
42
|
-
const openStores = new Set();
|
|
43
|
-
const closeStores = () => {
|
|
44
|
-
for (const close of openStores) {
|
|
45
|
-
try {
|
|
46
|
-
close();
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// A dead-pid or expired-heartbeat lock can be reclaimed next startup.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
function hasCode(error, code) {
|
|
54
|
-
return error?.code === code;
|
|
55
|
-
}
|
|
56
|
-
function lockFile(path) {
|
|
57
|
-
const lockPath = `${path}.lock`;
|
|
58
|
-
const contents = JSON.stringify({
|
|
59
|
-
pid: process.pid,
|
|
60
|
-
pidScope,
|
|
61
|
-
createdAt: Date.now(),
|
|
62
|
-
id: randomUUID(),
|
|
63
|
-
});
|
|
64
|
-
const readLock = () => {
|
|
65
|
-
try {
|
|
66
|
-
return readFileSync(lockPath, "utf8");
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (hasCode(error, "ENOENT"))
|
|
70
|
-
return null;
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
const refuseLiveHolder = () => {
|
|
75
|
-
const raw = readLock();
|
|
76
|
-
if (raw === null)
|
|
77
|
-
return false;
|
|
78
|
-
try {
|
|
79
|
-
if (stale(lockPath))
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
if (hasCode(error, "ENOENT"))
|
|
84
|
-
return false;
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
let holder;
|
|
88
|
-
try {
|
|
89
|
-
holder = JSON.parse(raw);
|
|
90
|
-
if (!holder || !Number.isInteger(holder.pid) || holder.pid <= 0 ||
|
|
91
|
-
holder.pid > 2147483647 || !Number.isFinite(holder.createdAt)) {
|
|
92
|
-
throw new Error("Invalid lock holder");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
throw new Error(`[connecta] state file ${path} has an unreadable lock at ${lockPath}. ` +
|
|
97
|
-
`Refusing to start. Retry after its heartbeat has been stale for 60 seconds.`);
|
|
98
|
-
}
|
|
99
|
-
if (pidScope !== undefined && holder.pidScope === pidScope) {
|
|
100
|
-
if (holder.pid === process.pid) {
|
|
101
|
-
// A new container process may inherit the old holder's pid. Only our
|
|
102
|
-
// own registry can distinguish that incarnation from a second opener.
|
|
103
|
-
if (localLocks.get(path) !== raw)
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
try {
|
|
108
|
-
process.kill(holder.pid, 0);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
if (hasCode(error, "ESRCH"))
|
|
112
|
-
return true;
|
|
113
|
-
// Permission errors do not establish that the holder is dead.
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
throw new Error(`[connecta] state file ${path} is held by pid ${holder.pid} ` +
|
|
118
|
-
`(lock timestamp ${holder.createdAt}). Close that store before opening another.`);
|
|
119
|
-
};
|
|
120
|
-
const acquire = () => {
|
|
121
|
-
let fd;
|
|
122
|
-
try {
|
|
123
|
-
fd = openSync(lockPath, "wx", 0o600);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
if (hasCode(error, "EEXIST"))
|
|
127
|
-
return false;
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
writeFileSync(fd, contents);
|
|
132
|
-
const now = new Date();
|
|
133
|
-
futimesSync(fd, now, now);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
unlinkSync(lockPath);
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
closeSync(fd);
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
};
|
|
144
|
-
if (!acquire()) {
|
|
145
|
-
refuseLiveHolder();
|
|
146
|
-
// Serialize stale-lock removal. Without this guard, two reclaimers could
|
|
147
|
-
// both observe the dead pid and the slower one unlink the new live lock.
|
|
148
|
-
// Recovery is synchronous. A guard older than the lease is from a crashed
|
|
149
|
-
// or paused reclaimer, which must recheck its ownership before continuing.
|
|
150
|
-
const reclaimPath = `${lockPath}.reclaim`;
|
|
151
|
-
const recoveryError = () => new Error(`[connecta] state file ${path} has a lock recovery in progress at ${reclaimPath}. ` +
|
|
152
|
-
`Retry after its 60-second lease expires.`);
|
|
153
|
-
try {
|
|
154
|
-
const expired = statSync(reclaimPath);
|
|
155
|
-
if (Date.now() - expired.mtimeMs > STALE_LOCK_MS) {
|
|
156
|
-
const markers = readdirSync(reclaimPath);
|
|
157
|
-
const current = statSync(reclaimPath);
|
|
158
|
-
if (current.dev !== expired.dev || current.ino !== expired.ino ||
|
|
159
|
-
current.birthtimeMs !== expired.birthtimeMs || !stale(reclaimPath)) {
|
|
160
|
-
throw recoveryError();
|
|
161
|
-
}
|
|
162
|
-
// Delete only the expired owner's unique marker. A competing cleanup
|
|
163
|
-
// cannot empty a replacement guard by deleting these old filenames.
|
|
164
|
-
for (const marker of markers)
|
|
165
|
-
rmSync(`${reclaimPath}/${marker}`, { force: true });
|
|
166
|
-
rmdirSync(reclaimPath);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
if (hasCode(error, "ENOTEMPTY") || hasCode(error, "EEXIST"))
|
|
171
|
-
throw recoveryError();
|
|
172
|
-
if (!hasCode(error, "ENOENT"))
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
mkdirSync(reclaimPath, { mode: 0o700 });
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
if (!hasCode(error, "EEXIST"))
|
|
180
|
-
throw error;
|
|
181
|
-
throw recoveryError();
|
|
182
|
-
}
|
|
183
|
-
const guard = statSync(reclaimPath);
|
|
184
|
-
const markerPath = `${reclaimPath}/${randomUUID()}`;
|
|
185
|
-
const ownsGuard = () => {
|
|
186
|
-
try {
|
|
187
|
-
const current = statSync(reclaimPath);
|
|
188
|
-
return current.dev === guard.dev && current.ino === guard.ino &&
|
|
189
|
-
existsSync(markerPath);
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
if (hasCode(error, "ENOENT"))
|
|
193
|
-
return false;
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
const assertGuard = () => {
|
|
198
|
-
if (!ownsGuard() || stale(reclaimPath))
|
|
199
|
-
throw recoveryError();
|
|
200
|
-
};
|
|
201
|
-
try {
|
|
202
|
-
writeFileSync(markerPath, "", { flag: "wx", mode: 0o600 });
|
|
203
|
-
if (!ownsGuard())
|
|
204
|
-
throw recoveryError();
|
|
205
|
-
const now = new Date();
|
|
206
|
-
utimesSync(reclaimPath, now, now);
|
|
207
|
-
const removeStale = refuseLiveHolder();
|
|
208
|
-
assertGuard();
|
|
209
|
-
if (removeStale)
|
|
210
|
-
unlinkSync(lockPath);
|
|
211
|
-
if (!acquire()) {
|
|
212
|
-
refuseLiveHolder();
|
|
213
|
-
throw new Error(`[connecta] state file ${path} lock changed during recovery. Retry opening it.`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
const owned = ownsGuard();
|
|
218
|
-
rmSync(markerPath, { force: true });
|
|
219
|
-
if (owned)
|
|
220
|
-
rmdirSync(reclaimPath);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const assertHeld = () => {
|
|
224
|
-
if (readLock() !== contents) {
|
|
225
|
-
throw new Error(`[connecta] state file ${path} lock was lost. Refusing to write a stale snapshot.`);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
localLocks.set(path, contents);
|
|
229
|
-
const heartbeat = setInterval(() => {
|
|
230
|
-
try {
|
|
231
|
-
assertHeld();
|
|
232
|
-
const now = new Date();
|
|
233
|
-
utimesSync(lockPath, now, now);
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
// A replaced lock belongs to its new holder. Failed refreshes let the
|
|
237
|
-
// lease expire; subsequent writes still have to prove ownership.
|
|
238
|
-
clearInterval(heartbeat);
|
|
239
|
-
}
|
|
240
|
-
}, HEARTBEAT_MS);
|
|
241
|
-
heartbeat.unref();
|
|
242
|
-
return {
|
|
243
|
-
assertHeld,
|
|
244
|
-
release() {
|
|
245
|
-
clearInterval(heartbeat);
|
|
246
|
-
if (localLocks.get(path) === contents)
|
|
247
|
-
localLocks.delete(path);
|
|
248
|
-
if (readLock() === contents)
|
|
249
|
-
unlinkSync(lockPath);
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* JSON-file-backed KVStorage for Node. Loads once, persists on every write via
|
|
255
|
-
* an exclusive temp-file + rename. Refuses a second holder of the same path.
|
|
256
|
-
* Call close() when finished to release its lock; process exit also releases it.
|
|
257
|
-
* Only reachable via the "@zackbart/connecta/node"
|
|
258
|
-
* subpath so the main entry stays Workers-clean.
|
|
259
|
-
*/
|
|
260
|
-
export function fileStorage(path, opts = {}) {
|
|
261
|
-
path = resolve(path);
|
|
262
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
263
|
-
const lock = lockFile(path);
|
|
264
|
-
let closed = false;
|
|
265
|
-
const close = () => {
|
|
266
|
-
if (closed)
|
|
267
|
-
return;
|
|
268
|
-
closed = true;
|
|
269
|
-
openStores.delete(close);
|
|
270
|
-
if (!openStores.size)
|
|
271
|
-
process.removeListener("exit", closeStores);
|
|
272
|
-
lock.release();
|
|
273
|
-
};
|
|
274
|
-
const assertOpen = () => {
|
|
275
|
-
if (closed)
|
|
276
|
-
throw new Error(`[connecta] state file ${path} is closed.`);
|
|
277
|
-
};
|
|
278
|
-
const logger = opts.logger ?? console;
|
|
279
|
-
// With a sealing vault, downstream OAuth tokens here are ciphertext; without
|
|
280
|
-
// one they are cleartext, and OAuth flow state is plaintext either way. Keep
|
|
281
|
-
// the file owner-only regardless. Repair is best-effort: chmod is a no-op or throws on
|
|
282
|
-
// non-POSIX filesystems, and a loose mode must never keep the store from
|
|
283
|
-
// starting.
|
|
284
|
-
const tighten = () => {
|
|
285
|
-
try {
|
|
286
|
-
chmodSync(path, 0o600);
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
// Non-POSIX filesystem or a race on the file — leave the mode as-is.
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
let data = Object.create(null);
|
|
293
|
-
try {
|
|
294
|
-
if (existsSync(path)) {
|
|
295
|
-
tighten();
|
|
296
|
-
try {
|
|
297
|
-
const loaded = JSON.parse(readFileSync(path, "utf8"));
|
|
298
|
-
if (loaded === null || typeof loaded !== "object" || Array.isArray(loaded)) {
|
|
299
|
-
throw new TypeError("Expected a state object");
|
|
300
|
-
}
|
|
301
|
-
if (!Object.values(loaded).every(validEntry)) {
|
|
302
|
-
throw new TypeError("Expected string values and finite expiry timestamps");
|
|
303
|
-
}
|
|
304
|
-
data = Object.assign(Object.create(null), loaded);
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
// Never let a damaged state file be silently replaced by an empty one:
|
|
308
|
-
// the next set() would persist {} over irreplaceable downstream OAuth
|
|
309
|
-
// tokens and credential-vault entries. Quarantine the bytes so they
|
|
310
|
-
// survive for manual recovery, and refuse to start if even that fails —
|
|
311
|
-
// losing the file loudly beats losing it quietly.
|
|
312
|
-
const quarantine = `${path}.corrupt-${Date.now()}`;
|
|
313
|
-
try {
|
|
314
|
-
renameSync(path, quarantine);
|
|
315
|
-
}
|
|
316
|
-
catch (renameError) {
|
|
317
|
-
throw new Error(`[connecta] state file ${path} is not valid JSON and could not be ` +
|
|
318
|
-
`moved aside (${String(renameError)}). Refusing to start rather ` +
|
|
319
|
-
`than overwrite it. Move or repair the file, then restart.`);
|
|
320
|
-
}
|
|
321
|
-
logger.error(`[connecta] state file ${path} is not valid JSON ` +
|
|
322
|
-
`(${error instanceof Error ? error.message : String(error)}) — ` +
|
|
323
|
-
`moved to ${quarantine}, starting from empty state. Downstream ` +
|
|
324
|
-
`OAuth connectors must be re-authorized and stored credentials ` +
|
|
325
|
-
`re-entered.`);
|
|
326
|
-
data = Object.create(null);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
close();
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
if (!openStores.size)
|
|
335
|
-
process.on("exit", closeStores);
|
|
336
|
-
openStores.add(close);
|
|
337
|
-
const persist = () => {
|
|
338
|
-
// Physical expiry rides on an operation that was already going to write.
|
|
339
|
-
// Reads only prune memory; they do not rewrite the state file.
|
|
340
|
-
const now = Date.now();
|
|
341
|
-
for (const [key, entry] of Object.entries(data)) {
|
|
342
|
-
if (entry.exp !== undefined && now >= entry.exp)
|
|
343
|
-
delete data[key];
|
|
344
|
-
}
|
|
345
|
-
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
346
|
-
// 0o600 on the tmp file; the atomic rename below preserves it, so the live
|
|
347
|
-
// state file is never briefly world-readable.
|
|
348
|
-
const fd = openSync(tmp, "wx", 0o600);
|
|
349
|
-
let committed = false;
|
|
350
|
-
try {
|
|
351
|
-
try {
|
|
352
|
-
writeFileSync(fd, JSON.stringify(data));
|
|
353
|
-
}
|
|
354
|
-
finally {
|
|
355
|
-
closeSync(fd);
|
|
356
|
-
}
|
|
357
|
-
lock.assertHeld();
|
|
358
|
-
renameSync(tmp, path);
|
|
359
|
-
committed = true;
|
|
360
|
-
}
|
|
361
|
-
finally {
|
|
362
|
-
// A successful rename already removed the temp path. Cleanup must not
|
|
363
|
-
// turn a committed snapshot into a failure that callers roll back.
|
|
364
|
-
if (!committed)
|
|
365
|
-
rmSync(tmp, { force: true });
|
|
366
|
-
}
|
|
367
|
-
tighten();
|
|
368
|
-
};
|
|
369
|
-
const fresh = (key) => {
|
|
370
|
-
const e = data[key];
|
|
371
|
-
if (!e)
|
|
372
|
-
return null;
|
|
373
|
-
if (e.exp !== undefined && Date.now() >= e.exp) {
|
|
374
|
-
delete data[key];
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
return e;
|
|
378
|
-
};
|
|
379
|
-
return {
|
|
380
|
-
close,
|
|
381
|
-
async get(key) {
|
|
382
|
-
// Reads use the loaded snapshot; only writes need filesystem ownership
|
|
383
|
-
// checks to prevent a reclaimed holder from overwriting newer state.
|
|
384
|
-
assertOpen();
|
|
385
|
-
return fresh(key)?.value ?? null;
|
|
386
|
-
},
|
|
387
|
-
async set(key, value, opts) {
|
|
388
|
-
assertOpen();
|
|
389
|
-
lock.assertHeld();
|
|
390
|
-
const previous = fresh(key);
|
|
391
|
-
data[key] = makeEntry(value, opts?.ttlSeconds);
|
|
392
|
-
try {
|
|
393
|
-
persist();
|
|
394
|
-
}
|
|
395
|
-
catch (error) {
|
|
396
|
-
if (previous)
|
|
397
|
-
data[key] = previous;
|
|
398
|
-
else
|
|
399
|
-
delete data[key];
|
|
400
|
-
throw error;
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
async delete(key) {
|
|
404
|
-
assertOpen();
|
|
405
|
-
lock.assertHeld();
|
|
406
|
-
const previous = fresh(key);
|
|
407
|
-
delete data[key];
|
|
408
|
-
try {
|
|
409
|
-
persist();
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
if (previous)
|
|
413
|
-
data[key] = previous;
|
|
414
|
-
throw error;
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
async list(prefix) {
|
|
418
|
-
assertOpen();
|
|
419
|
-
return Object.keys(data)
|
|
420
|
-
.filter((key) => Boolean(fresh(key)) && key.startsWith(prefix))
|
|
421
|
-
.sort();
|
|
422
|
-
},
|
|
423
|
-
// Atomic twice over: the lock makes this process the only writer of the
|
|
424
|
-
// file, and nothing between the comparison and the persisted rename
|
|
425
|
-
// yields, so no other call on this store can interleave. A write that
|
|
426
|
-
// cannot be persisted is rolled back so memory never claims a value the
|
|
427
|
-
// file does not hold.
|
|
428
|
-
async compareAndSet(key, expected, next, opts) {
|
|
429
|
-
assertOpen();
|
|
430
|
-
lock.assertHeld();
|
|
431
|
-
const previous = fresh(key);
|
|
432
|
-
if ((previous?.value ?? null) !== expected)
|
|
433
|
-
return false;
|
|
434
|
-
if (next === null) {
|
|
435
|
-
if (!previous)
|
|
436
|
-
return true;
|
|
437
|
-
delete data[key];
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
data[key] = makeEntry(next, opts?.ttlSeconds);
|
|
441
|
-
}
|
|
442
|
-
try {
|
|
443
|
-
persist();
|
|
444
|
-
}
|
|
445
|
-
catch (error) {
|
|
446
|
-
if (previous)
|
|
447
|
-
data[key] = previous;
|
|
448
|
-
else
|
|
449
|
-
delete data[key];
|
|
450
|
-
throw error;
|
|
451
|
-
}
|
|
452
|
-
return true;
|
|
453
|
-
},
|
|
454
|
-
};
|
|
455
|
-
}
|
package/ethos.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# connecta — ethos
|
|
2
|
-
|
|
3
|
-
Connecta’s scope, refusals, and invariants. Contradictions require a design decision.
|
|
4
|
-
|
|
5
|
-
## What this is
|
|
6
|
-
|
|
7
|
-
- **One MCP endpoint.** Agents reach configured connectors through JavaScript,
|
|
8
|
-
a required executor, and seven meta-tools.
|
|
9
|
-
- **Config-as-code.** One tenant and connector set; identities get
|
|
10
|
-
config-derived views. Maintained providers, `remoteMcp()`, and `api()` share
|
|
11
|
-
one connector contract.
|
|
12
|
-
- **Safe by default.** Programs run only read-only or config-exempt tools;
|
|
13
|
-
every other write crosses `call_destructive_tool`. The host owns approval.
|
|
14
|
-
- **One fetch-native core.** Web APIs on Node and Workers; platform code and
|
|
15
|
-
optional features behind explicit subpaths; Effect inside, Promises outside.
|
|
16
|
-
- **Human auth management.** Optional pages show status and payload-free history.
|
|
17
|
-
Explicit permissions allow credential changes and OAuth. Capabilities stay in code.
|
|
18
|
-
|
|
19
|
-
## What this isn't
|
|
20
|
-
|
|
21
|
-
No runtime registration, admin-editable capabilities, or policy engine; config
|
|
22
|
-
may only exempt a tool from asking. No schema ingestion, accounts, or groups;
|
|
23
|
-
personal state remains within one tenant. Breaking changes remain acceptable;
|
|
24
|
-
version numbers signal change, not stability.
|
|
25
|
-
|
|
26
|
-
## Decisions
|
|
27
|
-
|
|
28
|
-
Revisiting a verdict requires a new argument. Accepted designs live in
|
|
29
|
-
subsystem guides and the CHANGELOG; `planned` ones bind review before either
|
|
30
|
-
carries them.
|
|
31
|
-
|
|
32
|
-
| Decision | Verdict | Why |
|
|
33
|
-
| --- | --- | --- |
|
|
34
|
-
| OpenAPI / GraphQL ingestion | refused | the disease is a document-authored tool nobody chose; hand-written literals, even through a shared factory, are still authorship |
|
|
35
|
-
| Multi-tenancy / account model | refused | one deployment per tenant; inbound auth owns identity |
|
|
36
|
-
| Approvals and pauses | removed | hosts approve per call, so under always-allow the resume prompt was a rubber stamp that cost a round trip and a replay ([#672](https://github.com/zackbart/connecta/issues/672), superseding [#565](https://github.com/zackbart/connecta/issues/565)) |
|
|
37
|
-
| Approval exemptions | accepted | config-only, program-only, never read-only elsewhere ([#566](https://github.com/zackbart/connecta/issues/566)) |
|
|
38
|
-
| Runtime connector registration | refused | config-as-code is the security model |
|
|
39
|
-
| Optional deployment modules | accepted | typed slots select UI, activity, vault, and inbound auth; core keeps discovery, execution, invocation, and enforcement |
|
|
40
|
-
| Artifacts module | accepted | a built-in connector for team-only sandboxed pages over stored JSON; immutable versions let writes skip approval. Supersedes [#287](https://github.com/zackbart/connecta/issues/287) |
|
|
41
|
-
| Plugin lifecycle, provider registry, or marketplace | refused | modules are deployment code, not runtime installs; prebuilt connections are imports, discovered in docs ([#297](https://github.com/zackbart/connecta/issues/297)) |
|
|
42
|
-
| OAuth on `api()` | accepted | OAuth-only APIs; static grants reuse `remoteMcp()` machinery, core's callback route |
|
|
43
|
-
| Connecta-issued access tokens | accepted | optional /auth/access-tokens preserves v0.23 secrets; config owns grants, interactive management needs explicit permission ([#619](https://github.com/zackbart/connecta/issues/619)) |
|
|
44
|
-
| Expanded Notion page create/update options | refused | different workflows, not missing fields; use `api()` ([#408](https://github.com/zackbart/connecta/issues/408)) |
|
|
45
|
-
| Resources, prompts, and downstream MCP Apps templates | refused | tools only; clients own presentation ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
46
|
-
| Protocol sessions, server push, elicitation passthrough | refused | stateless per request; elicitation has no route |
|
|
47
|
-
| Repository formatter | refused | style is authored, not enforced |
|
|
48
|
-
| Host-side projection or paging of program results | refused | a program projects: heuristics drop fields invisibly, paging rewards unprojected returns ([#223](https://github.com/zackbart/connecta/issues/223)) |
|
|
49
|
-
| Native Tasks for oversized results | refused | tasks solve duration, `get_result` solves size ([#176](https://github.com/zackbart/connecta/issues/176)) |
|
|
50
|
-
| Widening the `Executor` result contract | refused | `{ result, error?, logs? }` is the `@cloudflare/codemode` parity guarantee ([#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
51
|
-
| Erasable TypeScript in `execute_code` | refused | a 24 MB core dependency, no measured agent benefit ([#419](https://github.com/zackbart/connecta/issues/419)) |
|
|
52
|
-
| Guest-minted `resource` / `resource_link` blocks | refused | no program mints a URI a client may dereference ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
53
|
-
| Provenance tracking for emitted content | refused | everything a program emits is program output ([#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
54
|
-
| Result sampling on the catalog surface | refused | sampling is execution, not a catalog read ([#282](https://github.com/zackbart/connecta/issues/282)) |
|
|
55
|
-
| Legacy embedded `UIResource` delivery | refused | superseded upstream, rendered by no client we face ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
56
|
-
| Effect as the core effect system | accepted | lifetimes held the bugs: the rewrite closed leaked permits, hung waits, and cross-request races. Overturns [#470](https://github.com/zackbart/connecta/issues/470) |
|
|
57
|
-
| Shared bounded queue under both admission controllers | refused | built: −17 lines bought a hook-parameterised abstraction ([#453](https://github.com/zackbart/connecta/issues/453)) |
|
|
58
|
-
| MCP Apps rendering and `connecta.ui` | removed | clients render returned data |
|
|
59
|
-
| Connector shortcut globals | removed | canonical addresses need no sanitization |
|
|
60
|
-
| `connecta.batch` | removed | JavaScript promises suffice |
|
|
61
|
-
| Automatic direct-call retries | removed | callers own retry timing |
|
|
62
|
-
| Connector HTTP routes | removed | deployments own custom routes; the operator UI's artifact viewer is the exception |
|
|
63
|
-
| Caller-selected toolkits | removed | config derives every view, grant-gated `/mcp/<pool>` pools included ([#178](https://github.com/zackbart/connecta/issues/178), [#531](https://github.com/zackbart/connecta/issues/531)) |
|
|
64
|
-
| Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
|
|
65
|
-
| Classic (executor-free) surface | removed | an executor is mandatory ([#273](https://github.com/zackbart/connecta/issues/273)) |
|
|
66
|
-
| Per-result lexical query coverage | removed | failed a precommitted response-bytes gate ([#323](https://github.com/zackbart/connecta/issues/323)) |
|
|
67
|
-
| Stabilized workflows | accepted | only as artifact refresh: deployment-triggered read-only programs, shared credentials ([#225](https://github.com/zackbart/connecta/issues/225), [#564](https://github.com/zackbart/connecta/issues/564)) |
|
|
68
|
-
| Semantic tool search | gated | keyword search has not been shown to fail ([#27](https://github.com/zackbart/connecta/issues/27)) |
|
|
69
|
-
| MRTR / `input_required` passthrough | gated | relayable statelessly; no host or downstream emits it yet ([#176](https://github.com/zackbart/connecta/issues/176)) |
|
|
70
|
-
| Downstream `ttlMs` cache hints | gated | needs refresh-churn evidence ([#206](https://github.com/zackbart/connecta/issues/206)) |
|
|
71
|
-
| Worker Access inbound auth | provisional | Managed OAuth and Clerk migration need production evidence ([#506](https://github.com/zackbart/connecta/issues/506)) |
|
|
72
|
-
| Program UI tool calls | removed | duplicated calls without improving retrieval ([#287](https://github.com/zackbart/connecta/issues/287), [#484](https://github.com/zackbart/connecta/issues/484)) |
|
|
73
|
-
|
|
74
|
-
## Invariants
|
|
75
|
-
|
|
76
|
-
Tests beside subsystem documentation enforce these.
|
|
77
|
-
|
|
78
|
-
- **Fail-closed read-only.** A missing, false, or contradictory annotation never gets the benefit of the doubt.
|
|
79
|
-
- **Generated code cannot mint capabilities.** Admission, credentials, and classification are enforced below the sandbox.
|
|
80
|
-
- **Only explicitly read-only or config-exempt work runs in the sandbox.** Every other write crosses `call_destructive_tool`.
|
|
81
|
-
- **Nothing request-bound survives a request.** No transport, stream, signal, or awaited promise outlives it.
|
|
82
|
-
- **A downstream catalog is complete or it is a failure.** A partial one is never cached, persisted, or served.
|
|
83
|
-
- **Activity is payload-free by construction.** The event type has nowhere to put arguments, results, code, or raw errors.
|
|
84
|
-
- **An observed shape is never a declaration.** Names and broad types only, labeled, and gone behind any declared schema.
|
|
85
|
-
- **Credentials never leave the host.** Encrypted at rest, readable only by the owning connector (and principal, for personal auth), rendered by nothing.
|
|
86
|
-
- **Import-graph purity.** Nothing reachable from the root entry imports a `node:` builtin or `effect/testing`.
|
|
87
|
-
- **The published surface is a boundary.** Heavyweight or platform-bound code goes behind an optional-peer subpath; Effect is the one hard dependency, named by no published type.
|
|
88
|
-
- **Human routes manage auth, never capability.** Visibility grants use, not administration. Auth mutations need separate config-derived permissions, denied by default; activity reads its own. Artifact viewing respects connector grants and cannot invoke tools.
|
|
89
|
-
- **Omitted modules do no work.** Core imports no UI bundle, vault, activity, artifacts, or bearer implementation. OAuth callbacks work without UI.
|
|
90
|
-
- **Status reads do not start authorization.** Only an explicit authorized action starts OAuth, never a page load.
|
|
91
|
-
- **Structural mistakes throw at construction.** Booting into the wrong shape is worse than not booting.
|
|
92
|
-
|
|
93
|
-
Connecta simplifies [executor](https://github.com/UsefulSoftwareCo/executor).
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { KVStorage } from "@zackbart/connecta";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* KVStorage backed by a Cloudflare Workers KV namespace binding.
|
|
5
|
-
* Note: Workers KV enforces a 60s minimum TTL; shorter TTLs are dropped
|
|
6
|
-
* (stored without expiry) rather than rejected.
|
|
7
|
-
*
|
|
8
|
-
* Workers KV is eventually consistent across locations. It is suitable for
|
|
9
|
-
* this example's durable state, but cannot promise immediate global OAuth
|
|
10
|
-
* disconnect, credential rotation, or access-token issuance/revocation; use a
|
|
11
|
-
* strongly consistent adapter when that is required.
|
|
12
|
-
*/
|
|
13
|
-
export function cloudflareKvStorage(namespace: KVNamespace): KVStorage {
|
|
14
|
-
return {
|
|
15
|
-
async get(key) {
|
|
16
|
-
return namespace.get(key);
|
|
17
|
-
},
|
|
18
|
-
async set(key, value, opts) {
|
|
19
|
-
const ttl = opts?.ttlSeconds;
|
|
20
|
-
await namespace.put(
|
|
21
|
-
key,
|
|
22
|
-
value,
|
|
23
|
-
ttl && ttl >= 60 ? { expirationTtl: ttl } : undefined,
|
|
24
|
-
);
|
|
25
|
-
},
|
|
26
|
-
async delete(key) {
|
|
27
|
-
await namespace.delete(key);
|
|
28
|
-
},
|
|
29
|
-
async list(prefix) {
|
|
30
|
-
const keys: string[] = [];
|
|
31
|
-
let cursor: string | undefined;
|
|
32
|
-
do {
|
|
33
|
-
const page = await namespace.list({
|
|
34
|
-
prefix,
|
|
35
|
-
...(cursor ? { cursor } : {}),
|
|
36
|
-
});
|
|
37
|
-
keys.push(...page.keys.map((key) => key.name));
|
|
38
|
-
cursor = page.list_complete ? undefined : page.cursor;
|
|
39
|
-
} while (cursor);
|
|
40
|
-
return keys.sort();
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|