wake-bridge 0.9.0-preview.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/dist/src/adapters/botlingknows.d.ts +42 -0
  4. package/dist/src/adapters/botlingknows.js +141 -0
  5. package/dist/src/adapters/botlingknows.js.map +1 -0
  6. package/dist/src/adapters/group-chat.d.ts +43 -0
  7. package/dist/src/adapters/group-chat.js +120 -0
  8. package/dist/src/adapters/group-chat.js.map +1 -0
  9. package/dist/src/cli.d.ts +2 -0
  10. package/dist/src/cli.js +637 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/connector-catalog.d.ts +17 -0
  13. package/dist/src/connector-catalog.js +43 -0
  14. package/dist/src/connector-catalog.js.map +1 -0
  15. package/dist/src/connectors/botlingknows-mcp.d.ts +15 -0
  16. package/dist/src/connectors/botlingknows-mcp.js +130 -0
  17. package/dist/src/connectors/botlingknows-mcp.js.map +1 -0
  18. package/dist/src/connectors/group-chat-http.d.ts +17 -0
  19. package/dist/src/connectors/group-chat-http.js +226 -0
  20. package/dist/src/connectors/group-chat-http.js.map +1 -0
  21. package/dist/src/connectors/mcp-http-client.d.ts +11 -0
  22. package/dist/src/connectors/mcp-http-client.js +108 -0
  23. package/dist/src/connectors/mcp-http-client.js.map +1 -0
  24. package/dist/src/core.d.ts +170 -0
  25. package/dist/src/core.js +1720 -0
  26. package/dist/src/core.js.map +1 -0
  27. package/dist/src/daemon.d.ts +31 -0
  28. package/dist/src/daemon.js +605 -0
  29. package/dist/src/daemon.js.map +1 -0
  30. package/dist/src/db.d.ts +31 -0
  31. package/dist/src/db.js +513 -0
  32. package/dist/src/db.js.map +1 -0
  33. package/dist/src/event-sdk.d.ts +43 -0
  34. package/dist/src/event-sdk.js +152 -0
  35. package/dist/src/event-sdk.js.map +1 -0
  36. package/dist/src/host-adapter-registry.d.ts +21 -0
  37. package/dist/src/host-adapter-registry.js +59 -0
  38. package/dist/src/host-adapter-registry.js.map +1 -0
  39. package/dist/src/index.d.ts +5 -0
  40. package/dist/src/index.js +6 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/instance-config.d.ts +46 -0
  43. package/dist/src/instance-config.js +176 -0
  44. package/dist/src/instance-config.js.map +1 -0
  45. package/dist/src/mcp.d.ts +51 -0
  46. package/dist/src/mcp.js +728 -0
  47. package/dist/src/mcp.js.map +1 -0
  48. package/dist/src/operator-control.d.ts +82 -0
  49. package/dist/src/operator-control.js +220 -0
  50. package/dist/src/operator-control.js.map +1 -0
  51. package/dist/src/out-of-process-host.d.ts +37 -0
  52. package/dist/src/out-of-process-host.js +196 -0
  53. package/dist/src/out-of-process-host.js.map +1 -0
  54. package/dist/src/policy-control.d.ts +3 -0
  55. package/dist/src/policy-control.js +120 -0
  56. package/dist/src/policy-control.js.map +1 -0
  57. package/dist/src/release-lifecycle.d.ts +98 -0
  58. package/dist/src/release-lifecycle.js +378 -0
  59. package/dist/src/release-lifecycle.js.map +1 -0
  60. package/dist/src/sdk-error.d.ts +6 -0
  61. package/dist/src/sdk-error.js +12 -0
  62. package/dist/src/sdk-error.js.map +1 -0
  63. package/dist/src/source-adapter.d.ts +32 -0
  64. package/dist/src/source-adapter.js +164 -0
  65. package/dist/src/source-adapter.js.map +1 -0
  66. package/dist/src/source-connector.d.ts +50 -0
  67. package/dist/src/source-connector.js +358 -0
  68. package/dist/src/source-connector.js.map +1 -0
  69. package/dist/src/source-control-client.d.ts +38 -0
  70. package/dist/src/source-control-client.js +61 -0
  71. package/dist/src/source-control-client.js.map +1 -0
  72. package/dist/src/source-ingress.d.ts +16 -0
  73. package/dist/src/source-ingress.js +47 -0
  74. package/dist/src/source-ingress.js.map +1 -0
  75. package/dist/src/source-sdk.d.ts +6 -0
  76. package/dist/src/source-sdk.js +53 -0
  77. package/dist/src/source-sdk.js.map +1 -0
  78. package/dist/src/transport-sdk.d.ts +102 -0
  79. package/dist/src/transport-sdk.js +222 -0
  80. package/dist/src/transport-sdk.js.map +1 -0
  81. package/dist/src/transport.d.ts +23 -0
  82. package/dist/src/transport.js +71 -0
  83. package/dist/src/transport.js.map +1 -0
  84. package/dist/src/types.d.ts +522 -0
  85. package/dist/src/types.js +2 -0
  86. package/dist/src/types.js.map +1 -0
  87. package/dist/src/util.d.ts +26 -0
  88. package/dist/src/util.js +136 -0
  89. package/dist/src/util.js.map +1 -0
  90. package/dist/src/version.d.ts +1 -0
  91. package/dist/src/version.js +2 -0
  92. package/dist/src/version.js.map +1 -0
  93. package/docs/adapters/botlingknows.md +40 -0
  94. package/docs/adapters/gmail-planned.md +16 -0
  95. package/docs/adapters/group-chat.md +126 -0
  96. package/docs/operations/connector-catalog.md +40 -0
  97. package/docs/operations/operator-control.md +83 -0
  98. package/docs/operations/out-of-process-host-adapter.md +150 -0
  99. package/docs/operations/policies.md +106 -0
  100. package/docs/operations/public-sdk.md +87 -0
  101. package/docs/operations/release-lifecycle.md +154 -0
  102. package/docs/operations/source-connectors.md +163 -0
  103. package/docs/operations/source-ingress.md +108 -0
  104. package/docs/operations/templates/botlingknows-connector.env.example +5 -0
  105. package/docs/operations/templates/sources.json.example +15 -0
  106. package/docs/quickstart.md +143 -0
  107. package/docs/releases/0.9.0-preview.8.md +44 -0
  108. package/docs/verification.md +19 -0
  109. package/examples/policies/botlingknows-conservative.json +44 -0
  110. package/examples/reference-host-adapter/README.md +41 -0
  111. package/examples/reference-host-adapter/adapter.mjs +173 -0
  112. package/examples/reference-host-adapter/host-adapters.json.example +26 -0
  113. package/package.json +94 -0
  114. package/schemas/event-v1.schema.json +26 -0
  115. package/schemas/policy-v1.schema.json +66 -0
@@ -0,0 +1,12 @@
1
+ /** Error raised by a public contract validator or SDK client. */
2
+ export class WakeBridgeSdkError extends Error {
3
+ code;
4
+ status;
5
+ constructor(message, code = "invalid_contract", status = 400) {
6
+ super(message);
7
+ this.code = code;
8
+ this.status = status;
9
+ this.name = "WakeBridgeSdkError";
10
+ }
11
+ }
12
+ //# sourceMappingURL=sdk-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-error.js","sourceRoot":"","sources":["../../src/sdk-error.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAGhC;IACA;IAHX,YACE,OAAe,EACN,OAAe,kBAAkB,EACjC,SAAiB,GAAG;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,SAAI,GAAJ,IAAI,CAA6B;QACjC,WAAM,GAAN,MAAM,CAAc;QAG7B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ import { WakeBridge } from "./core.js";
2
+ import type { PullSourceAdapter, SourceAdapterManifest, SourceCheckpoint, SourceCursor, SourceRunResult } from "./types.js";
3
+ export declare class SourceCheckpointStore {
4
+ readonly bridge: WakeBridge;
5
+ constructor(bridge: WakeBridge);
6
+ get(source: string): SourceCheckpoint | null;
7
+ /** Compare-and-swap prevents two pollers from silently moving one cursor. */
8
+ commit(manifest: SourceAdapterManifest, cursor: SourceCursor, expectedRevision: number): SourceCheckpoint;
9
+ replace(manifest: SourceAdapterManifest, cursor: SourceCursor, expectedRevision: number, reason: string): SourceCheckpoint;
10
+ }
11
+ export declare class SourceControlStore {
12
+ readonly bridge: WakeBridge;
13
+ constructor(bridge: WakeBridge);
14
+ get(source: string): boolean | null;
15
+ set(source: string, enabled: boolean): void;
16
+ }
17
+ export interface SourceRunnerOptions {
18
+ limit?: number;
19
+ /** Unsafe sources remain test-only unless an embedding opts in explicitly. */
20
+ allow_read_side_effects?: boolean;
21
+ /**
22
+ * Broad/unknown provider credentials are allowed behind an external connector,
23
+ * but remain fail-closed when injected into the Wake Bridge process itself.
24
+ */
25
+ allow_in_process_broad_credentials?: boolean;
26
+ }
27
+ export declare class SourceRunner {
28
+ readonly bridge: WakeBridge;
29
+ readonly checkpoints: SourceCheckpointStore;
30
+ constructor(bridge: WakeBridge);
31
+ runOnce(adapter: PullSourceAdapter, options?: SourceRunnerOptions): Promise<SourceRunResult>;
32
+ }
@@ -0,0 +1,164 @@
1
+ import { BridgeError } from "./core.js";
2
+ import { sqlValue } from "./db.js";
3
+ import { validateSourceManifest } from "./source-sdk.js";
4
+ const MAX_CURSOR_BYTES = 4096;
5
+ function cursorJson(cursor) {
6
+ let encoded;
7
+ try {
8
+ encoded = JSON.stringify(cursor);
9
+ }
10
+ catch {
11
+ throw new BridgeError("source cursor must be JSON", "invalid_source_cursor", 400);
12
+ }
13
+ if (typeof encoded !== "string") {
14
+ throw new BridgeError("source cursor must be JSON", "invalid_source_cursor", 400);
15
+ }
16
+ if (Buffer.byteLength(encoded, "utf8") > MAX_CURSOR_BYTES) {
17
+ throw new BridgeError("source cursor is too large", "invalid_source_cursor", 413);
18
+ }
19
+ return encoded;
20
+ }
21
+ export class SourceCheckpointStore {
22
+ bridge;
23
+ constructor(bridge) {
24
+ this.bridge = bridge;
25
+ }
26
+ get(source) {
27
+ const row = this.bridge.db.query(`SELECT * FROM source_checkpoints WHERE instance_id=${sqlValue(this.bridge.config.instance_id)} AND source=${sqlValue(source)} LIMIT 1;`)[0];
28
+ if (!row)
29
+ return null;
30
+ let cursor;
31
+ try {
32
+ cursor = JSON.parse(String(row.cursor_json));
33
+ cursorJson(cursor);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof BridgeError)
37
+ throw error;
38
+ throw new BridgeError("stored source cursor is corrupt", "source_cursor_corrupt", 500);
39
+ }
40
+ return {
41
+ instance_id: String(row.instance_id),
42
+ source: String(row.source),
43
+ adapter_version: String(row.adapter_version),
44
+ subject_ref: String(row.subject_ref),
45
+ binding_fingerprint: String(row.binding_fingerprint),
46
+ cursor,
47
+ revision: Number(row.revision),
48
+ updated_at: String(row.updated_at),
49
+ };
50
+ }
51
+ /** Compare-and-swap prevents two pollers from silently moving one cursor. */
52
+ commit(manifest, cursor, expectedRevision) {
53
+ const encoded = cursorJson(cursor);
54
+ const now = this.bridge.nowIso();
55
+ const statements = expectedRevision === 0
56
+ ? [`INSERT OR IGNORE INTO source_checkpoints(instance_id, source, adapter_version, subject_ref, binding_fingerprint, cursor_json, revision, updated_at)
57
+ VALUES(${sqlValue(this.bridge.config.instance_id)}, ${sqlValue(manifest.id)}, ${sqlValue(manifest.version)}, ${sqlValue(manifest.subject_ref)}, ${sqlValue(manifest.binding_fingerprint)}, ${sqlValue(encoded)}, 1, ${sqlValue(now)});`]
58
+ : [`UPDATE source_checkpoints SET adapter_version=${sqlValue(manifest.version)}, subject_ref=${sqlValue(manifest.subject_ref)}, binding_fingerprint=${sqlValue(manifest.binding_fingerprint)}, cursor_json=${sqlValue(encoded)}, revision=revision+1, updated_at=${sqlValue(now)}
59
+ WHERE instance_id=${sqlValue(this.bridge.config.instance_id)} AND source=${sqlValue(manifest.id)} AND revision=${expectedRevision}
60
+ AND subject_ref=${sqlValue(manifest.subject_ref)} AND binding_fingerprint=${sqlValue(manifest.binding_fingerprint)};`];
61
+ this.bridge.db.transaction(statements);
62
+ const checkpoint = this.get(manifest.id);
63
+ if (!checkpoint || checkpoint.revision !== expectedRevision + 1 || checkpoint.adapter_version !== manifest.version
64
+ || checkpoint.subject_ref !== manifest.subject_ref || checkpoint.binding_fingerprint !== manifest.binding_fingerprint
65
+ || cursorJson(checkpoint.cursor) !== encoded) {
66
+ throw new BridgeError("source cursor changed concurrently", "source_cursor_conflict", 409);
67
+ }
68
+ return checkpoint;
69
+ }
70
+ replace(manifest, cursor, expectedRevision, reason) {
71
+ const before = this.get(manifest.id);
72
+ if (!before || before.revision !== expectedRevision) {
73
+ throw new BridgeError("source checkpoint changed concurrently", "source_cursor_conflict", 409);
74
+ }
75
+ const encoded = cursorJson(cursor);
76
+ const now = this.bridge.nowIso();
77
+ this.bridge.db.transaction([
78
+ `INSERT INTO source_checkpoint_history(instance_id, source, adapter_version, subject_ref, binding_fingerprint, cursor_json, revision, updated_at, archived_at, reason)
79
+ SELECT instance_id, source, adapter_version, subject_ref, binding_fingerprint, cursor_json, revision, updated_at, ${sqlValue(now)}, ${sqlValue(reason)}
80
+ FROM source_checkpoints
81
+ WHERE instance_id=${sqlValue(this.bridge.config.instance_id)} AND source=${sqlValue(manifest.id)} AND revision=${expectedRevision};`,
82
+ `UPDATE source_checkpoints SET adapter_version=${sqlValue(manifest.version)}, subject_ref=${sqlValue(manifest.subject_ref)}, binding_fingerprint=${sqlValue(manifest.binding_fingerprint)}, cursor_json=${sqlValue(encoded)}, revision=revision+1, updated_at=${sqlValue(now)}
83
+ WHERE instance_id=${sqlValue(this.bridge.config.instance_id)} AND source=${sqlValue(manifest.id)} AND revision=${expectedRevision};`,
84
+ ]);
85
+ const checkpoint = this.get(manifest.id);
86
+ if (!checkpoint || checkpoint.revision !== expectedRevision + 1 || checkpoint.subject_ref !== manifest.subject_ref
87
+ || checkpoint.binding_fingerprint !== manifest.binding_fingerprint || cursorJson(checkpoint.cursor) !== encoded) {
88
+ throw new BridgeError("source checkpoint replacement failed", "source_cursor_conflict", 409);
89
+ }
90
+ return checkpoint;
91
+ }
92
+ }
93
+ export class SourceControlStore {
94
+ bridge;
95
+ constructor(bridge) {
96
+ this.bridge = bridge;
97
+ }
98
+ get(source) {
99
+ const row = this.bridge.db.query(`SELECT enabled FROM source_controls WHERE instance_id=${sqlValue(this.bridge.config.instance_id)} AND source=${sqlValue(source)} LIMIT 1;`)[0];
100
+ return row ? Boolean(Number(row.enabled)) : null;
101
+ }
102
+ set(source, enabled) {
103
+ const now = this.bridge.nowIso();
104
+ this.bridge.db.exec(`INSERT INTO source_controls(instance_id, source, enabled, updated_at)
105
+ VALUES(${sqlValue(this.bridge.config.instance_id)}, ${sqlValue(source)}, ${sqlValue(enabled)}, ${sqlValue(now)})
106
+ ON CONFLICT(instance_id, source) DO UPDATE SET enabled=excluded.enabled, updated_at=excluded.updated_at;`);
107
+ }
108
+ }
109
+ export class SourceRunner {
110
+ bridge;
111
+ checkpoints;
112
+ constructor(bridge) {
113
+ this.bridge = bridge;
114
+ this.checkpoints = new SourceCheckpointStore(bridge);
115
+ }
116
+ async runOnce(adapter, options = {}) {
117
+ const manifest = validateSourceManifest(adapter.manifest);
118
+ if (manifest.read_side_effects !== "none" && !options.allow_read_side_effects) {
119
+ throw new BridgeError(`source ${manifest.id} has ${manifest.read_side_effects} read side effects`, "unsafe_source_read", 409);
120
+ }
121
+ // This gate checks the deployment's declared custody; it is not a sandbox
122
+ // and cannot inspect where an arbitrary adapter object really keeps a token.
123
+ // Production launchers must derive/attest custody instead of trusting an
124
+ // untrusted provider payload. Provider credentials never arrive via events.
125
+ if (manifest.credential_custody === "wake_bridge_process"
126
+ && !["none", "read_only"].includes(manifest.upstream_credential_breadth)
127
+ && !options.allow_in_process_broad_credentials) {
128
+ throw new BridgeError(`source ${manifest.id} injects a broad or unknown credential into Wake Bridge`, "unsafe_source_credential", 409);
129
+ }
130
+ const limit = Math.min(1000, Math.max(1, Math.floor(options.limit ?? 100)));
131
+ const before = this.checkpoints.get(manifest.id);
132
+ if (before && (before.subject_ref !== manifest.subject_ref || before.binding_fingerprint !== manifest.binding_fingerprint)) {
133
+ throw new BridgeError(`source ${manifest.id} identity differs from its checkpoint`, "source_identity_mismatch", 409);
134
+ }
135
+ const result = await adapter.poll({ cursor: before?.cursor ?? null, limit });
136
+ if (!result || !Array.isArray(result.events) || result.events.length > limit) {
137
+ throw new BridgeError("source poll returned an invalid or oversized event page", "invalid_source_page", 502);
138
+ }
139
+ cursorJson(result.next_cursor);
140
+ let inserted = 0;
141
+ let duplicate = 0;
142
+ for (const event of result.events) {
143
+ const emitted = this.bridge.emitEvent(manifest.id, event);
144
+ if (emitted.duplicate)
145
+ duplicate += 1;
146
+ else
147
+ inserted += 1;
148
+ }
149
+ // Cursor advancement happens only after every event in this page is
150
+ // durably accepted. A crash in the gap replays the page and is absorbed by
151
+ // the event dedupe key rather than losing source facts.
152
+ const checkpoint = this.checkpoints.commit(manifest, result.next_cursor, before?.revision ?? 0);
153
+ return {
154
+ source: manifest.id,
155
+ adapter_version: manifest.version,
156
+ checkpoint_revision: checkpoint.revision,
157
+ events_seen: result.events.length,
158
+ events_inserted: inserted,
159
+ events_duplicate: duplicate,
160
+ has_more: result.has_more === true,
161
+ };
162
+ }
163
+ }
164
+ //# sourceMappingURL=source-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-adapter.js","sourceRoot":"","sources":["../../src/source-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { WakeBridge } from "./core.js";
2
+ import { SourceCheckpointStore } from "./source-adapter.js";
3
+ import type { PullSourceAdapter, SourceAdapterManifest, SourceConnectorConfig, SourcePollContext, SourcePollResult, SourceSupervisorStatus, SourceVerification } from "./types.js";
4
+ export declare function validateSourceConnectorConfig(value: SourceConnectorConfig): Required<SourceConnectorConfig>;
5
+ export declare function loadSourceConnectorFile(path: string): Required<SourceConnectorConfig>[];
6
+ type Fetch = typeof fetch;
7
+ /** One connected poll view. Provider credentials never cross this boundary. */
8
+ export declare class HttpPullSourceAdapter implements PullSourceAdapter {
9
+ readonly manifest: SourceAdapterManifest;
10
+ private readonly config;
11
+ private readonly environment;
12
+ private readonly fetcher;
13
+ private constructor();
14
+ static connect(config: Required<SourceConnectorConfig>, environment?: NodeJS.ProcessEnv, fetcher?: Fetch): Promise<HttpPullSourceAdapter>;
15
+ poll(context: SourcePollContext): Promise<SourcePollResult>;
16
+ }
17
+ export declare function bootstrapSourceConnector(bridge: WakeBridge, rawConfig: SourceConnectorConfig, mode: "from-now", environment?: NodeJS.ProcessEnv, fetcher?: Fetch, expected?: {
18
+ subject_ref: string;
19
+ binding_fingerprint: string;
20
+ }): Promise<ReturnType<SourceCheckpointStore["commit"]>>;
21
+ export declare class SourceSupervisor {
22
+ private readonly environment;
23
+ private readonly fetcher;
24
+ private readonly runner;
25
+ private readonly controls;
26
+ private readonly slots;
27
+ private stopped;
28
+ constructor(bridge: WakeBridge, configs: SourceConnectorConfig[], environment?: NodeJS.ProcessEnv, fetcher?: Fetch);
29
+ list(): SourceSupervisorStatus[];
30
+ verify(source: string): Promise<SourceVerification>;
31
+ bootstrap(source: string, expected: {
32
+ subject_ref: string;
33
+ binding_fingerprint: string;
34
+ }): Promise<SourceVerification>;
35
+ rebind(source: string, expectedRevision: number, expected: {
36
+ subject_ref: string;
37
+ binding_fingerprint: string;
38
+ }, reason: string): Promise<SourceVerification>;
39
+ enable(source: string): Promise<SourceSupervisorStatus>;
40
+ disable(source: string): Promise<SourceSupervisorStatus>;
41
+ start(): void;
42
+ runOnce(source: string): Promise<SourceSupervisorStatus>;
43
+ private slot;
44
+ private refreshCheckpoint;
45
+ stop(): Promise<void>;
46
+ private schedule;
47
+ private launch;
48
+ private run;
49
+ }
50
+ export {};
@@ -0,0 +1,358 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { BridgeError } from "./core.js";
3
+ import { SourceCheckpointStore, SourceControlStore, SourceRunner } from "./source-adapter.js";
4
+ import { validateSourceManifest } from "./source-sdk.js";
5
+ import { WakeBridgeSdkError } from "./sdk-error.js";
6
+ const SOURCE_ID = /^[A-Za-z0-9_.:-]{1,128}$/u;
7
+ const ENV_NAME = /^[A-Z_][A-Z0-9_]{0,127}$/u;
8
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "[::1]", "::1"]);
9
+ function integer(value, fallback, min, max, field) {
10
+ const candidate = value === undefined ? fallback : Number(value);
11
+ if (!Number.isSafeInteger(candidate) || candidate < min || candidate > max) {
12
+ throw new BridgeError(`${field} is invalid`, "invalid_source_connector", 400);
13
+ }
14
+ return candidate;
15
+ }
16
+ export function validateSourceConnectorConfig(value) {
17
+ if (!value || !SOURCE_ID.test(value.id || "")) {
18
+ throw new BridgeError("source connector id is invalid", "invalid_source_connector", 400);
19
+ }
20
+ if (!ENV_NAME.test(value.token_env || "")) {
21
+ throw new BridgeError(`source connector ${value.id} token_env is invalid`, "invalid_source_connector", 400);
22
+ }
23
+ let url;
24
+ try {
25
+ url = new URL(value.base_url);
26
+ }
27
+ catch {
28
+ throw new BridgeError(`source connector ${value.id} base_url is invalid`, "invalid_source_connector", 400);
29
+ }
30
+ if (url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname) || url.username || url.password
31
+ || (url.pathname !== "/" && url.pathname !== "") || url.search || url.hash) {
32
+ throw new BridgeError(`source connector ${value.id} must use a loopback HTTP origin`, "unsafe_source_connector", 409);
33
+ }
34
+ return {
35
+ id: value.id,
36
+ base_url: url.origin,
37
+ token_env: value.token_env,
38
+ enabled: value.enabled !== false,
39
+ poll_interval_ms: integer(value.poll_interval_ms, 60_000, 1_000, 86_400_000, "poll_interval_ms"),
40
+ max_backoff_ms: integer(value.max_backoff_ms, 900_000, 1_000, 86_400_000, "max_backoff_ms"),
41
+ limit: integer(value.limit, 100, 1, 1000, "limit"),
42
+ max_pages_per_cycle: integer(value.max_pages_per_cycle, 4, 1, 100, "max_pages_per_cycle"),
43
+ };
44
+ }
45
+ export function loadSourceConnectorFile(path) {
46
+ let document;
47
+ try {
48
+ document = JSON.parse(readFileSync(path, "utf8"));
49
+ }
50
+ catch (error) {
51
+ throw new BridgeError(`cannot read source connector file: ${error instanceof Error ? error.message : String(error)}`, "invalid_source_connector_file", 400);
52
+ }
53
+ const file = document;
54
+ if (!file || file.version !== 1 || !Array.isArray(file.sources)) {
55
+ throw new BridgeError("source connector file must have version 1 and a sources array", "invalid_source_connector_file", 400);
56
+ }
57
+ const sources = file.sources.map(validateSourceConnectorConfig);
58
+ const ids = new Set();
59
+ for (const source of sources) {
60
+ if (ids.has(source.id))
61
+ throw new BridgeError(`duplicate source connector id: ${source.id}`, "invalid_source_connector_file", 400);
62
+ ids.add(source.id);
63
+ }
64
+ return sources;
65
+ }
66
+ async function connectorJson(fetcher, config, environment, path, body) {
67
+ const token = environment[config.token_env];
68
+ if (!token)
69
+ throw new BridgeError(`source connector ${config.id} token is unavailable`, "source_connector_auth_unavailable", 503);
70
+ let response;
71
+ try {
72
+ response = await fetcher(`${config.base_url}${path}`, {
73
+ method: body === undefined ? "GET" : "POST",
74
+ headers: { authorization: `Bearer ${token}`, accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json" }) },
75
+ body: body === undefined ? undefined : JSON.stringify(body),
76
+ signal: AbortSignal.timeout(15_000),
77
+ });
78
+ }
79
+ catch (error) {
80
+ throw new BridgeError(`source connector ${config.id} is unavailable: ${error instanceof Error ? error.message : String(error)}`, "source_connector_unavailable", 503);
81
+ }
82
+ if (!response.ok) {
83
+ const errorClass = response.status === 401 || response.status === 403
84
+ ? "source_connector_unauthorized"
85
+ : response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500
86
+ ? "source_connector_http_error"
87
+ : "source_connector_contract_error";
88
+ throw new BridgeError(`source connector ${config.id} returned HTTP ${response.status}`, errorClass, 502);
89
+ }
90
+ try {
91
+ return await response.json();
92
+ }
93
+ catch {
94
+ throw new BridgeError(`source connector ${config.id} returned invalid JSON`, "source_connector_invalid_json", 502);
95
+ }
96
+ }
97
+ /** One connected poll view. Provider credentials never cross this boundary. */
98
+ export class HttpPullSourceAdapter {
99
+ manifest;
100
+ config;
101
+ environment;
102
+ fetcher;
103
+ constructor(manifest, config, environment, fetcher) {
104
+ this.manifest = manifest;
105
+ this.config = config;
106
+ this.environment = environment;
107
+ this.fetcher = fetcher;
108
+ }
109
+ static async connect(config, environment = process.env, fetcher = fetch) {
110
+ const manifest = await connectorJson(fetcher, config, environment, "/v1/manifest");
111
+ if (!manifest || manifest.id !== config.id) {
112
+ throw new BridgeError(`source connector ${config.id} manifest id does not match`, "source_connector_identity_mismatch", 409);
113
+ }
114
+ if (!["connector", "none"].includes(manifest.credential_custody)) {
115
+ throw new BridgeError(`source connector ${config.id} reports invalid credential custody`, "source_connector_identity_mismatch", 409);
116
+ }
117
+ return new HttpPullSourceAdapter(manifest, config, environment, fetcher);
118
+ }
119
+ async poll(context) {
120
+ return await connectorJson(this.fetcher, this.config, this.environment, "/v1/poll", context);
121
+ }
122
+ }
123
+ export async function bootstrapSourceConnector(bridge, rawConfig, mode, environment = process.env, fetcher = fetch, expected) {
124
+ const config = validateSourceConnectorConfig(rawConfig);
125
+ const adapter = await HttpPullSourceAdapter.connect(config, environment, fetcher);
126
+ const manifest = validateSourceManifest(adapter.manifest);
127
+ if (manifest.read_side_effects !== "none") {
128
+ throw new BridgeError(`source ${manifest.id} has ${manifest.read_side_effects} read side effects`, "unsafe_source_read", 409);
129
+ }
130
+ if (!expected || manifest.subject_ref !== expected.subject_ref || manifest.binding_fingerprint !== expected.binding_fingerprint) {
131
+ throw new BridgeError(`source ${manifest.id} identity was not explicitly confirmed`, "source_identity_unconfirmed", 409);
132
+ }
133
+ const checkpoints = new SourceCheckpointStore(bridge);
134
+ if (checkpoints.get(manifest.id)) {
135
+ throw new BridgeError(`source ${manifest.id} already has a checkpoint`, "source_already_bootstrapped", 409);
136
+ }
137
+ const result = await connectorJson(fetcher, config, environment, "/v1/bootstrap", { mode });
138
+ if (!result || typeof result !== "object" || !("cursor" in result)) {
139
+ throw new BridgeError(`source connector ${config.id} returned an invalid bootstrap`, "source_connector_invalid_bootstrap", 502);
140
+ }
141
+ return checkpoints.commit(manifest, result.cursor, 0);
142
+ }
143
+ export class SourceSupervisor {
144
+ environment;
145
+ fetcher;
146
+ runner;
147
+ controls;
148
+ slots = new Map();
149
+ stopped = false;
150
+ constructor(bridge, configs, environment = process.env, fetcher = fetch) {
151
+ this.environment = environment;
152
+ this.fetcher = fetcher;
153
+ this.runner = new SourceRunner(bridge);
154
+ this.controls = new SourceControlStore(bridge);
155
+ for (const raw of configs) {
156
+ const config = validateSourceConnectorConfig(raw);
157
+ if (this.slots.has(config.id))
158
+ throw new BridgeError(`duplicate source connector id: ${config.id}`, "invalid_source_connector", 400);
159
+ config.enabled = this.controls.get(config.id) ?? config.enabled;
160
+ const checkpoint = this.runner.checkpoints.get(config.id);
161
+ this.slots.set(config.id, {
162
+ config,
163
+ status: {
164
+ source: config.id, enabled: config.enabled, state: config.enabled ? "idle" : "stopped",
165
+ last_started_at: null, last_success_at: null, last_error_at: null, last_error_class: null,
166
+ consecutive_failures: 0, next_poll_at: null,
167
+ checkpoint_revision: checkpoint?.revision ?? 0,
168
+ checkpoint_subject_ref: checkpoint?.subject_ref ?? null,
169
+ checkpoint_binding_fingerprint: checkpoint?.binding_fingerprint ?? null,
170
+ last_run: null,
171
+ },
172
+ });
173
+ }
174
+ }
175
+ list() {
176
+ return [...this.slots.values()].map((slot) => ({ ...slot.status, last_run: slot.status.last_run ? { ...slot.status.last_run } : null }));
177
+ }
178
+ async verify(source) {
179
+ const slot = this.slot(source);
180
+ const adapter = await HttpPullSourceAdapter.connect(slot.config, this.environment, this.fetcher);
181
+ const manifest = validateSourceManifest(adapter.manifest);
182
+ const storedCheckpoint = this.runner.checkpoints.get(source);
183
+ const checkpoint = storedCheckpoint ? {
184
+ source: storedCheckpoint.source,
185
+ adapter_version: storedCheckpoint.adapter_version,
186
+ subject_ref: storedCheckpoint.subject_ref,
187
+ binding_fingerprint: storedCheckpoint.binding_fingerprint,
188
+ revision: storedCheckpoint.revision,
189
+ updated_at: storedCheckpoint.updated_at,
190
+ } : null;
191
+ return {
192
+ source,
193
+ manifest,
194
+ checkpoint,
195
+ binding_matches: Boolean(storedCheckpoint && storedCheckpoint.subject_ref === manifest.subject_ref
196
+ && storedCheckpoint.binding_fingerprint === manifest.binding_fingerprint),
197
+ enabled: slot.config.enabled,
198
+ };
199
+ }
200
+ async bootstrap(source, expected) {
201
+ const slot = this.slot(source);
202
+ if (slot.config.enabled)
203
+ throw new BridgeError("disable the source before bootstrap", "source_must_be_disabled", 409);
204
+ await bootstrapSourceConnector(this.runner.bridge, slot.config, "from-now", this.environment, this.fetcher, expected);
205
+ this.refreshCheckpoint(slot);
206
+ return await this.verify(source);
207
+ }
208
+ async rebind(source, expectedRevision, expected, reason) {
209
+ const slot = this.slot(source);
210
+ if (slot.config.enabled)
211
+ throw new BridgeError("disable the source before rebind", "source_must_be_disabled", 409);
212
+ if (!reason || reason.length > 300)
213
+ throw new BridgeError("source rebind reason is invalid", "invalid_arguments", 400);
214
+ const adapter = await HttpPullSourceAdapter.connect(slot.config, this.environment, this.fetcher);
215
+ const manifest = validateSourceManifest(adapter.manifest);
216
+ if (manifest.subject_ref !== expected.subject_ref || manifest.binding_fingerprint !== expected.binding_fingerprint) {
217
+ throw new BridgeError(`source ${source} identity was not explicitly confirmed`, "source_identity_unconfirmed", 409);
218
+ }
219
+ const result = await connectorJson(this.fetcher, slot.config, this.environment, "/v1/bootstrap", { mode: "from-now" });
220
+ if (!result || typeof result !== "object" || !("cursor" in result)) {
221
+ throw new BridgeError(`source connector ${source} returned an invalid bootstrap`, "source_connector_invalid_bootstrap", 502);
222
+ }
223
+ this.runner.checkpoints.replace(manifest, result.cursor, expectedRevision, reason);
224
+ this.refreshCheckpoint(slot);
225
+ return await this.verify(source);
226
+ }
227
+ async enable(source) {
228
+ const slot = this.slot(source);
229
+ const verified = await this.verify(source);
230
+ if (!verified.checkpoint || !verified.binding_matches) {
231
+ throw new BridgeError("source identity is not bound to its checkpoint", "source_identity_mismatch", 409);
232
+ }
233
+ this.controls.set(source, true);
234
+ slot.config.enabled = true;
235
+ slot.status.enabled = true;
236
+ if (!this.stopped && !slot.promise)
237
+ this.schedule(slot, 0);
238
+ return this.list().find((item) => item.source === source);
239
+ }
240
+ async disable(source) {
241
+ const slot = this.slot(source);
242
+ this.controls.set(source, false);
243
+ slot.config.enabled = false;
244
+ slot.status.enabled = false;
245
+ if (slot.timer)
246
+ clearTimeout(slot.timer);
247
+ slot.timer = undefined;
248
+ slot.status.next_poll_at = null;
249
+ await slot.promise;
250
+ slot.status.state = "stopped";
251
+ return this.list().find((item) => item.source === source);
252
+ }
253
+ start() {
254
+ this.stopped = false;
255
+ for (const slot of this.slots.values())
256
+ if (slot.config.enabled)
257
+ this.schedule(slot, 0);
258
+ }
259
+ async runOnce(source) {
260
+ const slot = this.slot(source);
261
+ if (slot.promise)
262
+ throw new BridgeError(`source connector ${source} is already polling`, "source_connector_busy", 409);
263
+ if (slot.timer)
264
+ clearTimeout(slot.timer);
265
+ slot.timer = undefined;
266
+ slot.promise = this.launch(slot);
267
+ await slot.promise;
268
+ return this.list().find((status) => status.source === source);
269
+ }
270
+ slot(source) {
271
+ const slot = this.slots.get(source);
272
+ if (!slot)
273
+ throw new BridgeError(`source connector ${source} is not configured`, "source_connector_not_found", 404);
274
+ return slot;
275
+ }
276
+ refreshCheckpoint(slot) {
277
+ const checkpoint = this.runner.checkpoints.get(slot.config.id);
278
+ slot.status.checkpoint_revision = checkpoint?.revision ?? 0;
279
+ slot.status.checkpoint_subject_ref = checkpoint?.subject_ref ?? null;
280
+ slot.status.checkpoint_binding_fingerprint = checkpoint?.binding_fingerprint ?? null;
281
+ }
282
+ async stop() {
283
+ this.stopped = true;
284
+ for (const slot of this.slots.values()) {
285
+ if (slot.timer)
286
+ clearTimeout(slot.timer);
287
+ slot.timer = undefined;
288
+ slot.status.next_poll_at = null;
289
+ }
290
+ await Promise.all([...this.slots.values()].map((slot) => slot.promise).filter(Boolean));
291
+ for (const slot of this.slots.values())
292
+ slot.status.state = "stopped";
293
+ }
294
+ schedule(slot, delay) {
295
+ if (this.stopped || !slot.config.enabled)
296
+ return;
297
+ if (slot.timer)
298
+ clearTimeout(slot.timer);
299
+ slot.status.next_poll_at = new Date(Date.now() + delay).toISOString();
300
+ slot.timer = setTimeout(() => {
301
+ slot.timer = undefined;
302
+ if (slot.promise)
303
+ return;
304
+ slot.promise = this.launch(slot);
305
+ }, delay);
306
+ slot.timer.unref();
307
+ }
308
+ launch(slot) {
309
+ let nextDelay = null;
310
+ return this.run(slot)
311
+ .then((delay) => { nextDelay = delay; })
312
+ .finally(() => {
313
+ slot.promise = undefined;
314
+ if (nextDelay !== null)
315
+ this.schedule(slot, nextDelay);
316
+ });
317
+ }
318
+ async run(slot) {
319
+ slot.status.state = "polling";
320
+ slot.status.last_started_at = new Date().toISOString();
321
+ slot.status.next_poll_at = null;
322
+ try {
323
+ const adapter = await HttpPullSourceAdapter.connect(slot.config, this.environment, this.fetcher);
324
+ let result = await this.runner.runOnce(adapter, { limit: slot.config.limit });
325
+ const aggregate = { ...result };
326
+ for (let page = 1; result.has_more && page < slot.config.max_pages_per_cycle; page += 1) {
327
+ result = await this.runner.runOnce(adapter, { limit: slot.config.limit });
328
+ aggregate.checkpoint_revision = result.checkpoint_revision;
329
+ aggregate.events_seen += result.events_seen;
330
+ aggregate.events_inserted += result.events_inserted;
331
+ aggregate.events_duplicate += result.events_duplicate;
332
+ aggregate.has_more = result.has_more;
333
+ }
334
+ slot.status.state = "healthy";
335
+ slot.status.last_success_at = new Date().toISOString();
336
+ slot.status.last_error_at = null;
337
+ slot.status.last_error_class = null;
338
+ slot.status.consecutive_failures = 0;
339
+ slot.status.checkpoint_revision = aggregate.checkpoint_revision;
340
+ this.refreshCheckpoint(slot);
341
+ slot.status.last_run = aggregate;
342
+ return aggregate.has_more ? 0 : slot.config.poll_interval_ms;
343
+ }
344
+ catch (error) {
345
+ const errorClass = error instanceof WakeBridgeSdkError ? error.code : "source_connector_error";
346
+ const retryable = errorClass === "source_connector_unavailable" || errorClass === "source_connector_http_error";
347
+ slot.status.state = retryable ? "backoff" : "needs_attention";
348
+ slot.status.last_error_at = new Date().toISOString();
349
+ slot.status.last_error_class = errorClass;
350
+ slot.status.consecutive_failures += 1;
351
+ if (retryable) {
352
+ return Math.min(slot.config.max_backoff_ms, slot.config.poll_interval_ms * (2 ** Math.min(10, slot.status.consecutive_failures - 1)));
353
+ }
354
+ return null;
355
+ }
356
+ }
357
+ }
358
+ //# sourceMappingURL=source-connector.js.map