@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/activity.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ActivityModule } from "./module-contracts.js";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import { type ClassificationCode } from "./errors.js";
|
|
3
|
+
import type { Logger } from "./types.js";
|
|
4
|
+
/** One policy for the record builder, SQL write/read boundaries, and UI. */
|
|
5
|
+
export declare function activityClientFact(value: unknown, field: "name" | "version"): string | undefined;
|
|
6
|
+
/** Stored package versions use a bounded release-version grammar, including prereleases. */
|
|
7
|
+
export declare function activityPackageVersion(value: unknown): string | undefined;
|
|
8
|
+
export type ActivityCallSource = "catalog_refresh" | "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
|
|
4
9
|
export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
|
|
5
10
|
/** Read-only history: how much one `resume_execution` approved (#672). */
|
|
6
11
|
export type ActivityApproval = "call" | "tool";
|
|
@@ -28,11 +33,37 @@ export interface ActivityActor {
|
|
|
28
33
|
* by construction. Deployments that need a safe human summary can add a
|
|
29
34
|
* separate, explicit connector-level feature later.
|
|
30
35
|
*/
|
|
36
|
+
export interface ActivityCatalogChange {
|
|
37
|
+
kind: "catalog_changed";
|
|
38
|
+
addedTools: number;
|
|
39
|
+
removedTools: number;
|
|
40
|
+
changedTools: number;
|
|
41
|
+
}
|
|
42
|
+
/** Integer metadata is checked at every write and read boundary. */
|
|
43
|
+
export declare function activityCount(value: unknown): number | undefined;
|
|
44
|
+
export declare function activityBehaviorFacts(event: {
|
|
45
|
+
classification?: unknown;
|
|
46
|
+
resultBytes?: unknown;
|
|
47
|
+
kind?: unknown;
|
|
48
|
+
drift?: unknown;
|
|
49
|
+
pool?: unknown;
|
|
50
|
+
actorBasis?: unknown;
|
|
51
|
+
}): Pick<ToolCallActivityEvent, "classification" | "resultBytes" | "kind" | "drift" | "pool" | "actorBasis">;
|
|
31
52
|
export interface ToolCallActivityEvent {
|
|
32
53
|
schemaVersion: 1;
|
|
33
54
|
id: string;
|
|
34
55
|
occurredAt: string;
|
|
35
56
|
requestId: string;
|
|
57
|
+
/** Absent on legacy tool rows. Catalog changes use the same paging envelope. */
|
|
58
|
+
kind?: "catalog_drift";
|
|
59
|
+
drift?: ActivityCatalogChange;
|
|
60
|
+
/** The final registry verdict captured when resolution succeeded. */
|
|
61
|
+
classification?: "read" | "write";
|
|
62
|
+
/** UTF-8 bytes of the downstream value before result paging or truncation. */
|
|
63
|
+
resultBytes?: number;
|
|
64
|
+
pool?: string;
|
|
65
|
+
/** Personal ownership is provable only for rows recorded from the admitted principal. */
|
|
66
|
+
actorBasis?: "principal";
|
|
36
67
|
actor: ActivityActor;
|
|
37
68
|
connectorId: string;
|
|
38
69
|
toolName: string;
|
|
@@ -55,28 +86,13 @@ export interface ToolCallActivityEvent {
|
|
|
55
86
|
* here — the approved arguments were never recorded.
|
|
56
87
|
*/
|
|
57
88
|
approval?: ActivityApproval;
|
|
89
|
+
/** Real build version. Absent only in history written before telemetry existed. */
|
|
90
|
+
packageVersion?: string;
|
|
58
91
|
serverName: string;
|
|
59
92
|
serverVersion: string;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* A downstream catalog moved away from the manifest a release reviewed.
|
|
64
|
-
*
|
|
65
|
-
* Four integers and an id: the type has nowhere to put a tool name, a schema,
|
|
66
|
-
* an argument, a result, or a downstream error string, which is the same
|
|
67
|
-
* construction guarantee `ToolCallActivityEvent` makes. Which tool drifted is
|
|
68
|
-
* deliberately absent — the runtime reports that something did, and the
|
|
69
|
-
* maintainer-run check with a live catalog in front of it names names
|
|
70
|
-
* ([#343](https://github.com/zackbart/connecta/issues/343),
|
|
71
|
-
* [#351](https://github.com/zackbart/connecta/issues/351)).
|
|
72
|
-
*/
|
|
73
|
-
export interface CatalogDriftActivityEvent extends CatalogDriftCounts {
|
|
74
|
-
schemaVersion: 1;
|
|
75
|
-
id: string;
|
|
76
|
-
occurredAt: string;
|
|
77
|
-
connectorId: string;
|
|
78
|
-
serverName: string;
|
|
79
|
-
serverVersion: string;
|
|
93
|
+
/** Self-declared client identity checked by activityClientFact; invalid facts are absent. */
|
|
94
|
+
clientName?: string;
|
|
95
|
+
clientVersion?: string;
|
|
80
96
|
deploymentId?: string;
|
|
81
97
|
}
|
|
82
98
|
export interface ActivityPage {
|
|
@@ -97,13 +113,6 @@ export interface ActivityReadPage {
|
|
|
97
113
|
/** Write-only deployments can implement only this small, vendor-neutral seam. */
|
|
98
114
|
export interface ActivitySink {
|
|
99
115
|
record(event: ToolCallActivityEvent): void | Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Optional catalog-drift channel. Optional rather than a widened `record`,
|
|
102
|
-
* because a store written before drift existed already decided what a row
|
|
103
|
-
* looks like: a sink that does not implement this simply never hears about
|
|
104
|
-
* drift, and the operator still reads the same counts from connector status.
|
|
105
|
-
*/
|
|
106
|
-
recordCatalogDrift?(event: CatalogDriftActivityEvent): void | Promise<void>;
|
|
107
116
|
}
|
|
108
117
|
/** Optional read side used by Connecta's authenticated Activity UI. */
|
|
109
118
|
export interface ActivityReader {
|
|
@@ -114,6 +123,14 @@ export interface ActivityReader {
|
|
|
114
123
|
}
|
|
115
124
|
export interface ActivityStore extends ActivitySink {
|
|
116
125
|
list?: ActivityReader["list"];
|
|
126
|
+
/**
|
|
127
|
+
* The adapter and its retention, for `Connecta.describeConfig()`. Kinds
|
|
128
|
+
* other than the shipped `"d1"` and `"sqlite"` are described as `"custom"`.
|
|
129
|
+
*/
|
|
130
|
+
describe?(): {
|
|
131
|
+
kind: string;
|
|
132
|
+
retentionDays?: number;
|
|
133
|
+
};
|
|
117
134
|
}
|
|
118
135
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
119
136
|
export declare class InvalidActivityCursorError extends Error {
|
|
@@ -122,17 +139,21 @@ export declare class InvalidActivityCursorError extends Error {
|
|
|
122
139
|
}
|
|
123
140
|
export type ActivityReadGate = (actor: ActivityActor) => boolean | Promise<boolean>;
|
|
124
141
|
/**
|
|
125
|
-
*
|
|
126
|
-
* a
|
|
127
|
-
* did, so it carries no actor and no request id to attribute it to one.
|
|
142
|
+
* Default destination for catalog changes. Request attribution overrides it
|
|
143
|
+
* when a publication belongs to an admitted caller.
|
|
128
144
|
*/
|
|
129
145
|
export interface CatalogDriftActivityContext {
|
|
130
|
-
|
|
146
|
+
recordChange?: typeof recordCatalogChangeActivity;
|
|
147
|
+
defer?: (promise: Promise<unknown>) => void;
|
|
131
148
|
sink: ActivitySink;
|
|
132
149
|
serverInfo: {
|
|
133
150
|
name: string;
|
|
134
151
|
version: string;
|
|
135
152
|
};
|
|
153
|
+
clientInfo?: {
|
|
154
|
+
name: string;
|
|
155
|
+
version: string;
|
|
156
|
+
};
|
|
136
157
|
deploymentId?: string;
|
|
137
158
|
logger: Logger;
|
|
138
159
|
}
|
|
@@ -141,34 +162,47 @@ export interface ActivityRequestContext {
|
|
|
141
162
|
recordTool?: typeof recordToolActivity | undefined;
|
|
142
163
|
sink: ActivitySink;
|
|
143
164
|
actor: ActivityActor;
|
|
165
|
+
/** Personal rows identify their admitted owner in the existing actor fields. */
|
|
166
|
+
principalActor?: ActivityActor;
|
|
144
167
|
requestId: string;
|
|
168
|
+
pool?: string;
|
|
145
169
|
serverInfo: {
|
|
146
170
|
name: string;
|
|
147
171
|
version: string;
|
|
148
172
|
};
|
|
173
|
+
clientInfo?: {
|
|
174
|
+
name: string;
|
|
175
|
+
version: string;
|
|
176
|
+
};
|
|
149
177
|
deploymentId?: string;
|
|
150
178
|
defer?: (promise: Promise<unknown>) => void;
|
|
151
179
|
logger: Logger;
|
|
152
180
|
}
|
|
153
|
-
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "
|
|
181
|
+
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "friction" | "classification" | "resultBytes" | "kind" | "drift"> & {
|
|
182
|
+
/**
|
|
183
|
+
* A code connecta assigns. Rows written by older deployments may hold
|
|
184
|
+
* others, so the stored event keeps `string`; a new row never does.
|
|
185
|
+
*/
|
|
186
|
+
errorCode?: ClassificationCode;
|
|
187
|
+
personal?: boolean;
|
|
188
|
+
privateCatalog?: boolean;
|
|
189
|
+
};
|
|
154
190
|
/**
|
|
155
191
|
* Best-effort by design: activity storage can never change a tool result.
|
|
156
192
|
* Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
|
|
157
193
|
* Engine complete inline; Node promises remain detached from the response.
|
|
158
194
|
*/
|
|
159
195
|
export declare function recordToolActivity(context: ActivityRequestContext | undefined, input: ActivityEventInput): void;
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
* write: a store that is down, or a sink that never implemented the channel,
|
|
163
|
-
* can never change what a refresh returned to the caller who paid for it.
|
|
164
|
-
*/
|
|
165
|
-
export declare function recordCatalogDriftActivity(context: CatalogDriftActivityContext | undefined, input: {
|
|
196
|
+
/** A discrete catalog change, with no catalog names, descriptions or schemas. */
|
|
197
|
+
export declare function recordCatalogChangeActivity(context: CatalogDriftActivityContext | undefined, input: {
|
|
166
198
|
connectorId: string;
|
|
167
|
-
|
|
199
|
+
drift: ActivityCatalogChange;
|
|
200
|
+
personal?: boolean;
|
|
201
|
+
privateCatalog?: boolean;
|
|
202
|
+
}, request?: ActivityRequestContext): void;
|
|
168
203
|
export interface ActivityHistoryOptions {
|
|
169
204
|
store: ActivityStore;
|
|
170
205
|
deploymentId?: string;
|
|
171
206
|
readGate?: ActivityReadGate;
|
|
172
207
|
}
|
|
173
|
-
/** Attach payload-free history without changing tool results on store failure. */
|
|
174
208
|
export declare function activityHistory(options: ActivityHistoryOptions): ActivityModule;
|
package/dist/activity.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
2
|
+
import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
|
|
1
3
|
import { routeActivity } from "./routes/activity.js";
|
|
2
|
-
import { boundedEchoText } from "./errors.js";
|
|
4
|
+
import { boundedEchoText, classificationCode } from "./errors.js";
|
|
5
|
+
import { failureRecord, logFailure } from "./operator-record.js";
|
|
3
6
|
/**
|
|
4
7
|
* How long an identity field may be before the store stops believing it.
|
|
5
8
|
*
|
|
@@ -15,10 +18,72 @@ import { boundedEchoText } from "./errors.js";
|
|
|
15
18
|
* 40,000 would. The `…` marker keeps a clamped value from reading as a real one.
|
|
16
19
|
*/
|
|
17
20
|
const MAX_ACTIVITY_NAME_BYTES = 128;
|
|
21
|
+
/**
|
|
22
|
+
* Client facts use bounded ASCII grammars, never truncation or escaping.
|
|
23
|
+
* Names are 1 to 64 characters: alphanumeric or @ first (scoped package names
|
|
24
|
+
* such as `@modelcontextprotocol/inspector`), then alphanumerics, space, dot,
|
|
25
|
+
* underscore, @, /, + or -. Versions are 1 to 32 characters: alphanumeric
|
|
26
|
+
* first, then alphanumerics, dot, underscore, + or -. Prototype-key names are
|
|
27
|
+
* withheld too. The end assertion rejects even a final CR/LF, unlike `$`.
|
|
28
|
+
*/
|
|
29
|
+
const CLIENT_FACT_GRAMMARS = {
|
|
30
|
+
name: /^[A-Za-z0-9@][A-Za-z0-9 ._@/+-]{0,63}(?![\s\S])/,
|
|
31
|
+
version: /^[A-Za-z0-9][A-Za-z0-9._+-]{0,31}(?![\s\S])/,
|
|
32
|
+
};
|
|
33
|
+
const RESERVED_CLIENT_FACTS = new Set(["__proto__", "constructor", "prototype"]);
|
|
34
|
+
/** One policy for the record builder, SQL write/read boundaries, and UI. */
|
|
35
|
+
export function activityClientFact(value, field) {
|
|
36
|
+
return typeof value === "string" && !RESERVED_CLIENT_FACTS.has(value) && CLIENT_FACT_GRAMMARS[field].test(value)
|
|
37
|
+
? value
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Stored package versions use a bounded release-version grammar, including prereleases. */
|
|
41
|
+
export function activityPackageVersion(value) {
|
|
42
|
+
return typeof value === "string" &&
|
|
43
|
+
value.length <= 128 &&
|
|
44
|
+
/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?(?![\s\S])/.test(value)
|
|
45
|
+
? value
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
18
48
|
/** Two names and the dot between them. */
|
|
19
49
|
const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
|
|
20
50
|
import { agentFrictionForCode } from "./activity-friction.js";
|
|
21
51
|
export { agentFrictionForCode } from "./activity-friction.js";
|
|
52
|
+
/** Integer metadata is checked at every write and read boundary. */
|
|
53
|
+
export function activityCount(value) {
|
|
54
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
55
|
+
}
|
|
56
|
+
export function activityBehaviorFacts(event) {
|
|
57
|
+
const classification = event.classification === "read" || event.classification === "write" ? event.classification : undefined;
|
|
58
|
+
const resultBytes = activityCount(event.resultBytes);
|
|
59
|
+
const change = event.drift;
|
|
60
|
+
const drift = event.kind === "catalog_drift" &&
|
|
61
|
+
change?.kind === "catalog_changed" &&
|
|
62
|
+
activityCount(change.addedTools) !== undefined &&
|
|
63
|
+
activityCount(change.removedTools) !== undefined &&
|
|
64
|
+
activityCount(change.changedTools) !== undefined
|
|
65
|
+
? {
|
|
66
|
+
kind: "catalog_changed",
|
|
67
|
+
addedTools: change.addedTools,
|
|
68
|
+
removedTools: change.removedTools,
|
|
69
|
+
changedTools: change.changedTools,
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
72
|
+
// Pool names come from matched deployment config, whose grammar has no size
|
|
73
|
+
// ceiling. Invalid stored scope stays withheld rather than becoming root scope.
|
|
74
|
+
const pool = event.pool === undefined || event.pool === null
|
|
75
|
+
? undefined
|
|
76
|
+
: typeof event.pool === "string" && /^[a-z0-9_-]+(?![\s\S])/.test(event.pool)
|
|
77
|
+
? event.pool
|
|
78
|
+
: "<withheld>";
|
|
79
|
+
return {
|
|
80
|
+
...(event.actorBasis === "principal" ? { actorBasis: "principal" } : {}),
|
|
81
|
+
...(classification !== undefined ? { classification } : {}),
|
|
82
|
+
...(resultBytes !== undefined ? { resultBytes } : {}),
|
|
83
|
+
...(drift ? { kind: "catalog_drift", drift } : {}),
|
|
84
|
+
...(pool !== undefined ? { pool } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
22
87
|
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
23
88
|
export class InvalidActivityCursorError extends Error {
|
|
24
89
|
name = "InvalidActivityCursorError";
|
|
@@ -36,13 +101,24 @@ export function recordToolActivity(context, input) {
|
|
|
36
101
|
return;
|
|
37
102
|
// A caller-supplied class wins because it knows something the code table
|
|
38
103
|
// cannot: friction that belongs to a call which did not fail.
|
|
39
|
-
|
|
104
|
+
// Checked here as well as by type: a handler can put any string in a
|
|
105
|
+
// `ConnectorCallError`'s code, and a downstream's own code is not recorded.
|
|
106
|
+
const errorCode = classificationCode(input.errorCode);
|
|
107
|
+
const friction = input.friction ?? agentFrictionForCode(errorCode);
|
|
108
|
+
// Re-check client grammars at the record boundary; never spread the envelope.
|
|
109
|
+
const clientName = activityClientFact(context.clientInfo?.name, "name");
|
|
110
|
+
const clientVersion = activityClientFact(context.clientInfo?.version, "version");
|
|
40
111
|
const event = {
|
|
41
112
|
schemaVersion: 1,
|
|
42
113
|
id: crypto.randomUUID(),
|
|
43
114
|
occurredAt: new Date().toISOString(),
|
|
44
115
|
requestId: context.requestId,
|
|
45
|
-
actor: context.actor,
|
|
116
|
+
actor: input.personal || input.privateCatalog ? (context.principalActor ?? { kind: context.actor.kind }) : context.actor,
|
|
117
|
+
...activityBehaviorFacts({
|
|
118
|
+
...input,
|
|
119
|
+
pool: context.pool,
|
|
120
|
+
actorBasis: input.privateCatalog || (input.personal && context.principalActor) ? "principal" : undefined,
|
|
121
|
+
}),
|
|
46
122
|
connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
|
|
47
123
|
toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
|
|
48
124
|
address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
|
|
@@ -50,13 +126,14 @@ export function recordToolActivity(context, input) {
|
|
|
50
126
|
outcome: input.outcome,
|
|
51
127
|
durationMs: Math.max(0, Math.trunc(input.durationMs)),
|
|
52
128
|
attempts: Math.max(1, Math.trunc(input.attempts)),
|
|
53
|
-
...(
|
|
129
|
+
...(errorCode ? { errorCode } : {}),
|
|
54
130
|
...(friction ? { friction } : {}),
|
|
131
|
+
packageVersion: CONNECTA_VERSION,
|
|
55
132
|
serverName: context.serverInfo.name,
|
|
56
133
|
serverVersion: context.serverInfo.version,
|
|
57
|
-
...(
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
...(clientName !== undefined ? { clientName } : {}),
|
|
135
|
+
...(clientVersion !== undefined ? { clientVersion } : {}),
|
|
136
|
+
...(context.deploymentId ? { deploymentId: context.deploymentId } : {}),
|
|
60
137
|
};
|
|
61
138
|
try {
|
|
62
139
|
const result = context.sink.record(event);
|
|
@@ -64,51 +141,42 @@ export function recordToolActivity(context, input) {
|
|
|
64
141
|
return;
|
|
65
142
|
}
|
|
66
143
|
const pending = Promise.resolve(result).catch((error) => {
|
|
67
|
-
context.logger
|
|
144
|
+
logFailure(context.logger, "activity record failed", failureRecord({}, error));
|
|
68
145
|
});
|
|
69
146
|
if (context.defer)
|
|
70
147
|
context.defer(pending);
|
|
71
148
|
}
|
|
72
149
|
catch (error) {
|
|
73
|
-
context.logger
|
|
150
|
+
logFailure(context.logger, "activity record failed", failureRecord({}, error));
|
|
74
151
|
}
|
|
75
152
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* can never change what a refresh returned to the caller who paid for it.
|
|
80
|
-
*/
|
|
81
|
-
export function recordCatalogDriftActivity(context, input) {
|
|
82
|
-
if (!context?.sink.recordCatalogDrift)
|
|
153
|
+
/** A discrete catalog change, with no catalog names, descriptions or schemas. */
|
|
154
|
+
export function recordCatalogChangeActivity(context, input, request) {
|
|
155
|
+
if (!context)
|
|
83
156
|
return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
void Promise.resolve(result).catch((error) => {
|
|
103
|
-
context.logger.warn("[connecta] catalog drift record failed", error);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
context.logger.warn("[connecta] catalog drift record failed", error);
|
|
108
|
-
}
|
|
157
|
+
recordToolActivity(request ?? {
|
|
158
|
+
...context,
|
|
159
|
+
actor: { kind: "system" },
|
|
160
|
+
requestId: crypto.randomUUID(),
|
|
161
|
+
}, {
|
|
162
|
+
connectorId: input.connectorId,
|
|
163
|
+
toolName: "<catalog>",
|
|
164
|
+
address: `${input.connectorId}.<catalog>`,
|
|
165
|
+
source: "catalog_refresh",
|
|
166
|
+
outcome: "success",
|
|
167
|
+
durationMs: 0,
|
|
168
|
+
attempts: 1,
|
|
169
|
+
kind: "catalog_drift",
|
|
170
|
+
drift: input.drift,
|
|
171
|
+
...(input.personal ? { personal: true } : {}),
|
|
172
|
+
...(input.privateCatalog ? { privateCatalog: true } : {}),
|
|
173
|
+
});
|
|
109
174
|
}
|
|
110
175
|
/** Attach payload-free history without changing tool results on store failure. */
|
|
176
|
+
/** The closed options activityHistory() accepts; see `assertKnownOptions`. */
|
|
177
|
+
const ACTIVITY_HISTORY_OPTIONS = optionsOf()(keys("store", "deploymentId", "readGate"));
|
|
111
178
|
export function activityHistory(options) {
|
|
179
|
+
options = assertKnownOptions(options, "activityHistory()", ACTIVITY_HISTORY_OPTIONS);
|
|
112
180
|
if (!options || typeof options.store?.record !== "function") {
|
|
113
181
|
throw new Error("activityHistory.store must implement record(event)");
|
|
114
182
|
}
|
|
@@ -116,6 +184,6 @@ export function activityHistory(options) {
|
|
|
116
184
|
...options,
|
|
117
185
|
handle: routeActivity,
|
|
118
186
|
recordTool: recordToolActivity,
|
|
119
|
-
|
|
187
|
+
recordChange: recordCatalogChangeActivity,
|
|
120
188
|
};
|
|
121
189
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const PUBLIC = {};
|
|
2
|
+
const OMIT = { omit: true };
|
|
3
|
+
const OMITTED = Symbol("omitted argument");
|
|
4
|
+
const MAX_DEPTH = 32;
|
|
5
|
+
const MAX_SCHEMAS = 2048;
|
|
6
|
+
function object(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function sensitive(plan) {
|
|
10
|
+
return Object.keys(plan).length > 0;
|
|
11
|
+
}
|
|
12
|
+
function equal(left, right) {
|
|
13
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
14
|
+
}
|
|
15
|
+
function item(plan, index) {
|
|
16
|
+
return plan.prefix?.[index] ?? plan.items ?? PUBLIC;
|
|
17
|
+
}
|
|
18
|
+
/** Combine restrictions from references, siblings, and allOf without losing array indices. */
|
|
19
|
+
function merge(left, right) {
|
|
20
|
+
if (left.omit || right.omit)
|
|
21
|
+
return OMIT;
|
|
22
|
+
const names = new Set([...Object.keys(left.properties ?? {}), ...Object.keys(right.properties ?? {})]);
|
|
23
|
+
const properties = Object.fromEntries([...names]
|
|
24
|
+
.sort()
|
|
25
|
+
.map((name) => [
|
|
26
|
+
name,
|
|
27
|
+
merge(Object.hasOwn(left.properties ?? {}, name) ? left.properties[name] : PUBLIC, Object.hasOwn(right.properties ?? {}, name) ? right.properties[name] : PUBLIC),
|
|
28
|
+
]));
|
|
29
|
+
const items = mergeItems(left.items ?? PUBLIC, right.items ?? PUBLIC);
|
|
30
|
+
const prefix = Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => mergeItems(item(left, index), item(right, index)));
|
|
31
|
+
// Equivalent plans compare equally even when one schema spells out public prefixes.
|
|
32
|
+
while (prefix.length && equal(prefix.at(-1), items))
|
|
33
|
+
prefix.pop();
|
|
34
|
+
return {
|
|
35
|
+
...(names.size ? { properties } : {}),
|
|
36
|
+
...(prefix.length ? { prefix } : {}),
|
|
37
|
+
...(sensitive(items) ? { items } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function mergeItems(left, right) {
|
|
41
|
+
if (!sensitive(left))
|
|
42
|
+
return right;
|
|
43
|
+
if (!sensitive(right))
|
|
44
|
+
return left;
|
|
45
|
+
return merge(left, right);
|
|
46
|
+
}
|
|
47
|
+
/** Compile a bounded plan. Unknown sensitivity refuses the entire echo, never just a branch. */
|
|
48
|
+
function compile(root, unionAlternatives = false) {
|
|
49
|
+
let visited = 0;
|
|
50
|
+
const active = new Set();
|
|
51
|
+
const unresolved = () => {
|
|
52
|
+
throw new Error("Unresolved argument sensitivity");
|
|
53
|
+
};
|
|
54
|
+
function reference(ref) {
|
|
55
|
+
if (typeof ref !== "string" || !ref.startsWith("#"))
|
|
56
|
+
return unresolved();
|
|
57
|
+
const pointer = decodeURIComponent(ref.slice(1));
|
|
58
|
+
if (pointer === "")
|
|
59
|
+
return root;
|
|
60
|
+
if (!pointer.startsWith("/"))
|
|
61
|
+
return unresolved();
|
|
62
|
+
let target = root;
|
|
63
|
+
for (const encoded of pointer.slice(1).split("/")) {
|
|
64
|
+
if (/~[^01]|~$/.test(encoded))
|
|
65
|
+
return unresolved();
|
|
66
|
+
const key = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
67
|
+
if (target === null || typeof target !== "object" || !Object.hasOwn(target, key))
|
|
68
|
+
return unresolved();
|
|
69
|
+
target = target[key];
|
|
70
|
+
}
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
function walk(schema, depth) {
|
|
74
|
+
if (++visited > MAX_SCHEMAS || depth > MAX_DEPTH)
|
|
75
|
+
return unresolved();
|
|
76
|
+
if (typeof schema === "boolean")
|
|
77
|
+
return PUBLIC;
|
|
78
|
+
if (!object(schema) || active.has(schema))
|
|
79
|
+
return unresolved();
|
|
80
|
+
if (schema["writeOnly"] !== undefined && typeof schema["writeOnly"] !== "boolean")
|
|
81
|
+
return unresolved();
|
|
82
|
+
if (schema["writeOnly"] === true)
|
|
83
|
+
return OMIT;
|
|
84
|
+
if ((schema !== root && schema["$id"] !== undefined) ||
|
|
85
|
+
schema["$dynamicRef"] !== undefined ||
|
|
86
|
+
schema["$recursiveRef"] !== undefined)
|
|
87
|
+
return unresolved();
|
|
88
|
+
active.add(schema);
|
|
89
|
+
try {
|
|
90
|
+
let plan = PUBLIC;
|
|
91
|
+
const child = (value) => walk(value, depth + 1);
|
|
92
|
+
if (schema["$ref"] !== undefined)
|
|
93
|
+
plan = merge(plan, child(reference(schema["$ref"])));
|
|
94
|
+
if (schema["properties"] !== undefined) {
|
|
95
|
+
if (!object(schema["properties"]))
|
|
96
|
+
return unresolved();
|
|
97
|
+
const properties = Object.entries(schema["properties"])
|
|
98
|
+
.map(([name, value]) => [name, child(value)])
|
|
99
|
+
.filter(([, value]) => sensitive(value))
|
|
100
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
101
|
+
plan = merge(plan, properties.length ? { properties: Object.fromEntries(properties) } : PUBLIC);
|
|
102
|
+
}
|
|
103
|
+
const prefixes = schema["prefixItems"] ?? (Array.isArray(schema["items"]) ? schema["items"] : undefined);
|
|
104
|
+
if (prefixes !== undefined && !Array.isArray(prefixes))
|
|
105
|
+
return unresolved();
|
|
106
|
+
if (schema["prefixItems"] !== undefined && Array.isArray(schema["items"]))
|
|
107
|
+
return unresolved();
|
|
108
|
+
const tail = Array.isArray(schema["items"]) ? schema["additionalItems"] : schema["items"];
|
|
109
|
+
const arrayPlan = {
|
|
110
|
+
...(prefixes ? { prefix: prefixes.map(child) } : {}),
|
|
111
|
+
...(tail !== undefined ? { items: child(tail) } : {}),
|
|
112
|
+
};
|
|
113
|
+
plan = merge(plan, arrayPlan);
|
|
114
|
+
for (const keyword of ["allOf", "oneOf", "anyOf"]) {
|
|
115
|
+
const branches = schema[keyword];
|
|
116
|
+
if (branches === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (!Array.isArray(branches) || !branches.length)
|
|
119
|
+
return unresolved();
|
|
120
|
+
const plans = branches.map(child);
|
|
121
|
+
if (keyword === "allOf" || unionAlternatives)
|
|
122
|
+
for (const branch of plans)
|
|
123
|
+
plan = merge(plan, branch);
|
|
124
|
+
else {
|
|
125
|
+
if (plans.some((branch) => !equal(branch, plans[0])))
|
|
126
|
+
return unresolved();
|
|
127
|
+
plan = merge(plan, plans[0]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// These applicators need matching/evaluation rules we do not implement.
|
|
131
|
+
// Public-only schemas are harmless; sensitivity or an unresolved reference fails closed.
|
|
132
|
+
for (const keyword of [
|
|
133
|
+
"additionalProperties",
|
|
134
|
+
"unevaluatedProperties",
|
|
135
|
+
"unevaluatedItems",
|
|
136
|
+
"contains",
|
|
137
|
+
"not",
|
|
138
|
+
"if",
|
|
139
|
+
"then",
|
|
140
|
+
"else",
|
|
141
|
+
])
|
|
142
|
+
if (schema[keyword] !== undefined && sensitive(child(schema[keyword])))
|
|
143
|
+
return unresolved();
|
|
144
|
+
for (const keyword of ["patternProperties", "dependentSchemas", "dependencies"]) {
|
|
145
|
+
if (schema[keyword] === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
if (!object(schema[keyword]))
|
|
148
|
+
return unresolved();
|
|
149
|
+
for (const value of Object.values(schema[keyword])) {
|
|
150
|
+
if (keyword === "dependencies" && Array.isArray(value))
|
|
151
|
+
continue;
|
|
152
|
+
if (sensitive(child(value)))
|
|
153
|
+
return unresolved();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return plan;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
active.delete(schema);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return walk(root, 0);
|
|
163
|
+
}
|
|
164
|
+
/** Omit whole array values when private elements cannot be removed without shifting indices. */
|
|
165
|
+
function apply(value, plan, mark, depth = 0) {
|
|
166
|
+
if (!sensitive(plan))
|
|
167
|
+
return value;
|
|
168
|
+
if (depth > MAX_DEPTH)
|
|
169
|
+
throw new Error("Argument redaction depth exceeded");
|
|
170
|
+
if (plan.omit) {
|
|
171
|
+
mark(value);
|
|
172
|
+
return OMITTED;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
const values = value.map((entry, index) => apply(entry, item(plan, index), mark, depth + 1));
|
|
176
|
+
return values.includes(OMITTED) ? OMITTED : values;
|
|
177
|
+
}
|
|
178
|
+
if (!object(value))
|
|
179
|
+
return value;
|
|
180
|
+
return Object.fromEntries(Object.entries(value).flatMap(([name, entry]) => {
|
|
181
|
+
const field = Object.hasOwn(plan.properties ?? {}, name) ? plan.properties[name] : PUBLIC;
|
|
182
|
+
const filtered = apply(entry, field, mark, depth + 1);
|
|
183
|
+
return filtered === OMITTED ? [] : [[name, filtered]];
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
/** Output redaction protects the union of private fields across alternatives.
|
|
187
|
+
* Other sensitivity rules and unresolved-schema handling follow echo redaction. */
|
|
188
|
+
export function visitPrivateCallArguments(args, schema, visit) {
|
|
189
|
+
// Public-only schemas need no sensitivity compilation, regardless of size.
|
|
190
|
+
// Iterative traversal visits each object once, including definitions and cycles.
|
|
191
|
+
const pending = [schema];
|
|
192
|
+
const seen = new Set();
|
|
193
|
+
let privateSchema = false;
|
|
194
|
+
while (pending.length) {
|
|
195
|
+
const value = pending.pop();
|
|
196
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
197
|
+
continue;
|
|
198
|
+
seen.add(value);
|
|
199
|
+
if (Object.hasOwn(value, "writeOnly") && value["writeOnly"] === true) {
|
|
200
|
+
privateSchema = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
for (const entry of Object.values(value))
|
|
204
|
+
pending.push(entry);
|
|
205
|
+
}
|
|
206
|
+
if (!privateSchema)
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
apply(args, compile(schema, true), visit);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// When a private annotation exists but its location cannot be resolved,
|
|
213
|
+
// treat every submitted value as private, just as the echo is withheld.
|
|
214
|
+
visit(args);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Internal schema filter; the single echo helper owns snapshotting and byte budgeting. */
|
|
218
|
+
export function redactCallArguments(args, schema) {
|
|
219
|
+
try {
|
|
220
|
+
const plan = compile(schema);
|
|
221
|
+
let redacted = false;
|
|
222
|
+
const value = apply(args, plan, () => {
|
|
223
|
+
redacted = true;
|
|
224
|
+
});
|
|
225
|
+
return { ...(value !== OMITTED ? { value } : {}), redacted };
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return { redacted: true };
|
|
229
|
+
}
|
|
230
|
+
}
|