@scopebond/hook 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +27 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +116 -15
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cloud.d.ts +24 -2
  6. package/dist/cloud.d.ts.map +1 -1
  7. package/dist/cloud.js +77 -9
  8. package/dist/cloud.js.map +1 -1
  9. package/dist/delivery-report.d.ts +16 -0
  10. package/dist/delivery-report.d.ts.map +1 -0
  11. package/dist/delivery-report.js +51 -0
  12. package/dist/delivery-report.js.map +1 -0
  13. package/dist/delivery-state.d.ts +30 -0
  14. package/dist/delivery-state.d.ts.map +1 -0
  15. package/dist/delivery-state.js +86 -0
  16. package/dist/delivery-state.js.map +1 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/install.d.ts.map +1 -1
  22. package/dist/install.js +8 -2
  23. package/dist/install.js.map +1 -1
  24. package/dist/managed.d.ts +3 -1
  25. package/dist/managed.d.ts.map +1 -1
  26. package/dist/managed.js +50 -4
  27. package/dist/managed.js.map +1 -1
  28. package/dist/obs-emitter.d.ts.map +1 -1
  29. package/dist/obs-emitter.js +2 -2
  30. package/dist/obs-emitter.js.map +1 -1
  31. package/dist/policy-sync.d.ts.map +1 -1
  32. package/dist/policy-sync.js +6 -2
  33. package/dist/policy-sync.js.map +1 -1
  34. package/dist/proof.d.ts +1 -1
  35. package/dist/proof.d.ts.map +1 -1
  36. package/dist/proof.js +2 -1
  37. package/dist/proof.js.map +1 -1
  38. package/dist/recover.d.ts +33 -0
  39. package/dist/recover.d.ts.map +1 -0
  40. package/dist/recover.js +201 -0
  41. package/dist/recover.js.map +1 -0
  42. package/dist/runtime.d.ts +2 -0
  43. package/dist/runtime.d.ts.map +1 -1
  44. package/dist/runtime.js +12 -2
  45. package/dist/runtime.js.map +1 -1
  46. package/dist/version.d.ts +4 -4
  47. package/dist/version.d.ts.map +1 -1
  48. package/dist/version.js +5 -5
  49. package/dist/version.js.map +1 -1
  50. package/package.json +3 -3
package/dist/cloud.d.ts CHANGED
@@ -12,14 +12,36 @@ export interface HookConnection extends CloudEnrollmentResult {
12
12
  * is, so there is no fallback: without it the hook reports observations unsupported
13
13
  * rather than guessing a generation. */
14
14
  installation_generation?: number;
15
+ /** Where this machine sends its records: the workspace's regional ingest address, named by
16
+ * the enrollment answer. Absent from older answers and connections; `url` is used then. */
17
+ ingest_url?: string;
15
18
  }
19
+ /** The address records, observations and recovery go to: the enrollment's `ingest_url` when it
20
+ * is a valid HTTPS origin (or localhost in development), otherwise the workspace URL. Sign-in,
21
+ * policy and the portal always use `url`. */
22
+ export declare function ingestUrl(connection: Pick<HookConnection, "url" | "ingest_url">): string;
16
23
  export declare const connectionPath: (dir: string) => string;
17
24
  /** Read the persisted connection, or null when the hook is not connected to Cloud. */
18
25
  export declare function loadConnection(dir: string): HookConnection | null;
19
26
  /** Enroll the machine's attester with a workspace using the portal's one-use handoff,
20
27
  * then persist the scoped machine credential. The attester whose possession is proved
21
- * here is the same key the hook countersigns receipts with, so ingest accepts them. */
22
- export declare function connectCloud(dir: string, url: string, bundle: CloudEnrollmentBundle, fetchImpl?: typeof fetch): Promise<HookConnection>;
28
+ * here is the same key the hook countersigns receipts with, so ingest accepts them.
29
+ *
30
+ * A workspace refuses a key it already knows — one revoked when the computer was
31
+ * replaced or disconnected, or one already enrolled elsewhere — without spending the
32
+ * token. Reconnecting then replaces the key and enrolls again with the same token, so a
33
+ * second `login` always works; the old key is kept under retired-keys/, and queued
34
+ * receipts it signed leave the delivery queue (they stay in the local log for
35
+ * `recover`), because the new connection can never deliver them. */
36
+ export declare function connectCloud(dir: string, url: string, bundle: CloudEnrollmentBundle, fetchImpl?: typeof fetch): Promise<HookConnection & {
37
+ rotatedFrom?: string;
38
+ setAside?: number;
39
+ }>;
40
+ /** Where a replaced countersigning key is kept: receipts it signed stay verifiable, and
41
+ * `recover` can still deliver the ones the workspace never received. */
42
+ export declare const retiredKeysDir: (dir: string) => string;
43
+ /** Move the current countersigning key aside so the next load creates a fresh one. */
44
+ export declare function retireAttesterKey(dir: string, kid: string): string;
23
45
  /** Wrap a receipt store so every stored receipt is enqueued to a durable outbox and
24
46
  * exported to Cloud. Returns the wrapped store and the exporter (flush + stop). */
25
47
  export declare function attachExporter(outboxDbPath: string, connection: HookConnection, store: ReceiptStore, fetchImpl?: typeof fetch): {
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAC9F,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAwB,MAAM,yBAAyB,CAAC;AAElF;8EAC8E;AAC9E,MAAM,WAAW,cAAe,SAAQ,qBAAqB;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ;;2EAEuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;6CAEyC;IACzC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAiC,CAAC;AAE/E,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAQjE;AAED;;wFAEwF;AACxF,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAChF,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED;oFACoF;AACpF,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAC9F;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAI7E;AAED;kFACkF;AAClF,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3F"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAC9F,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAwB,MAAM,yBAAyB,CAAC;AAGlF;8EAC8E;AAC9E,MAAM,WAAW,cAAe,SAAQ,qBAAqB;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ;;2EAEuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;6CAEyC;IACzC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;gGAC4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;8CAE8C;AAC9C,wBAAgB,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,YAAY,CAAC,GAAG,MAAM,CAExF;AAYD,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAiC,CAAC;AAE/E,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAQjE;AAED;;;;;;;;;qEASqE;AACrE,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAChF,OAAO,CAAC,cAAc,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBvE;AAED;yEACyE;AACzE,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAmC,CAAC;AAEjF,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED;oFACoF;AACpF,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAC9F;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAQ7E;AAED;kFACkF;AAClF,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3F"}
package/dist/cloud.js CHANGED
@@ -3,10 +3,31 @@
3
3
  // hook starts monitoring automatically. Reuses the gateway's enrollment and bounded
4
4
  // durable exporter (D40 — no new transport). The machine credential and the complete
5
5
  // receipt log stay local-first; export is best-effort and never blocks a tool call.
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { completeCloudEnrollment, createCloudExporter, withCloudExporter, } from "@scopebond/gateway";
8
+ import { CloudEnrollmentError, completeCloudEnrollment, createCloudExporter, withCloudExporter, } from "@scopebond/gateway";
9
9
  import { SqliteCloudOutbox, loadOrCreateAttester } from "@scopebond/gateway/node";
10
+ import { LOSSLESS_OUTBOX } from "./delivery-report.js";
11
+ /** The address records, observations and recovery go to: the enrollment's `ingest_url` when it
12
+ * is a valid HTTPS origin (or localhost in development), otherwise the workspace URL. Sign-in,
13
+ * policy and the portal always use `url`. */
14
+ export function ingestUrl(connection) {
15
+ return safeIngestOrigin(connection.ingest_url) ?? connection.url;
16
+ }
17
+ function safeIngestOrigin(value) {
18
+ if (typeof value !== "string" || value.length > 2048)
19
+ return null;
20
+ try {
21
+ const parsed = new URL(value);
22
+ const local = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
23
+ if ((parsed.protocol !== "https:" && !(local && parsed.protocol === "http:")) || parsed.username || parsed.password)
24
+ return null;
25
+ return parsed.origin;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
10
31
  export const connectionPath = (dir) => join(dir, "cloud.json");
11
32
  /** Read the persisted connection, or null when the hook is not connected to Cloud. */
12
33
  export function loadConnection(dir) {
@@ -23,20 +44,67 @@ export function loadConnection(dir) {
23
44
  }
24
45
  /** Enroll the machine's attester with a workspace using the portal's one-use handoff,
25
46
  * then persist the scoped machine credential. The attester whose possession is proved
26
- * here is the same key the hook countersigns receipts with, so ingest accepts them. */
47
+ * here is the same key the hook countersigns receipts with, so ingest accepts them.
48
+ *
49
+ * A workspace refuses a key it already knows — one revoked when the computer was
50
+ * replaced or disconnected, or one already enrolled elsewhere — without spending the
51
+ * token. Reconnecting then replaces the key and enrolls again with the same token, so a
52
+ * second `login` always works; the old key is kept under retired-keys/, and queued
53
+ * receipts it signed leave the delivery queue (they stay in the local log for
54
+ * `recover`), because the new connection can never deliver them. */
27
55
  export async function connectCloud(dir, url, bundle, fetchImpl) {
28
- const { attester } = loadOrCreateAttester({ file: join(dir, "attester.key") });
56
+ let { attester } = loadOrCreateAttester({ file: join(dir, "attester.key") });
29
57
  const { attester: agent } = loadOrCreateAttester({ file: join(dir, "agent.key") });
30
- const result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });
31
- const connection = { url, ...result };
58
+ let rotatedFrom;
59
+ let result;
60
+ try {
61
+ result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });
62
+ }
63
+ catch (error) {
64
+ if (!(error instanceof CloudEnrollmentError) || error.code !== "gateway_key_conflict")
65
+ throw error;
66
+ rotatedFrom = attester.kid;
67
+ retireAttesterKey(dir, attester.kid);
68
+ attester = loadOrCreateAttester({ file: join(dir, "attester.key") }).attester;
69
+ result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });
70
+ }
71
+ const { ingest_url: offered, ...enrolled } = result;
72
+ const ingest = safeIngestOrigin(offered);
73
+ const connection = { url, ...enrolled, ...(ingest ? { ingest_url: ingest } : {}) };
32
74
  writeFileSync(connectionPath(dir), JSON.stringify(connection, null, 2) + "\n", { mode: 0o600 });
33
- return connection;
75
+ let setAside = 0;
76
+ const outboxPath = join(dir, "receipts.db.cloud-outbox.db");
77
+ if (existsSync(outboxPath)) {
78
+ const outbox = new SqliteCloudOutbox(outboxPath);
79
+ try {
80
+ setAside = outbox.discardNotSignedBy(attester.kid);
81
+ }
82
+ finally {
83
+ outbox.close();
84
+ }
85
+ }
86
+ return { ...connection, ...(rotatedFrom ? { rotatedFrom } : {}), ...(setAside ? { setAside } : {}) };
87
+ }
88
+ /** Where a replaced countersigning key is kept: receipts it signed stay verifiable, and
89
+ * `recover` can still deliver the ones the workspace never received. */
90
+ export const retiredKeysDir = (dir) => join(dir, "retired-keys");
91
+ /** Move the current countersigning key aside so the next load creates a fresh one. */
92
+ export function retireAttesterKey(dir, kid) {
93
+ const target = join(retiredKeysDir(dir), `${kid.replace(/[^A-Za-z0-9_-]/g, "-")}.key`);
94
+ const destination = existsSync(target) ? `${target}.${Date.now()}` : target;
95
+ mkdirSync(retiredKeysDir(dir), { recursive: true });
96
+ renameSync(join(dir, "attester.key"), destination);
97
+ return destination;
34
98
  }
35
99
  /** Wrap a receipt store so every stored receipt is enqueued to a durable outbox and
36
100
  * exported to Cloud. Returns the wrapped store and the exporter (flush + stop). */
37
101
  export function attachExporter(outboxDbPath, connection, store, fetchImpl) {
38
- const outbox = new SqliteCloudOutbox(outboxDbPath);
39
- const exporter = createCloudExporter({ url: connection.url, credential: connection.credential, outbox, fetch: fetchImpl });
102
+ // Lossless (SB275): no cap and no expiry. A record leaves the queue only when the workspace
103
+ // accepts it, or when a key change makes it undeliverable (`recover` then sends it). The
104
+ // gateway's defaults (10,000 records, 64 MiB, 7 days) dropped the newest records once a long
105
+ // outage filled the queue.
106
+ const outbox = new SqliteCloudOutbox(outboxDbPath, LOSSLESS_OUTBOX);
107
+ const exporter = createCloudExporter({ url: ingestUrl(connection), credential: connection.credential, outbox, fetch: fetchImpl });
40
108
  return { store: withCloudExporter(store, exporter), exporter, outbox };
41
109
  }
42
110
  /** Attempt delivery with a bounded timeout so a per-invocation hook never hangs the
package/dist/cloud.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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","sourcesContent":["// scopebond-hook → Scopebond Cloud: enroll the machine's countersigning key with a\n// workspace and auto-export signed receipts to the hosted portal, so a connected\n// hook starts monitoring automatically. Reuses the gateway's enrollment and bounded\n// durable exporter (D40 — no new transport). The machine credential and the complete\n// receipt log stay local-first; export is best-effort and never blocks a tool call.\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n completeCloudEnrollment, createCloudExporter, withCloudExporter,\n type CloudEnrollmentBundle, type CloudEnrollmentResult, type CloudExporter, type ReceiptStore,\n} from \"@scopebond/gateway\";\nimport { SqliteCloudOutbox, loadOrCreateAttester } from \"@scopebond/gateway/node\";\n\n/** The persisted connection between this machine and a Cloud workspace. Holds the\n * scoped machine credential; treat cloud.json as a secret (written 0600). */\nexport interface HookConnection extends CloudEnrollmentResult {\n url: string;\n /** The workspace's installation id for this machine. An enrollment answer names it\n * `installation_id`, or (older answers) as `gateway_id`, which is the same identifier;\n * observations use `gateway_id` when the explicit field is absent. */\n installation_id?: string;\n /** The installation generation the enrollment created. Only the workspace can say what it\n * is, so there is no fallback: without it the hook reports observations unsupported\n * rather than guessing a generation. */\n installation_generation?: number;\n}\n\nexport const connectionPath = (dir: string): string => join(dir, \"cloud.json\");\n\n/** Read the persisted connection, or null when the hook is not connected to Cloud. */\nexport function loadConnection(dir: string): HookConnection | null {\n const path = connectionPath(dir);\n if (!existsSync(path)) return null;\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<HookConnection>;\n if (typeof parsed.url === \"string\" && typeof parsed.credential === \"string\") return parsed as HookConnection;\n } catch { /* fall through */ }\n return null;\n}\n\n/** Enroll the machine's attester with a workspace using the portal's one-use handoff,\n * then persist the scoped machine credential. The attester whose possession is proved\n * here is the same key the hook countersigns receipts with, so ingest accepts them. */\nexport async function connectCloud(\n dir: string, url: string, bundle: CloudEnrollmentBundle, fetchImpl?: typeof fetch,\n): Promise<HookConnection> {\n const { attester } = loadOrCreateAttester({ file: join(dir, \"attester.key\") });\n const { attester: agent } = loadOrCreateAttester({ file: join(dir, \"agent.key\") });\n const result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });\n const connection: HookConnection = { url, ...result };\n writeFileSync(connectionPath(dir), JSON.stringify(connection, null, 2) + \"\\n\", { mode: 0o600 });\n return connection;\n}\n\n/** Wrap a receipt store so every stored receipt is enqueued to a durable outbox and\n * exported to Cloud. Returns the wrapped store and the exporter (flush + stop). */\nexport function attachExporter(\n outboxDbPath: string, connection: HookConnection, store: ReceiptStore, fetchImpl?: typeof fetch,\n): { store: ReceiptStore; exporter: CloudExporter; outbox: SqliteCloudOutbox } {\n const outbox = new SqliteCloudOutbox(outboxDbPath);\n const exporter = createCloudExporter({ url: connection.url, credential: connection.credential, outbox, fetch: fetchImpl });\n return { store: withCloudExporter(store, exporter), exporter, outbox };\n}\n\n/** Attempt delivery with a bounded timeout so a per-invocation hook never hangs the\n * agent; undelivered receipts stay in the durable outbox and flush next time. */\nexport async function flushBounded(exporter: CloudExporter, timeoutMs = 3000): Promise<void> {\n await Promise.race([\n exporter.flush().catch(() => {}),\n new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, timeoutMs)).unref?.()),\n ]);\n}\n"]}
1
+ {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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","sourcesContent":["// scopebond-hook → Scopebond Cloud: enroll the machine's countersigning key with a\n// workspace and auto-export signed receipts to the hosted portal, so a connected\n// hook starts monitoring automatically. Reuses the gateway's enrollment and bounded\n// durable exporter (D40 — no new transport). The machine credential and the complete\n// receipt log stay local-first; export is best-effort and never blocks a tool call.\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n CloudEnrollmentError, completeCloudEnrollment, createCloudExporter, withCloudExporter,\n type CloudEnrollmentBundle, type CloudEnrollmentResult, type CloudExporter, type ReceiptStore,\n} from \"@scopebond/gateway\";\nimport { SqliteCloudOutbox, loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { LOSSLESS_OUTBOX } from \"./delivery-report.js\";\n\n/** The persisted connection between this machine and a Cloud workspace. Holds the\n * scoped machine credential; treat cloud.json as a secret (written 0600). */\nexport interface HookConnection extends CloudEnrollmentResult {\n url: string;\n /** The workspace's installation id for this machine. An enrollment answer names it\n * `installation_id`, or (older answers) as `gateway_id`, which is the same identifier;\n * observations use `gateway_id` when the explicit field is absent. */\n installation_id?: string;\n /** The installation generation the enrollment created. Only the workspace can say what it\n * is, so there is no fallback: without it the hook reports observations unsupported\n * rather than guessing a generation. */\n installation_generation?: number;\n /** Where this machine sends its records: the workspace's regional ingest address, named by\n * the enrollment answer. Absent from older answers and connections; `url` is used then. */\n ingest_url?: string;\n}\n\n/** The address records, observations and recovery go to: the enrollment's `ingest_url` when it\n * is a valid HTTPS origin (or localhost in development), otherwise the workspace URL. Sign-in,\n * policy and the portal always use `url`. */\nexport function ingestUrl(connection: Pick<HookConnection, \"url\" | \"ingest_url\">): string {\n return safeIngestOrigin(connection.ingest_url) ?? connection.url;\n}\n\nfunction safeIngestOrigin(value: unknown): string | null {\n if (typeof value !== \"string\" || value.length > 2048) return null;\n try {\n const parsed = new URL(value);\n const local = parsed.hostname === \"localhost\" || parsed.hostname === \"127.0.0.1\";\n if ((parsed.protocol !== \"https:\" && !(local && parsed.protocol === \"http:\")) || parsed.username || parsed.password) return null;\n return parsed.origin;\n } catch { return null; }\n}\n\nexport const connectionPath = (dir: string): string => join(dir, \"cloud.json\");\n\n/** Read the persisted connection, or null when the hook is not connected to Cloud. */\nexport function loadConnection(dir: string): HookConnection | null {\n const path = connectionPath(dir);\n if (!existsSync(path)) return null;\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<HookConnection>;\n if (typeof parsed.url === \"string\" && typeof parsed.credential === \"string\") return parsed as HookConnection;\n } catch { /* fall through */ }\n return null;\n}\n\n/** Enroll the machine's attester with a workspace using the portal's one-use handoff,\n * then persist the scoped machine credential. The attester whose possession is proved\n * here is the same key the hook countersigns receipts with, so ingest accepts them.\n *\n * A workspace refuses a key it already knows — one revoked when the computer was\n * replaced or disconnected, or one already enrolled elsewhere — without spending the\n * token. Reconnecting then replaces the key and enrolls again with the same token, so a\n * second `login` always works; the old key is kept under retired-keys/, and queued\n * receipts it signed leave the delivery queue (they stay in the local log for\n * `recover`), because the new connection can never deliver them. */\nexport async function connectCloud(\n dir: string, url: string, bundle: CloudEnrollmentBundle, fetchImpl?: typeof fetch,\n): Promise<HookConnection & { rotatedFrom?: string; setAside?: number }> {\n let { attester } = loadOrCreateAttester({ file: join(dir, \"attester.key\") });\n const { attester: agent } = loadOrCreateAttester({ file: join(dir, \"agent.key\") });\n let rotatedFrom: string | undefined;\n let result;\n try {\n result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });\n } catch (error) {\n if (!(error instanceof CloudEnrollmentError) || error.code !== \"gateway_key_conflict\") throw error;\n rotatedFrom = attester.kid;\n retireAttesterKey(dir, attester.kid);\n attester = loadOrCreateAttester({ file: join(dir, \"attester.key\") }).attester;\n result = await completeCloudEnrollment({ url, bundle, attester, agent, fetch: fetchImpl });\n }\n const { ingest_url: offered, ...enrolled } = result as typeof result & { ingest_url?: unknown };\n const ingest = safeIngestOrigin(offered);\n const connection: HookConnection = { url, ...enrolled, ...(ingest ? { ingest_url: ingest } : {}) };\n writeFileSync(connectionPath(dir), JSON.stringify(connection, null, 2) + \"\\n\", { mode: 0o600 });\n let setAside = 0;\n const outboxPath = join(dir, \"receipts.db.cloud-outbox.db\");\n if (existsSync(outboxPath)) {\n const outbox = new SqliteCloudOutbox(outboxPath);\n try { setAside = outbox.discardNotSignedBy(attester.kid); } finally { outbox.close(); }\n }\n return { ...connection, ...(rotatedFrom ? { rotatedFrom } : {}), ...(setAside ? { setAside } : {}) };\n}\n\n/** Where a replaced countersigning key is kept: receipts it signed stay verifiable, and\n * `recover` can still deliver the ones the workspace never received. */\nexport const retiredKeysDir = (dir: string): string => join(dir, \"retired-keys\");\n\n/** Move the current countersigning key aside so the next load creates a fresh one. */\nexport function retireAttesterKey(dir: string, kid: string): string {\n const target = join(retiredKeysDir(dir), `${kid.replace(/[^A-Za-z0-9_-]/g, \"-\")}.key`);\n const destination = existsSync(target) ? `${target}.${Date.now()}` : target;\n mkdirSync(retiredKeysDir(dir), { recursive: true });\n renameSync(join(dir, \"attester.key\"), destination);\n return destination;\n}\n\n/** Wrap a receipt store so every stored receipt is enqueued to a durable outbox and\n * exported to Cloud. Returns the wrapped store and the exporter (flush + stop). */\nexport function attachExporter(\n outboxDbPath: string, connection: HookConnection, store: ReceiptStore, fetchImpl?: typeof fetch,\n): { store: ReceiptStore; exporter: CloudExporter; outbox: SqliteCloudOutbox } {\n // Lossless (SB275): no cap and no expiry. A record leaves the queue only when the workspace\n // accepts it, or when a key change makes it undeliverable (`recover` then sends it). The\n // gateway's defaults (10,000 records, 64 MiB, 7 days) dropped the newest records once a long\n // outage filled the queue.\n const outbox = new SqliteCloudOutbox(outboxDbPath, LOSSLESS_OUTBOX);\n const exporter = createCloudExporter({ url: ingestUrl(connection), credential: connection.credential, outbox, fetch: fetchImpl });\n return { store: withCloudExporter(store, exporter), exporter, outbox };\n}\n\n/** Attempt delivery with a bounded timeout so a per-invocation hook never hangs the\n * agent; undelivered receipts stay in the durable outbox and flush next time. */\nexport async function flushBounded(exporter: CloudExporter, timeoutMs = 3000): Promise<void> {\n await Promise.race([\n exporter.flush().catch(() => {}),\n new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, timeoutMs)).unref?.()),\n ]);\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { HookConnection } from "./cloud.js";
2
+ export declare const OUTBOX_FILE = "receipts.db.cloud-outbox.db";
3
+ /** The queue opened without limits, so reading it never expires or drops anything. */
4
+ export declare const LOSSLESS_OUTBOX: {
5
+ readonly maxPending: number;
6
+ readonly maxBytes: number;
7
+ readonly maxAgeMs: number;
8
+ };
9
+ export interface DeliveryReport {
10
+ lines: string[];
11
+ problems: string[];
12
+ /** The one command that fixes a refused connection, when the connection was refused. */
13
+ fix: string | null;
14
+ }
15
+ export declare function describeDelivery(dir: string, connection: Pick<HookConnection, "url">, now?: number): DeliveryReport;
16
+ //# sourceMappingURL=delivery-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-report.d.ts","sourceRoot":"","sources":["../src/delivery-report.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,eAAO,MAAM,WAAW,gCAAgC,CAAC;AAEzD,sFAAsF;AACtF,eAAO,MAAM,eAAe;;;;CAAyH,CAAC;AAEtJ,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wFAAwF;IACxF,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EAAE,GAAG,SAAa,GAAG,cAAc,CAmCvH"}
@@ -0,0 +1,51 @@
1
+ // SB273/SB274: what delivery to the workspace looks like from this computer — the persisted
2
+ // delivery state plus the queue — in the lines `status` prints and the problems `doctor` reports.
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { SqliteCloudOutbox } from "@scopebond/gateway/node";
6
+ import { ago, readDeliveryState } from "./delivery-state.js";
7
+ import { cliCommand } from "./version.js";
8
+ export const OUTBOX_FILE = "receipts.db.cloud-outbox.db";
9
+ /** The queue opened without limits, so reading it never expires or drops anything. */
10
+ export const LOSSLESS_OUTBOX = { maxPending: Number.MAX_SAFE_INTEGER, maxBytes: Number.MAX_SAFE_INTEGER, maxAgeMs: Number.MAX_SAFE_INTEGER };
11
+ export function describeDelivery(dir, connection, now = Date.now()) {
12
+ const state = readDeliveryState(dir);
13
+ const lines = [];
14
+ const problems = [];
15
+ let pending = 0;
16
+ let oldest = null;
17
+ const outboxPath = join(dir, OUTBOX_FILE);
18
+ if (existsSync(outboxPath)) {
19
+ try {
20
+ const outbox = new SqliteCloudOutbox(outboxPath, LOSSLESS_OUTBOX);
21
+ try {
22
+ const status = outbox.status();
23
+ pending = status.pending;
24
+ oldest = status.oldestEnqueuedAt;
25
+ }
26
+ finally {
27
+ outbox.close();
28
+ }
29
+ }
30
+ catch { /* an unreadable queue leaves the count at zero */ }
31
+ }
32
+ let fix = null;
33
+ if (state.invalid_since !== null) {
34
+ fix = cliCommand(`login ${connection.url}`);
35
+ lines.push(`NOT DELIVERING since ${new Date(state.invalid_since).toISOString()}: the workspace refused this computer's connection (it was revoked, replaced or removed).`);
36
+ lines.push(`fix: run ${fix}`);
37
+ problems.push(`the workspace no longer accepts this computer's connection; run ${fix}`);
38
+ }
39
+ const tried = state.last_attempt_at !== null ? `; last tried ${ago(state.last_attempt_at, now)}` : "";
40
+ lines.push(`last delivered ${ago(state.last_success_at, now)}${tried}`);
41
+ const age = oldest !== null ? `, oldest from ${ago(oldest, now)}` : "";
42
+ lines.push(`waiting to send ${pending} record(s)${age}`);
43
+ if (state.last_error && state.invalid_since === null) {
44
+ lines.push(`last problem ${state.last_error}`);
45
+ if (pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000) {
46
+ problems.push(`${pending} record(s) have waited more than an hour to send (${state.last_error}); they stay queued and send once the workspace accepts them`);
47
+ }
48
+ }
49
+ return { lines, problems, fix };
50
+ }
51
+ //# sourceMappingURL=delivery-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-report.js","sourceRoot":"","sources":["../src/delivery-report.ts"],"names":[],"mappings":"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","sourcesContent":["// SB273/SB274: what delivery to the workspace looks like from this computer — the persisted\n// delivery state plus the queue — in the lines `status` prints and the problems `doctor` reports.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { SqliteCloudOutbox } from \"@scopebond/gateway/node\";\nimport type { HookConnection } from \"./cloud.js\";\nimport { ago, readDeliveryState } from \"./delivery-state.js\";\nimport { cliCommand } from \"./version.js\";\n\nexport const OUTBOX_FILE = \"receipts.db.cloud-outbox.db\";\n\n/** The queue opened without limits, so reading it never expires or drops anything. */\nexport const LOSSLESS_OUTBOX = { maxPending: Number.MAX_SAFE_INTEGER, maxBytes: Number.MAX_SAFE_INTEGER, maxAgeMs: Number.MAX_SAFE_INTEGER } as const;\n\nexport interface DeliveryReport {\n lines: string[];\n problems: string[];\n /** The one command that fixes a refused connection, when the connection was refused. */\n fix: string | null;\n}\n\nexport function describeDelivery(dir: string, connection: Pick<HookConnection, \"url\">, now = Date.now()): DeliveryReport {\n const state = readDeliveryState(dir);\n const lines: string[] = [];\n const problems: string[] = [];\n let pending = 0;\n let oldest: number | null = null;\n const outboxPath = join(dir, OUTBOX_FILE);\n if (existsSync(outboxPath)) {\n try {\n const outbox = new SqliteCloudOutbox(outboxPath, LOSSLESS_OUTBOX);\n try {\n const status = outbox.status();\n pending = status.pending;\n oldest = status.oldestEnqueuedAt;\n } finally { outbox.close(); }\n } catch { /* an unreadable queue leaves the count at zero */ }\n }\n let fix: string | null = null;\n if (state.invalid_since !== null) {\n fix = cliCommand(`login ${connection.url}`);\n lines.push(`NOT DELIVERING since ${new Date(state.invalid_since).toISOString()}: the workspace refused this computer's connection (it was revoked, replaced or removed).`);\n lines.push(`fix: run ${fix}`);\n problems.push(`the workspace no longer accepts this computer's connection; run ${fix}`);\n }\n const tried = state.last_attempt_at !== null ? `; last tried ${ago(state.last_attempt_at, now)}` : \"\";\n lines.push(`last delivered ${ago(state.last_success_at, now)}${tried}`);\n const age = oldest !== null ? `, oldest from ${ago(oldest, now)}` : \"\";\n lines.push(`waiting to send ${pending} record(s)${age}`);\n if (state.last_error && state.invalid_since === null) {\n lines.push(`last problem ${state.last_error}`);\n if (pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000) {\n problems.push(`${pending} record(s) have waited more than an hour to send (${state.last_error}); they stay queued and send once the workspace accepts them`);\n }\n }\n return { lines, problems, fix };\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { CloudExporterStatus } from "@scopebond/gateway";
2
+ export declare const DELIVERY_STATE_FILE = "delivery.json";
3
+ export interface DeliveryState {
4
+ /** When this computer last tried to deliver records (ms since epoch). */
5
+ last_attempt_at: number | null;
6
+ /** When the workspace last accepted records from this computer. */
7
+ last_success_at: number | null;
8
+ /** The last delivery failure, in plain words, and its HTTP status when there was one. */
9
+ last_error: string | null;
10
+ last_status: number | null;
11
+ /** Set when the workspace refused this computer's credential (HTTP 401): the connection
12
+ * was revoked, replaced or removed, and only signing in again fixes it. */
13
+ invalid_since: number | null;
14
+ /** What saw the refusal: record delivery or the rules check. */
15
+ invalid_source: "delivery" | "rules" | null;
16
+ }
17
+ export declare function readDeliveryState(dir: string): DeliveryState;
18
+ /** Merge and save. Never throws: delivery state is diagnostic and must not affect a decision. */
19
+ export declare function writeDeliveryState(dir: string, patch: Partial<DeliveryState>): DeliveryState;
20
+ /** The HTTP status in an exporter error ("ingest failed: HTTP 401"), if any. */
21
+ export declare function httpStatusOf(message: string | null | undefined): number | null;
22
+ /** Record the outcome of one delivery attempt from the exporter's in-process status.
23
+ * `before` is the exporter's last success time before this attempt, so an attempt with
24
+ * nothing to send does not count as a delivery. */
25
+ export declare function recordDeliveryAttempt(dir: string, status: Pick<CloudExporterStatus, "lastSuccessAt" | "lastError" | "pending">, at: number, before?: number | null): DeliveryState;
26
+ /** The rules check saw a 401 (or a working connection again). */
27
+ export declare function recordRulesCredential(dir: string, valid: boolean, at: number): void;
28
+ /** "3 minutes ago", "2 hours ago", "4 days ago". */
29
+ export declare function ago(ms: number | null, now?: number): string;
30
+ //# sourceMappingURL=delivery-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-state.d.ts","sourceRoot":"","sources":["../src/delivery-state.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mEAAmE;IACnE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yFAAyF;IACzF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;gFAC4E;IAC5E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gEAAgE;IAChE,cAAc,EAAE,UAAU,GAAG,OAAO,GAAG,IAAI,CAAC;CAC7C;AAMD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAO5D;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAS5F;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAG9E;AAED;;oDAEoD;AACpD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE,eAAe,GAAG,WAAW,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,aAAa,CAgBxL;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAOnF;AAED,oDAAoD;AACpD,wBAAgB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,CASvE"}
@@ -0,0 +1,86 @@
1
+ // Delivery state that outlives the process. The hook runs once per tool call, so the
2
+ // exporter's own counters (last success, last error) vanish when it exits; without this
3
+ // file `status` could only say "connected" while every upload was being refused. Written
4
+ // after each delivery attempt and by policy sync; read by `status` and `doctor`.
5
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ export const DELIVERY_STATE_FILE = "delivery.json";
8
+ const EMPTY = {
9
+ last_attempt_at: null, last_success_at: null, last_error: null, last_status: null, invalid_since: null, invalid_source: null,
10
+ };
11
+ export function readDeliveryState(dir) {
12
+ const file = join(dir, DELIVERY_STATE_FILE);
13
+ if (!existsSync(file))
14
+ return { ...EMPTY };
15
+ try {
16
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
17
+ return { ...EMPTY, ...parsed };
18
+ }
19
+ catch {
20
+ return { ...EMPTY };
21
+ }
22
+ }
23
+ /** Merge and save. Never throws: delivery state is diagnostic and must not affect a decision. */
24
+ export function writeDeliveryState(dir, patch) {
25
+ const next = { ...readDeliveryState(dir), ...patch };
26
+ try {
27
+ const file = join(dir, DELIVERY_STATE_FILE);
28
+ const temp = `${file}.${process.pid}.tmp`;
29
+ writeFileSync(temp, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
30
+ renameSync(temp, file);
31
+ }
32
+ catch { /* best effort */ }
33
+ return next;
34
+ }
35
+ /** The HTTP status in an exporter error ("ingest failed: HTTP 401"), if any. */
36
+ export function httpStatusOf(message) {
37
+ const match = /HTTP (\d{3})/.exec(message ?? "");
38
+ return match ? Number(match[1]) : null;
39
+ }
40
+ /** Record the outcome of one delivery attempt from the exporter's in-process status.
41
+ * `before` is the exporter's last success time before this attempt, so an attempt with
42
+ * nothing to send does not count as a delivery. */
43
+ export function recordDeliveryAttempt(dir, status, at, before = null) {
44
+ const patch = { last_attempt_at: at };
45
+ if (status.lastError) {
46
+ const code = httpStatusOf(status.lastError);
47
+ patch.last_error = status.lastError;
48
+ patch.last_status = code;
49
+ if (code === 401) {
50
+ const current = readDeliveryState(dir);
51
+ patch.invalid_since = current.invalid_since ?? at;
52
+ patch.invalid_source = current.invalid_source ?? "delivery";
53
+ }
54
+ }
55
+ else if (status.lastSuccessAt !== null && status.lastSuccessAt !== before) {
56
+ // Accepted: the connection works, whatever an earlier run saw.
57
+ Object.assign(patch, { last_success_at: status.lastSuccessAt, last_error: null, last_status: null, invalid_since: null, invalid_source: null });
58
+ }
59
+ return writeDeliveryState(dir, patch);
60
+ }
61
+ /** The rules check saw a 401 (or a working connection again). */
62
+ export function recordRulesCredential(dir, valid, at) {
63
+ const current = readDeliveryState(dir);
64
+ if (valid) {
65
+ if (current.invalid_since !== null && current.invalid_source === "rules")
66
+ writeDeliveryState(dir, { invalid_since: null, invalid_source: null });
67
+ return;
68
+ }
69
+ writeDeliveryState(dir, { invalid_since: current.invalid_since ?? at, invalid_source: current.invalid_source ?? "rules" });
70
+ }
71
+ /** "3 minutes ago", "2 hours ago", "4 days ago". */
72
+ export function ago(ms, now = Date.now()) {
73
+ if (ms === null)
74
+ return "never";
75
+ const seconds = Math.max(0, Math.round((now - ms) / 1000));
76
+ if (seconds < 90)
77
+ return `${seconds} second${seconds === 1 ? "" : "s"} ago`;
78
+ const minutes = Math.round(seconds / 60);
79
+ if (minutes < 90)
80
+ return `${minutes} minutes ago`;
81
+ const hours = Math.round(minutes / 60);
82
+ if (hours < 36)
83
+ return `${hours} hours ago`;
84
+ return `${Math.round(hours / 24)} days ago`;
85
+ }
86
+ //# sourceMappingURL=delivery-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-state.js","sourceRoot":"","sources":["../src/delivery-state.ts"],"names":[],"mappings":"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","sourcesContent":["// Delivery state that outlives the process. The hook runs once per tool call, so the\n// exporter's own counters (last success, last error) vanish when it exits; without this\n// file `status` could only say \"connected\" while every upload was being refused. Written\n// after each delivery attempt and by policy sync; read by `status` and `doctor`.\n\nimport { existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { CloudExporterStatus } from \"@scopebond/gateway\";\n\nexport const DELIVERY_STATE_FILE = \"delivery.json\";\n\nexport interface DeliveryState {\n /** When this computer last tried to deliver records (ms since epoch). */\n last_attempt_at: number | null;\n /** When the workspace last accepted records from this computer. */\n last_success_at: number | null;\n /** The last delivery failure, in plain words, and its HTTP status when there was one. */\n last_error: string | null;\n last_status: number | null;\n /** Set when the workspace refused this computer's credential (HTTP 401): the connection\n * was revoked, replaced or removed, and only signing in again fixes it. */\n invalid_since: number | null;\n /** What saw the refusal: record delivery or the rules check. */\n invalid_source: \"delivery\" | \"rules\" | null;\n}\n\nconst EMPTY: DeliveryState = {\n last_attempt_at: null, last_success_at: null, last_error: null, last_status: null, invalid_since: null, invalid_source: null,\n};\n\nexport function readDeliveryState(dir: string): DeliveryState {\n const file = join(dir, DELIVERY_STATE_FILE);\n if (!existsSync(file)) return { ...EMPTY };\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as Partial<DeliveryState>;\n return { ...EMPTY, ...parsed };\n } catch { return { ...EMPTY }; }\n}\n\n/** Merge and save. Never throws: delivery state is diagnostic and must not affect a decision. */\nexport function writeDeliveryState(dir: string, patch: Partial<DeliveryState>): DeliveryState {\n const next = { ...readDeliveryState(dir), ...patch };\n try {\n const file = join(dir, DELIVERY_STATE_FILE);\n const temp = `${file}.${process.pid}.tmp`;\n writeFileSync(temp, JSON.stringify(next, null, 2) + \"\\n\", { mode: 0o600 });\n renameSync(temp, file);\n } catch { /* best effort */ }\n return next;\n}\n\n/** The HTTP status in an exporter error (\"ingest failed: HTTP 401\"), if any. */\nexport function httpStatusOf(message: string | null | undefined): number | null {\n const match = /HTTP (\\d{3})/.exec(message ?? \"\");\n return match ? Number(match[1]) : null;\n}\n\n/** Record the outcome of one delivery attempt from the exporter's in-process status.\n * `before` is the exporter's last success time before this attempt, so an attempt with\n * nothing to send does not count as a delivery. */\nexport function recordDeliveryAttempt(dir: string, status: Pick<CloudExporterStatus, \"lastSuccessAt\" | \"lastError\" | \"pending\">, at: number, before: number | null = null): DeliveryState {\n const patch: Partial<DeliveryState> = { last_attempt_at: at };\n if (status.lastError) {\n const code = httpStatusOf(status.lastError);\n patch.last_error = status.lastError;\n patch.last_status = code;\n if (code === 401) {\n const current = readDeliveryState(dir);\n patch.invalid_since = current.invalid_since ?? at;\n patch.invalid_source = current.invalid_source ?? \"delivery\";\n }\n } else if (status.lastSuccessAt !== null && status.lastSuccessAt !== before) {\n // Accepted: the connection works, whatever an earlier run saw.\n Object.assign(patch, { last_success_at: status.lastSuccessAt, last_error: null, last_status: null, invalid_since: null, invalid_source: null });\n }\n return writeDeliveryState(dir, patch);\n}\n\n/** The rules check saw a 401 (or a working connection again). */\nexport function recordRulesCredential(dir: string, valid: boolean, at: number): void {\n const current = readDeliveryState(dir);\n if (valid) {\n if (current.invalid_since !== null && current.invalid_source === \"rules\") writeDeliveryState(dir, { invalid_since: null, invalid_source: null });\n return;\n }\n writeDeliveryState(dir, { invalid_since: current.invalid_since ?? at, invalid_source: current.invalid_source ?? \"rules\" });\n}\n\n/** \"3 minutes ago\", \"2 hours ago\", \"4 days ago\". */\nexport function ago(ms: number | null, now: number = Date.now()): string {\n if (ms === null) return \"never\";\n const seconds = Math.max(0, Math.round((now - ms) / 1000));\n if (seconds < 90) return `${seconds} second${seconds === 1 ? \"\" : \"s\"} ago`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 90) return `${minutes} minutes ago`;\n const hours = Math.round(minutes / 60);\n if (hours < 36) return `${hours} hours ago`;\n return `${Math.round(hours / 24)} days ago`;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export { explainDeny, describeAction, findClause } from "./explain.js";
29
29
  export type { ExplainDenyInput, ExplainIntent, ExplainPolicy, ExplainClause } from "./explain.js";
30
30
  export { ensureDurableRuntime, isEphemeralPath, nodeModulesRootOf, runtimeRoot, runtimeDirFor, pinnedCliPath, } from "./runtime-install.js";
31
31
  export type { PinResult } from "./runtime-install.js";
32
- export { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
32
+ export { connectCloud, ingestUrl, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
33
33
  export type { HookConnection } from "./cloud.js";
34
34
  export { scrubSecrets, scrubParam, redactCommand, digest, sha256, keyedDigest, useDigestKey, loadOrCreateDigestKey } from "./minimize.js";
35
35
  export { AGENT_KINDS, assertAgentKind, observationSigner, signObservation, observationHash, sourceReceiptHash, signingBytes, buildPayload, buildOperation, operationsForCall, bindingKeyFromHex, loadOrCreateBindingKey, digestPolicy, REQUEST_BINDING_DOMAIN, MAX_OBSERVATION_BYTES, MAX_BATCH_ITEMS, MAX_BATCH_BODY_BYTES, } from "./observation.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ export { scaffold, harnessSnippet, installHarness, placeHook } from "./init.js";
19
19
  export { userHome, userHarnessFile, projectHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig, cursorDetected, codexDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, readHarnessConfig, trustProjectPolicy, isTrustedProject, untrustedProjectPolicy, trustedProjectsFile, harnessScopes, harnessScopeLabel, configuredHookCommands, hookCommandResolves, isScopebondHookCommand, harnessEntryMatches, localHarnessFile, isMachineSpecificCommand, gitShareState, excludeFromGit, pruneHarnessEntries, } from "./install.js";
20
20
  export { explainDeny, describeAction, findClause } from "./explain.js";
21
21
  export { ensureDurableRuntime, isEphemeralPath, nodeModulesRootOf, runtimeRoot, runtimeDirFor, pinnedCliPath, } from "./runtime-install.js";
22
- export { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
22
+ export { connectCloud, ingestUrl, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
23
23
  export { scrubSecrets, scrubParam, redactCommand, digest, sha256, keyedDigest, useDigestKey, loadOrCreateDigestKey } from "./minimize.js";
24
24
  export { AGENT_KINDS, assertAgentKind, observationSigner, signObservation, observationHash, sourceReceiptHash, signingBytes, buildPayload, buildOperation, operationsForCall, bindingKeyFromHex, loadOrCreateBindingKey, digestPolicy, REQUEST_BINDING_DOMAIN, MAX_OBSERVATION_BYTES, MAX_BATCH_ITEMS, MAX_BATCH_BODY_BYTES, } from "./observation.js";
25
25
  export { ObservationStore, OBSERVATION_DB } from "./obs-store.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// @scopebond/hook — the Claude Code, Cursor + Codex connector. The library surface is\n// the deterministic mapper and the check-only runtime; the `scopebond-hook`\n// binary is the thin harness adapter over them.\n\nexport { mapClaudeToolUse, mapCodexToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nexport type { Mapped, NormalizedIntent } from \"./map.js\";\nexport { createHookRuntime, starterPolicy, upgradeStarterPolicy } from \"./runtime.js\";\nexport type { RuntimeConfig, Decision } from \"./runtime.js\";\nexport {\n compile, defaultRules, describeRules, loadRules, saveRules, rulesPath, pathRuleFor, RULES_FILE,\n} from \"./rules.js\";\nexport type { RuleSet, PathRule } from \"./rules.js\";\nexport { pathRuleMatches, isDestructiveProgram, isProtectedBranch } from \"./rules.js\";\nexport { classifyIntent, classificationBlocks, BLOCKING } from \"./classify.js\";\nexport type { CatalogId, Classification } from \"./classify.js\";\nexport { classifyRoot, applyRootScope } from \"./paths.js\";\nexport type { RootScope, RootOptions } from \"./paths.js\";\nexport { actionGroupId, withActionGroup, distinctTargets, ACTION_GROUP_PARAM, ACTION_GROUP_SIZE_PARAM, ACTION_GROUP_SEQ_PARAM } from \"./group.js\";\nexport { computeManifest, renderManifest, cellState, cellKey, vectorsForCell, vectorDigest } from \"./capabilities.js\";\nexport type { Manifest, CapabilityCell, CapabilityState, ProofRecord, HostVariant, EventPhase, ManifestInput } from \"./capabilities.js\";\nexport { runProofFixtures, deliverProofReceipts, loadProofs, saveProofs, PROOF_FILE } from \"./proof.js\";\nexport { inspectBudgetExport, loadBudgetExport, localBudgetOf, BUDGET_EXPORT_TYPE } from \"./budget-load.js\";\nexport { inspectExport, loadPolicyExport, policyScopeDigest, policyBuilds, POLICY_SCOPE_DOMAIN, LOADED_POLICY_FILE } from \"./policy-load.js\";\nexport {\n inspectManaged, compileManaged, installManaged, restoreLocal, isManaged, readMeta, digestRules,\n MANAGED_RULE_IDS, MANAGED_DOC_FILE, MANAGED_META_FILE,\n} from \"./managed.js\";\nexport type { ManagedDocument, ManagedRule, ManagedMeta, RefusalReason } from \"./managed.js\";\nexport { syncPolicy, syncIfDue, SYNC_INTERVAL_MS, INLINE_BUDGET_MS } from \"./policy-sync.js\";\nexport type { SyncOutcome, SyncOptions } from \"./policy-sync.js\";\nexport { VECTORS, mapVector } from \"./vectors.js\";\nexport type { Vector, VectorAgent, Dialect } from \"./vectors.js\";\nexport { scaffold, harnessSnippet, installHarness, placeHook } from \"./init.js\";\nexport type { HookPlacement } from \"./init.js\";\nexport {\n userHome, userHarnessFile, projectHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig,\n cursorDetected, codexDetected, absoluteHookCommand, isHarnessConfigured, purgeHome,\n readHarnessConfig, trustProjectPolicy, isTrustedProject, untrustedProjectPolicy, trustedProjectsFile,\n harnessScopes, harnessScopeLabel, configuredHookCommands, hookCommandResolves,\n isScopebondHookCommand, harnessEntryMatches,\n localHarnessFile, isMachineSpecificCommand, gitShareState, excludeFromGit, pruneHarnessEntries,\n} from \"./install.js\";\nexport type { Harness, HarnessScopes, GitShare } from \"./install.js\";\nexport { explainDeny, describeAction, findClause } from \"./explain.js\";\nexport type { ExplainDenyInput, ExplainIntent, ExplainPolicy, ExplainClause } from \"./explain.js\";\nexport {\n ensureDurableRuntime, isEphemeralPath, nodeModulesRootOf, runtimeRoot, runtimeDirFor, pinnedCliPath,\n} from \"./runtime-install.js\";\nexport type { PinResult } from \"./runtime-install.js\";\nexport { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from \"./cloud.js\";\nexport type { HookConnection } from \"./cloud.js\";\nexport { scrubSecrets, scrubParam, redactCommand, digest, sha256, keyedDigest, useDigestKey, loadOrCreateDigestKey } from \"./minimize.js\";\nexport {\n AGENT_KINDS, assertAgentKind, observationSigner, signObservation, observationHash, sourceReceiptHash, signingBytes,\n buildPayload, buildOperation, operationsForCall, bindingKeyFromHex, loadOrCreateBindingKey, digestPolicy,\n REQUEST_BINDING_DOMAIN, MAX_OBSERVATION_BYTES, MAX_BATCH_ITEMS, MAX_BATCH_BODY_BYTES,\n} from \"./observation.js\";\nexport type { ObservationPayload, SignedObservation, ObservationSigner, BindingKey, AgentKind } from \"./observation.js\";\nexport { ObservationStore, OBSERVATION_DB } from \"./obs-store.js\";\nexport { uploadPending, parseRetryAfter, OBSERVATIONS_PATH } from \"./obs-upload.js\";\nexport { openObservations, observationStatus, describeObservations, ObservationEmitter, OBSERVATIONS_SCOPE } from \"./obs-emitter.js\";\nexport { wireLifecycleHooks, unwireLifecycleHooks } from \"./install.js\";\nexport {\n deriveTypedOperations, keyedIdFor, callRequestOf, TYPED_ACTION_TYPES, fixtureProbe, gitPushOperation, githubOperation, packageOperation, parseGh, parseGithubMcp, normalizeRemote, systemGit, UNBOUND,\n} from \"./typed-ops.js\";\nexport type { CallRequest, GitProbe, TypedContext, GithubRepo, GithubRequest, DeriveInput } from \"./typed-ops.js\";\nexport { deriveInfraOperation, databaseFacts, databaseGuardActions, parseDestination, fetchOperation, systemFiles, fixtureFiles } from \"./typed-infra.js\";\nexport type { FileProbe, InfraContext, ReadContext, DatabaseFacts, DatabaseGuardAction } from \"./typed-infra.js\";\nexport { classifySql } from \"./sql-classify.js\";\nexport type { SqlClass, SqlVerb } from \"./sql-classify.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// @scopebond/hook — the Claude Code, Cursor + Codex connector. The library surface is\n// the deterministic mapper and the check-only runtime; the `scopebond-hook`\n// binary is the thin harness adapter over them.\n\nexport { mapClaudeToolUse, mapCodexToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nexport type { Mapped, NormalizedIntent } from \"./map.js\";\nexport { createHookRuntime, starterPolicy, upgradeStarterPolicy } from \"./runtime.js\";\nexport type { RuntimeConfig, Decision } from \"./runtime.js\";\nexport {\n compile, defaultRules, describeRules, loadRules, saveRules, rulesPath, pathRuleFor, RULES_FILE,\n} from \"./rules.js\";\nexport type { RuleSet, PathRule } from \"./rules.js\";\nexport { pathRuleMatches, isDestructiveProgram, isProtectedBranch } from \"./rules.js\";\nexport { classifyIntent, classificationBlocks, BLOCKING } from \"./classify.js\";\nexport type { CatalogId, Classification } from \"./classify.js\";\nexport { classifyRoot, applyRootScope } from \"./paths.js\";\nexport type { RootScope, RootOptions } from \"./paths.js\";\nexport { actionGroupId, withActionGroup, distinctTargets, ACTION_GROUP_PARAM, ACTION_GROUP_SIZE_PARAM, ACTION_GROUP_SEQ_PARAM } from \"./group.js\";\nexport { computeManifest, renderManifest, cellState, cellKey, vectorsForCell, vectorDigest } from \"./capabilities.js\";\nexport type { Manifest, CapabilityCell, CapabilityState, ProofRecord, HostVariant, EventPhase, ManifestInput } from \"./capabilities.js\";\nexport { runProofFixtures, deliverProofReceipts, loadProofs, saveProofs, PROOF_FILE } from \"./proof.js\";\nexport { inspectBudgetExport, loadBudgetExport, localBudgetOf, BUDGET_EXPORT_TYPE } from \"./budget-load.js\";\nexport { inspectExport, loadPolicyExport, policyScopeDigest, policyBuilds, POLICY_SCOPE_DOMAIN, LOADED_POLICY_FILE } from \"./policy-load.js\";\nexport {\n inspectManaged, compileManaged, installManaged, restoreLocal, isManaged, readMeta, digestRules,\n MANAGED_RULE_IDS, MANAGED_DOC_FILE, MANAGED_META_FILE,\n} from \"./managed.js\";\nexport type { ManagedDocument, ManagedRule, ManagedMeta, RefusalReason } from \"./managed.js\";\nexport { syncPolicy, syncIfDue, SYNC_INTERVAL_MS, INLINE_BUDGET_MS } from \"./policy-sync.js\";\nexport type { SyncOutcome, SyncOptions } from \"./policy-sync.js\";\nexport { VECTORS, mapVector } from \"./vectors.js\";\nexport type { Vector, VectorAgent, Dialect } from \"./vectors.js\";\nexport { scaffold, harnessSnippet, installHarness, placeHook } from \"./init.js\";\nexport type { HookPlacement } from \"./init.js\";\nexport {\n userHome, userHarnessFile, projectHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig,\n cursorDetected, codexDetected, absoluteHookCommand, isHarnessConfigured, purgeHome,\n readHarnessConfig, trustProjectPolicy, isTrustedProject, untrustedProjectPolicy, trustedProjectsFile,\n harnessScopes, harnessScopeLabel, configuredHookCommands, hookCommandResolves,\n isScopebondHookCommand, harnessEntryMatches,\n localHarnessFile, isMachineSpecificCommand, gitShareState, excludeFromGit, pruneHarnessEntries,\n} from \"./install.js\";\nexport type { Harness, HarnessScopes, GitShare } from \"./install.js\";\nexport { explainDeny, describeAction, findClause } from \"./explain.js\";\nexport type { ExplainDenyInput, ExplainIntent, ExplainPolicy, ExplainClause } from \"./explain.js\";\nexport {\n ensureDurableRuntime, isEphemeralPath, nodeModulesRootOf, runtimeRoot, runtimeDirFor, pinnedCliPath,\n} from \"./runtime-install.js\";\nexport type { PinResult } from \"./runtime-install.js\";\nexport { connectCloud, ingestUrl, loadConnection, attachExporter, flushBounded, connectionPath } from \"./cloud.js\";\nexport type { HookConnection } from \"./cloud.js\";\nexport { scrubSecrets, scrubParam, redactCommand, digest, sha256, keyedDigest, useDigestKey, loadOrCreateDigestKey } from \"./minimize.js\";\nexport {\n AGENT_KINDS, assertAgentKind, observationSigner, signObservation, observationHash, sourceReceiptHash, signingBytes,\n buildPayload, buildOperation, operationsForCall, bindingKeyFromHex, loadOrCreateBindingKey, digestPolicy,\n REQUEST_BINDING_DOMAIN, MAX_OBSERVATION_BYTES, MAX_BATCH_ITEMS, MAX_BATCH_BODY_BYTES,\n} from \"./observation.js\";\nexport type { ObservationPayload, SignedObservation, ObservationSigner, BindingKey, AgentKind } from \"./observation.js\";\nexport { ObservationStore, OBSERVATION_DB } from \"./obs-store.js\";\nexport { uploadPending, parseRetryAfter, OBSERVATIONS_PATH } from \"./obs-upload.js\";\nexport { openObservations, observationStatus, describeObservations, ObservationEmitter, OBSERVATIONS_SCOPE } from \"./obs-emitter.js\";\nexport { wireLifecycleHooks, unwireLifecycleHooks } from \"./install.js\";\nexport {\n deriveTypedOperations, keyedIdFor, callRequestOf, TYPED_ACTION_TYPES, fixtureProbe, gitPushOperation, githubOperation, packageOperation, parseGh, parseGithubMcp, normalizeRemote, systemGit, UNBOUND,\n} from \"./typed-ops.js\";\nexport type { CallRequest, GitProbe, TypedContext, GithubRepo, GithubRequest, DeriveInput } from \"./typed-ops.js\";\nexport { deriveInfraOperation, databaseFacts, databaseGuardActions, parseDestination, fetchOperation, systemFiles, fixtureFiles } from \"./typed-infra.js\";\nexport type { FileProbe, InfraContext, ReadContext, DatabaseFacts, DatabaseGuardAction } from \"./typed-infra.js\";\nexport { classifySql } from \"./sql-classify.js\";\nexport type { SqlClass, SqlVerb } from \"./sql-classify.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpD,gFAAgF;AAChF,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;qEACqE;AACrE,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIxD;AAED;gEACgE;AAChE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,CAIxF;AAED;;;mDAGmD;AACnD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE7F;AAED;;;;2EAI2E;AAC3E,MAAM,WAAW,aAAa;IAAG,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAEpG,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,GAAE,MAAsB,GAAG,aAAa,CAS1F;AAED,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAW/D;AAED;iFACiF;AACjF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED;;;;;mDAKmD;AACnD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;AAEpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAepD;AAED;;0BAE0B;AAC1B,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBpD;AAED;4DAC4D;AAC5D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAqB9F;AAED;;sCAEsC;AACtC,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAYD;uFACuF;AACvF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;iFAKiF;AACjF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAavE;AAED,0FAA0F;AAC1F,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIpF;AAyBD,eAAO,MAAM,sBAAsB,GAAI,KAAK,OAAO,KAAG,OAgBrD,CAAC;AAGF;kDACkD;AAClD,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,KAAG,OACkF,CAAC;AAGpI;;iFAEiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUvE;AAED;;;;;;;4EAO4E;AAC5E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK/D;AAED;4FAC4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAyB1F;AAED;gDACgD;AAChD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzD;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;;;iEAQiE;AACjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAG7E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;sEAEsE;AACtE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAc7D;AAED;;;sFAGsF;AACtF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAa5D;AAED,2DAA2D;AAC3D,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,8EAA+E,CAAC;AAE7G;;;+EAG+E;AAC/E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAexE;AAED;uDACuD;AACvD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBzD"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
package/dist/install.js CHANGED
@@ -318,8 +318,7 @@ export function backupHarnessConfig(file) {
318
318
  * Shared by the project installer (npx command) and the user installer (absolute path). */
319
319
  export function writeHarnessConfig(file, harness, command) {
320
320
  const config = readHarnessConfig(file);
321
- backupHarnessConfig(file);
322
- mkdirSync(dirname(file), { recursive: true });
321
+ const before = existsSync(file) ? JSON.stringify(config) : null;
323
322
  const hooks = isRecord(config.hooks) ? config.hooks : (config.hooks = {});
324
323
  if (harness === "cursor") {
325
324
  config.version = config.version ?? 1;
@@ -346,6 +345,13 @@ export function writeHarnessConfig(file, harness, command) {
346
345
  else
347
346
  list.push(entry);
348
347
  }
348
+ // SB276: a settings file that already holds exactly this entry is left byte-for-byte
349
+ // untouched — no rewrite, no backup. A running agent session watches this file, and
350
+ // rewriting it (even with the same content) is what disturbed a live session on re-login.
351
+ if (before !== null && JSON.stringify(config) === before)
352
+ return file;
353
+ backupHarnessConfig(file);
354
+ mkdirSync(dirname(file), { recursive: true });
349
355
  writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
350
356
  return file;
351
357
  }