@scopebond/hook 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/proof.js CHANGED
@@ -30,6 +30,7 @@ import { mapVector } from "./vectors.js";
30
30
  import { bindingKeyFromHex, operationsForCall, sourceReceiptHash } from "./observation.js";
31
31
  import { callRequestOf, fixtureProbe } from "./typed-ops.js";
32
32
  import { fixtureFiles } from "./typed-infra.js";
33
+ import { ingestUrl } from "./cloud.js";
33
34
  export const PROOF_FILE = "capability-proof.json";
34
35
  const receiptActionType = (receipt) => receipt?.payload?.intent?.action_type;
35
36
  const receiptParams = (receipt) => {
@@ -162,7 +163,7 @@ export async function deliverProofReceipts(connection, receipts, options = {}) {
162
163
  if (receipts.length === 0)
163
164
  return true;
164
165
  const exporter = createCloudExporter({
165
- url: connection.url, credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),
166
+ url: ingestUrl(connection), credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),
166
167
  fetch: options.fetch, flushMs: 60_000,
167
168
  });
168
169
  try {
package/dist/proof.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"proof.js","sourceRoot":"","sources":["../src/proof.ts"],"names":[],"mappings":"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","sourcesContent":["// Safe proof fixtures.\n//\n// For each supported capability cell this runs its tagged vectors end to end against a\n// throwaway hook home: a safe action must be allowed and countersigned; a violating one\n// must be denied at the hook boundary; every receipt must verify against the\n// countersigning key; and every receipt of one tool call must carry one action group.\n//\n// Nothing here touches a developer's real settings, policy, keys or receipts. Every run\n// creates its own directory under the OS temp dir, scaffolds a fresh machine key, policy\n// and store there, and deletes it afterwards. No network is used, so a fixture can never\n// produce a Cloud acknowledgement: `cloud_ack` is always `not_checked`, and `origin` is\n// always `fixture`. A cell therefore cannot reach `verified_reporting` from this runner\n// (see capabilities.ts); it can only show `configured_unverified` with a passing\n// fixture, or `degraded` with a failing one.\n//\n// Observation-only cells (Cursor's after-edit event, and monitor-mode fetch and MCP\n// calls) are proven with a known successful after-action fixture, labelled\n// observation-only. No deny fixture is run for them and none is claimed.\n\nimport { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createCloudExporter, createMemoryCloudOutbox, verifyReceipt, type SignedReceipt } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { fillPushBranch } from \"./map.js\";\nimport { scaffold } from \"./init.js\";\nimport { createHookRuntime } from \"./runtime.js\";\nimport { ACTION_GROUP_PARAM } from \"./group.js\";\nimport { computeManifest, specForCell, vectorDigest, vectorsForCell, type Adapter, type CapabilityCell, type ProofRecord } from \"./capabilities.js\";\nimport { mapVector, type Vector } from \"./vectors.js\";\nimport { bindingKeyFromHex, operationsForCall, sourceReceiptHash } from \"./observation.js\";\nimport { callRequestOf, fixtureProbe } from \"./typed-ops.js\";\nimport { fixtureFiles } from \"./typed-infra.js\";\nimport type { HookConnection } from \"./cloud.js\";\n\nexport const PROOF_FILE = \"capability-proof.json\";\n\nconst receiptActionType = (receipt: unknown): string | undefined =>\n (receipt as { payload?: { intent?: { action_type?: unknown } } } | undefined)?.payload?.intent?.action_type as string | undefined;\n\n/** Options for a fixture run. */\nexport interface ProofRunOptions {\n /** A real hook config dir whose machine and countersigning keys the fixtures should be\n * signed with (copied into the temp home, never modified), so the workspace can accept\n * the fixture receipts and resolve a proof's digests against them. Without it the\n * fixtures use throwaway keys and produce no digests a workspace could resolve. */\n identityDir?: string;\n /** Receives every fixture receipt produced, once each, for delivery. */\n collect?: unknown[];\n}\n\nconst receiptParams = (receipt: unknown): Record<string, unknown> => {\n const payload = (receipt as { payload?: { intent?: { params?: Record<string, unknown> } } } | undefined)?.payload;\n return payload?.intent?.params ?? {};\n};\n\ninterface Outcome { decision: string; receipts: unknown[]; valid: boolean; grouped: boolean; typed?: boolean }\n\nconst FIXTURE_KEY = bindingKeyFromHex(\"00\".repeat(32));\n\n/** Whether the adapter derives the typed operation a vector expects, from the same dispatched\n * actions and raw request the hook uses, against a deterministic fixture repository. */\nfunction typedDerived(vector: Vector, dispatched: Array<{ action: { action_type: string; params: Record<string, unknown> } }> | undefined): boolean {\n if (!vector.typed) return true;\n const request = callRequestOf(vector.input);\n const operations = operationsForCall({ dispatched: dispatched ?? [], request }, { key: FIXTURE_KEY, cwd: \"/fixture\", repositoryId: \"sbr_fixture\", probe: fixtureProbe, packageManagerVersion: () => undefined, files: fixtureFiles, env: () => undefined });\n return operations.some((op) => op?.type === vector.typed!.type && (vector.typed!.verb === undefined || op.verb === vector.typed!.verb));\n}\n\nasync function run(vector: Vector, runtime: ReturnType<typeof createHookRuntime>, attesterPem: string): Promise<Outcome> {\n const decision = await runtime.evaluate(fillPushBranch(mapVector(vector), \"feature/work\"), { groupKey: `proof:${vector.id}` });\n const receipts = decision.receipts ?? (decision.receipt ? [decision.receipt] : []);\n const valid = receipts.length > 0 && receipts.every((r) => (verifyReceipt(r as Parameters<typeof verifyReceipt>[0], attesterPem) as unknown as { valid: boolean }).valid);\n const groups = new Set(receipts.map((r) => receiptParams(r)[ACTION_GROUP_PARAM]));\n const grouped = receipts.length > 0 && groups.size === 1 && typeof [...groups][0] === \"string\" && String([...groups][0]) !== \"\";\n return { decision: decision.decision, receipts, valid, grouped, typed: typedDerived(vector, decision.dispatched) };\n}\n\n/** Run one cell's fixtures in a temp hook home. Returns null when the cell has no vectors. */\nasync function proveCell(adapter: Adapter, actionType: string, phase: \"pre_action\" | \"after_action\", observationOnly: boolean, adapterVersion: string, options: ProofRunOptions): Promise<{ proof: Omit<ProofRecord, \"cell\">; receipts: unknown[] } | null> {\n const vectors = vectorsForCell(adapter, actionType, phase);\n const digest = vectorDigest(vectors);\n if (digest === null) return null;\n const dir = mkdtempSync(join(tmpdir(), \"scopebond-proof-\"));\n let runtime: ReturnType<typeof createHookRuntime> | undefined;\n try {\n if (options.identityDir) {\n for (const name of [\"agent.key\", \"attester.key\"]) {\n const source = join(options.identityDir, name);\n if (existsSync(source)) copyFileSync(source, join(dir, name));\n }\n }\n scaffold(dir);\n const attesterPath = join(dir, \"attester.key\");\n const { attester } = loadOrCreateAttester({ file: attesterPath });\n runtime = createHookRuntime({ policyPath: join(dir, \"policy.json\"), keyPath: join(dir, \"agent.key\"), attesterPath, dbPath: join(dir, \"receipts.db\") });\n const allow = vectors.filter((v) => v.cell?.role === \"allow\" || v.cell?.role === \"after_action\");\n const deny = vectors.filter((v) => v.cell?.role === \"deny\");\n const allowed: Outcome[] = [];\n for (const v of allow) allowed.push(await run(v, runtime, attester.publicKeyPem));\n const denied: Outcome[] = [];\n if (!observationOnly) for (const v of deny) denied.push(await run(v, runtime, attester.publicKeyPem));\n const all = [...allowed, ...denied];\n const receipts = all.flatMap((o) => o.receipts);\n // Only receipts of the cell's own action type stand as its proof (a compound command can\n // also produce receipts of other types), and each is named by the workspace's projection.\n const proofDigests = [...new Set(receipts.filter((r) => receiptActionType(r) === actionType).map((r) => sourceReceiptHash(r)))];\n return { receipts, proof: {\n ran_at: new Date().toISOString(), adapter_version: adapterVersion, test_vector_digest: digest, origin: \"fixture\",\n safe_allow: allowed.length > 0 && allowed.every((o) => o.decision !== \"deny\" && o.receipts.length > 0),\n safe_deny: observationOnly ? \"not_applicable\" : denied.length > 0 && denied.every((o) => o.decision === \"deny\"),\n signature: all.length > 0 && all.every((o) => o.valid),\n grouping: all.length > 0 && all.every((o) => o.grouped),\n cloud_ack: \"not_checked\", observation_only: observationOnly, proof_digests: proofDigests,\n ...(vectors.some((v) => v.typed) ? { typed_operation: all.length > 0 && all.every((o) => o.typed !== false) } : {}),\n } };\n } finally {\n try { runtime?.close(); } catch { /* the temp home is removed next */ }\n try { rmSync(dir, { recursive: true, force: true, maxRetries: 5 }); } catch { /* a handle still open on Windows; the OS temp dir is cleaned later */ }\n }\n}\n\n/** Run every supported cell's fixtures once per adapter/action/phase, and record the\n * result under each host variant's key. A fixture exercises the adapter, so it cannot\n * tell Codex desktop from Codex CLI: both keys get the same fixture proof, and neither\n * becomes verified (see `cellState`). */\nexport async function runProofFixtures(adapterVersion: string, options: ProofRunOptions = {}): Promise<Record<string, ProofRecord>> {\n const cells = computeManifest({ adapterVersion, configured: { claude: true, codex: true, cursor: true } }).cells;\n const records: Record<string, ProofRecord> = {};\n const cache = new Map<string, Omit<ProofRecord, \"cell\"> | null>();\n for (const cell of cells) {\n if (cell.state === \"unsupported\") continue;\n const spec = specForCell(cell);\n if (!spec) continue;\n const id = `${spec.adapter}/${spec.action_type}/${spec.phase}`;\n if (!cache.has(id)) {\n const run = await proveCell(spec.adapter, spec.action_type, spec.phase, cell.observation_only, adapterVersion, options);\n if (run) options.collect?.push(...run.receipts);\n cache.set(id, run?.proof ?? null);\n }\n const proof = cache.get(id);\n if (proof) records[cell.key] = { cell: cell.key, ...proof };\n }\n return records;\n}\n\nexport function loadProofs(configDir: string): Record<string, ProofRecord> {\n const file = join(configDir, PROOF_FILE);\n if (!existsSync(file)) return {};\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as { proofs?: Record<string, ProofRecord> };\n return parsed.proofs && typeof parsed.proofs === \"object\" ? parsed.proofs : {};\n } catch { return {}; }\n}\n\nexport function saveProofs(configDir: string, proofs: Record<string, ProofRecord>): string {\n const file = join(configDir, PROOF_FILE);\n writeFileSync(file, `${JSON.stringify({ version: 1, proofs }, null, 2)}\\n`);\n return file;\n}\n\n/** Whether a cell's fixture proof passed, for a summary line. */\nexport const proofPassed = (proof: ProofRecord, cell: CapabilityCell): boolean =>\n proof.safe_allow && (cell.observation_only ? proof.safe_deny === \"not_applicable\" : proof.safe_deny === true) && proof.signature && proof.grouping;\n\n/** Deliver fixture receipts to the workspace's receipt route and report whether every one was\n * accepted. A proof names these receipts by digest, so it is only worth sending after they\n * landed; anything short of full delivery leaves the proof unsent, never half-claimed. */\nexport async function deliverProofReceipts(connection: HookConnection, receipts: unknown[], options: { fetch?: typeof fetch; timeoutMs?: number } = {}): Promise<boolean> {\n if (receipts.length === 0) return true;\n const exporter = createCloudExporter({\n url: connection.url, credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),\n fetch: options.fetch, flushMs: 60_000,\n });\n try {\n for (const receipt of receipts) exporter.enqueue(receipt as SignedReceipt);\n const deadline = Date.now() + (options.timeoutMs ?? 15_000);\n while (exporter.pending() > 0 && Date.now() < deadline) {\n await exporter.flush();\n if (exporter.pending() > 0) {\n if (exporter.status().consecutiveFailures > 0) return false;\n await new Promise((resolve) => setTimeout(resolve, 25));\n }\n }\n return exporter.pending() === 0;\n } finally { exporter.stop(); }\n}\n"]}
1
+ {"version":3,"file":"proof.js","sourceRoot":"","sources":["../src/proof.ts"],"names":[],"mappings":"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","sourcesContent":["// Safe proof fixtures.\n//\n// For each supported capability cell this runs its tagged vectors end to end against a\n// throwaway hook home: a safe action must be allowed and countersigned; a violating one\n// must be denied at the hook boundary; every receipt must verify against the\n// countersigning key; and every receipt of one tool call must carry one action group.\n//\n// Nothing here touches a developer's real settings, policy, keys or receipts. Every run\n// creates its own directory under the OS temp dir, scaffolds a fresh machine key, policy\n// and store there, and deletes it afterwards. No network is used, so a fixture can never\n// produce a Cloud acknowledgement: `cloud_ack` is always `not_checked`, and `origin` is\n// always `fixture`. A cell therefore cannot reach `verified_reporting` from this runner\n// (see capabilities.ts); it can only show `configured_unverified` with a passing\n// fixture, or `degraded` with a failing one.\n//\n// Observation-only cells (Cursor's after-edit event, and monitor-mode fetch and MCP\n// calls) are proven with a known successful after-action fixture, labelled\n// observation-only. No deny fixture is run for them and none is claimed.\n\nimport { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createCloudExporter, createMemoryCloudOutbox, verifyReceipt, type SignedReceipt } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { fillPushBranch } from \"./map.js\";\nimport { scaffold } from \"./init.js\";\nimport { createHookRuntime } from \"./runtime.js\";\nimport { ACTION_GROUP_PARAM } from \"./group.js\";\nimport { computeManifest, specForCell, vectorDigest, vectorsForCell, type Adapter, type CapabilityCell, type ProofRecord } from \"./capabilities.js\";\nimport { mapVector, type Vector } from \"./vectors.js\";\nimport { bindingKeyFromHex, operationsForCall, sourceReceiptHash } from \"./observation.js\";\nimport { callRequestOf, fixtureProbe } from \"./typed-ops.js\";\nimport { fixtureFiles } from \"./typed-infra.js\";\nimport { ingestUrl, type HookConnection } from \"./cloud.js\";\n\nexport const PROOF_FILE = \"capability-proof.json\";\n\nconst receiptActionType = (receipt: unknown): string | undefined =>\n (receipt as { payload?: { intent?: { action_type?: unknown } } } | undefined)?.payload?.intent?.action_type as string | undefined;\n\n/** Options for a fixture run. */\nexport interface ProofRunOptions {\n /** A real hook config dir whose machine and countersigning keys the fixtures should be\n * signed with (copied into the temp home, never modified), so the workspace can accept\n * the fixture receipts and resolve a proof's digests against them. Without it the\n * fixtures use throwaway keys and produce no digests a workspace could resolve. */\n identityDir?: string;\n /** Receives every fixture receipt produced, once each, for delivery. */\n collect?: unknown[];\n}\n\nconst receiptParams = (receipt: unknown): Record<string, unknown> => {\n const payload = (receipt as { payload?: { intent?: { params?: Record<string, unknown> } } } | undefined)?.payload;\n return payload?.intent?.params ?? {};\n};\n\ninterface Outcome { decision: string; receipts: unknown[]; valid: boolean; grouped: boolean; typed?: boolean }\n\nconst FIXTURE_KEY = bindingKeyFromHex(\"00\".repeat(32));\n\n/** Whether the adapter derives the typed operation a vector expects, from the same dispatched\n * actions and raw request the hook uses, against a deterministic fixture repository. */\nfunction typedDerived(vector: Vector, dispatched: Array<{ action: { action_type: string; params: Record<string, unknown> } }> | undefined): boolean {\n if (!vector.typed) return true;\n const request = callRequestOf(vector.input);\n const operations = operationsForCall({ dispatched: dispatched ?? [], request }, { key: FIXTURE_KEY, cwd: \"/fixture\", repositoryId: \"sbr_fixture\", probe: fixtureProbe, packageManagerVersion: () => undefined, files: fixtureFiles, env: () => undefined });\n return operations.some((op) => op?.type === vector.typed!.type && (vector.typed!.verb === undefined || op.verb === vector.typed!.verb));\n}\n\nasync function run(vector: Vector, runtime: ReturnType<typeof createHookRuntime>, attesterPem: string): Promise<Outcome> {\n const decision = await runtime.evaluate(fillPushBranch(mapVector(vector), \"feature/work\"), { groupKey: `proof:${vector.id}` });\n const receipts = decision.receipts ?? (decision.receipt ? [decision.receipt] : []);\n const valid = receipts.length > 0 && receipts.every((r) => (verifyReceipt(r as Parameters<typeof verifyReceipt>[0], attesterPem) as unknown as { valid: boolean }).valid);\n const groups = new Set(receipts.map((r) => receiptParams(r)[ACTION_GROUP_PARAM]));\n const grouped = receipts.length > 0 && groups.size === 1 && typeof [...groups][0] === \"string\" && String([...groups][0]) !== \"\";\n return { decision: decision.decision, receipts, valid, grouped, typed: typedDerived(vector, decision.dispatched) };\n}\n\n/** Run one cell's fixtures in a temp hook home. Returns null when the cell has no vectors. */\nasync function proveCell(adapter: Adapter, actionType: string, phase: \"pre_action\" | \"after_action\", observationOnly: boolean, adapterVersion: string, options: ProofRunOptions): Promise<{ proof: Omit<ProofRecord, \"cell\">; receipts: unknown[] } | null> {\n const vectors = vectorsForCell(adapter, actionType, phase);\n const digest = vectorDigest(vectors);\n if (digest === null) return null;\n const dir = mkdtempSync(join(tmpdir(), \"scopebond-proof-\"));\n let runtime: ReturnType<typeof createHookRuntime> | undefined;\n try {\n if (options.identityDir) {\n for (const name of [\"agent.key\", \"attester.key\"]) {\n const source = join(options.identityDir, name);\n if (existsSync(source)) copyFileSync(source, join(dir, name));\n }\n }\n scaffold(dir);\n const attesterPath = join(dir, \"attester.key\");\n const { attester } = loadOrCreateAttester({ file: attesterPath });\n runtime = createHookRuntime({ policyPath: join(dir, \"policy.json\"), keyPath: join(dir, \"agent.key\"), attesterPath, dbPath: join(dir, \"receipts.db\") });\n const allow = vectors.filter((v) => v.cell?.role === \"allow\" || v.cell?.role === \"after_action\");\n const deny = vectors.filter((v) => v.cell?.role === \"deny\");\n const allowed: Outcome[] = [];\n for (const v of allow) allowed.push(await run(v, runtime, attester.publicKeyPem));\n const denied: Outcome[] = [];\n if (!observationOnly) for (const v of deny) denied.push(await run(v, runtime, attester.publicKeyPem));\n const all = [...allowed, ...denied];\n const receipts = all.flatMap((o) => o.receipts);\n // Only receipts of the cell's own action type stand as its proof (a compound command can\n // also produce receipts of other types), and each is named by the workspace's projection.\n const proofDigests = [...new Set(receipts.filter((r) => receiptActionType(r) === actionType).map((r) => sourceReceiptHash(r)))];\n return { receipts, proof: {\n ran_at: new Date().toISOString(), adapter_version: adapterVersion, test_vector_digest: digest, origin: \"fixture\",\n safe_allow: allowed.length > 0 && allowed.every((o) => o.decision !== \"deny\" && o.receipts.length > 0),\n safe_deny: observationOnly ? \"not_applicable\" : denied.length > 0 && denied.every((o) => o.decision === \"deny\"),\n signature: all.length > 0 && all.every((o) => o.valid),\n grouping: all.length > 0 && all.every((o) => o.grouped),\n cloud_ack: \"not_checked\", observation_only: observationOnly, proof_digests: proofDigests,\n ...(vectors.some((v) => v.typed) ? { typed_operation: all.length > 0 && all.every((o) => o.typed !== false) } : {}),\n } };\n } finally {\n try { runtime?.close(); } catch { /* the temp home is removed next */ }\n try { rmSync(dir, { recursive: true, force: true, maxRetries: 5 }); } catch { /* a handle still open on Windows; the OS temp dir is cleaned later */ }\n }\n}\n\n/** Run every supported cell's fixtures once per adapter/action/phase, and record the\n * result under each host variant's key. A fixture exercises the adapter, so it cannot\n * tell Codex desktop from Codex CLI: both keys get the same fixture proof, and neither\n * becomes verified (see `cellState`). */\nexport async function runProofFixtures(adapterVersion: string, options: ProofRunOptions = {}): Promise<Record<string, ProofRecord>> {\n const cells = computeManifest({ adapterVersion, configured: { claude: true, codex: true, cursor: true } }).cells;\n const records: Record<string, ProofRecord> = {};\n const cache = new Map<string, Omit<ProofRecord, \"cell\"> | null>();\n for (const cell of cells) {\n if (cell.state === \"unsupported\") continue;\n const spec = specForCell(cell);\n if (!spec) continue;\n const id = `${spec.adapter}/${spec.action_type}/${spec.phase}`;\n if (!cache.has(id)) {\n const run = await proveCell(spec.adapter, spec.action_type, spec.phase, cell.observation_only, adapterVersion, options);\n if (run) options.collect?.push(...run.receipts);\n cache.set(id, run?.proof ?? null);\n }\n const proof = cache.get(id);\n if (proof) records[cell.key] = { cell: cell.key, ...proof };\n }\n return records;\n}\n\nexport function loadProofs(configDir: string): Record<string, ProofRecord> {\n const file = join(configDir, PROOF_FILE);\n if (!existsSync(file)) return {};\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as { proofs?: Record<string, ProofRecord> };\n return parsed.proofs && typeof parsed.proofs === \"object\" ? parsed.proofs : {};\n } catch { return {}; }\n}\n\nexport function saveProofs(configDir: string, proofs: Record<string, ProofRecord>): string {\n const file = join(configDir, PROOF_FILE);\n writeFileSync(file, `${JSON.stringify({ version: 1, proofs }, null, 2)}\\n`);\n return file;\n}\n\n/** Whether a cell's fixture proof passed, for a summary line. */\nexport const proofPassed = (proof: ProofRecord, cell: CapabilityCell): boolean =>\n proof.safe_allow && (cell.observation_only ? proof.safe_deny === \"not_applicable\" : proof.safe_deny === true) && proof.signature && proof.grouping;\n\n/** Deliver fixture receipts to the workspace's receipt route and report whether every one was\n * accepted. A proof names these receipts by digest, so it is only worth sending after they\n * landed; anything short of full delivery leaves the proof unsent, never half-claimed. */\nexport async function deliverProofReceipts(connection: HookConnection, receipts: unknown[], options: { fetch?: typeof fetch; timeoutMs?: number } = {}): Promise<boolean> {\n if (receipts.length === 0) return true;\n const exporter = createCloudExporter({\n url: ingestUrl(connection), credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),\n fetch: options.fetch, flushMs: 60_000,\n });\n try {\n for (const receipt of receipts) exporter.enqueue(receipt as SignedReceipt);\n const deadline = Date.now() + (options.timeoutMs ?? 15_000);\n while (exporter.pending() > 0 && Date.now() < deadline) {\n await exporter.flush();\n if (exporter.pending() > 0) {\n if (exporter.status().consecutiveFailures > 0) return false;\n await new Promise((resolve) => setTimeout(resolve, 25));\n }\n }\n return exporter.pending() === 0;\n } finally { exporter.stop(); }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { SqliteReceiptStore } from "@scopebond/gateway/node";
2
+ import { type HookConnection } from "./cloud.js";
3
+ export interface KeyGroup {
4
+ kid: string;
5
+ count: number;
6
+ first: string | null;
7
+ last: string | null;
8
+ }
9
+ export interface RecoverIo {
10
+ log(line: string): void;
11
+ fetch: typeof fetch;
12
+ sleep(ms: number): Promise<void>;
13
+ now(): number;
14
+ }
15
+ /** Walk the whole local log once and count receipts per signing key other than `currentKid`. */
16
+ export declare function groupByEarlierKey(store: SqliteReceiptStore, currentKid: string): KeyGroup[];
17
+ export interface RecoverOptions {
18
+ wait: boolean;
19
+ waitMs: number;
20
+ pollMs: number;
21
+ }
22
+ export interface RecoverResult {
23
+ groups: number;
24
+ accepted: number;
25
+ duplicates: number;
26
+ rejected: number;
27
+ pending: number;
28
+ skipped: number;
29
+ failed: boolean;
30
+ }
31
+ /** Recover every earlier key's receipts, one approval per key. */
32
+ export declare function recoverEarlierReceipts(dir: string, connection: HookConnection, io: RecoverIo, options: RecoverOptions): Promise<RecoverResult>;
33
+ //# sourceMappingURL=recover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover.d.ts","sourceRoot":"","sources":["../src/recover.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAU5D,MAAM,WAAW,QAAQ;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAEnG,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,GAAG,IAAI,MAAM,CAAC;CACf;AAOD,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE,CAkB3F;AAeD,MAAM,WAAW,cAAc;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAEjF,MAAM,WAAW,aAAa;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE;AAE5J,kEAAkE;AAClE,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAC9E,OAAO,CAAC,aAAa,CAAC,CA6CxB"}
@@ -0,0 +1,201 @@
1
+ // `recover` — deliver receipts this computer recorded but never delivered because the key
2
+ // that signed them was revoked first (the computer was replaced, disconnected or retired
3
+ // while records were still queued). The receipts are already signed; nothing is re-signed.
4
+ // The workspace checks each one against the revoked key it kept, accepts them only after
5
+ // an owner or admin approves, and labels them Recovered. Nothing here makes a revoked key
6
+ // usable again.
7
+ import { existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { SqliteReceiptStore, loadOrCreateAttester } from "@scopebond/gateway/node";
10
+ import { ingestUrl } from "./cloud.js";
11
+ /** The workspace accepts at most 1 MiB and 100 receipts per request. */
12
+ const MAX_BATCH_BYTES = 900 * 1024;
13
+ const MAX_BATCH_COUNT = 100;
14
+ const MAX_RECEIPT_BYTES = 128 * 1024;
15
+ const PAGE = 500;
16
+ /** How long one batch is retried through lost connections and server errors. */
17
+ const RETRY_FOR_MS = 15 * 60_000;
18
+ const kidOf = (r) => {
19
+ const kid = r.payload.attester?.kid;
20
+ return typeof kid === "string" ? kid : null;
21
+ };
22
+ /** Walk the whole local log once and count receipts per signing key other than `currentKid`. */
23
+ export function groupByEarlierKey(store, currentKid) {
24
+ const groups = new Map();
25
+ for (let after = 0;;) {
26
+ const page = store.page(after, PAGE);
27
+ if (!page.length)
28
+ break;
29
+ for (const { id, receipt } of page) {
30
+ after = id;
31
+ const kid = kidOf(receipt);
32
+ if (!kid || kid === currentKid)
33
+ continue;
34
+ const group = groups.get(kid) ?? { kid, count: 0, first: null, last: null };
35
+ group.count += 1;
36
+ const at = receipt.payload.timestamp;
37
+ if (!group.first || at < group.first)
38
+ group.first = at;
39
+ if (!group.last || at > group.last)
40
+ group.last = at;
41
+ groups.set(kid, group);
42
+ }
43
+ }
44
+ return [...groups.values()].sort((a, b) => (a.first ?? "").localeCompare(b.first ?? ""));
45
+ }
46
+ async function call(io, connection, path, body) {
47
+ const response = await io.fetch(new URL(path, ingestUrl(connection)), {
48
+ method: body === undefined ? "GET" : "POST",
49
+ headers: { authorization: `Bearer ${connection.credential}`, "content-type": "application/json" },
50
+ ...(body === undefined ? {} : { body: typeof body === "string" ? body : JSON.stringify(body) }),
51
+ redirect: "error",
52
+ signal: AbortSignal.timeout(60_000),
53
+ });
54
+ return { status: response.status, json: await response.json().catch(() => ({})) };
55
+ }
56
+ /** Recover every earlier key's receipts, one approval per key. */
57
+ export async function recoverEarlierReceipts(dir, connection, io, options) {
58
+ const result = { groups: 0, accepted: 0, duplicates: 0, rejected: 0, pending: 0, skipped: 0, failed: false };
59
+ const dbPath = join(dir, "receipts.db");
60
+ if (!existsSync(dbPath)) {
61
+ io.log("No local log here, so there is nothing to recover.");
62
+ return result;
63
+ }
64
+ const { attester } = loadOrCreateAttester({ file: join(dir, "attester.key") });
65
+ const store = new SqliteReceiptStore(dbPath);
66
+ try {
67
+ io.log("Reading the local log…");
68
+ const groups = groupByEarlierKey(store, attester.kid);
69
+ if (!groups.length) {
70
+ io.log("✓ Every local record was signed by this computer's current key. Nothing to recover.");
71
+ return result;
72
+ }
73
+ result.groups = groups.length;
74
+ for (const group of groups) {
75
+ io.log(`\n${group.count.toLocaleString()} records signed by an earlier key (${group.kid}), recorded ${group.first} – ${group.last}.`);
76
+ const asked = await call(io, connection, "/v1/recover", { source_kid: group.kid, claimed_count: group.count, first_at: group.first, last_at: group.last });
77
+ if (asked.json.code === "key_not_revoked") {
78
+ io.log(" Skipped: that key is still connected elsewhere, so its records are delivered from there, not recovered.");
79
+ result.skipped += group.count;
80
+ continue;
81
+ }
82
+ if (asked.status === 401) {
83
+ io.log(" This computer's own connection is not valid. Reconnect it first (login), then run recover again.");
84
+ result.failed = true;
85
+ return result;
86
+ }
87
+ if (asked.status >= 400) {
88
+ io.log(` The workspace refused: ${String(asked.json.error ?? `HTTP ${asked.status}`)}`);
89
+ result.skipped += group.count;
90
+ continue;
91
+ }
92
+ const id = String(asked.json.id ?? "");
93
+ let status = String(asked.json.status ?? "");
94
+ if (status === "pending") {
95
+ io.log(` Waiting for an owner or admin to approve it on Activity:\n\n ${String(asked.json.approve_url ?? connection.url)}\n`);
96
+ if (!options.wait) {
97
+ result.pending += group.count;
98
+ io.log(" Run recover again after it is approved.");
99
+ continue;
100
+ }
101
+ const deadline = io.now() + options.waitMs;
102
+ while (status === "pending" && io.now() < deadline) {
103
+ await io.sleep(options.pollMs);
104
+ const polled = await call(io, connection, `/v1/recover/${encodeURIComponent(id)}`).catch(() => null);
105
+ if (polled?.status === 200)
106
+ status = String(polled.json.status ?? status);
107
+ }
108
+ }
109
+ if (status === "declined") {
110
+ io.log(" Declined in the workspace. Nothing was sent.");
111
+ result.skipped += group.count;
112
+ continue;
113
+ }
114
+ if (status !== "approved") {
115
+ io.log(status === "pending" ? " Not approved yet. Run recover again after it is approved." : ` This recovery is ${status}. Run recover again to ask anew.`);
116
+ result.pending += group.count;
117
+ continue;
118
+ }
119
+ io.log(" ✓ Approved. Sending…");
120
+ const sent = await uploadGroup(store, group, id, connection, io);
121
+ result.accepted += sent.accepted;
122
+ result.duplicates += sent.duplicates;
123
+ result.rejected += sent.rejected;
124
+ if (sent.stopped) {
125
+ result.failed = true;
126
+ return result;
127
+ }
128
+ await call(io, connection, `/v1/recover/${encodeURIComponent(id)}/complete`, {}).catch(() => null);
129
+ io.log(` ✓ ${sent.accepted.toLocaleString()} recovered, ${sent.duplicates.toLocaleString()} were already in the workspace, ${sent.rejected.toLocaleString()} refused.`);
130
+ }
131
+ return result;
132
+ }
133
+ finally {
134
+ store.close();
135
+ }
136
+ }
137
+ async function uploadGroup(store, group, id, connection, io) {
138
+ const totals = { accepted: 0, duplicates: 0, rejected: 0, stopped: false };
139
+ let batch = [];
140
+ let bytes = 0;
141
+ let sent = 0;
142
+ const send = async () => {
143
+ if (!batch.length)
144
+ return true;
145
+ const body = `{"receipts":[${batch.join(",")}]}`;
146
+ const started = io.now();
147
+ for (let attempt = 1;; attempt++) {
148
+ const answer = await call(io, connection, `/v1/recover/${encodeURIComponent(id)}/receipts`, body).catch((error) => ({ status: 0, json: { error: error.message } }));
149
+ if (answer.status === 200) {
150
+ totals.accepted += Number(answer.json.accepted ?? 0);
151
+ totals.duplicates += Number(answer.json.duplicates ?? 0);
152
+ totals.rejected += Number(answer.json.rejected_count ?? 0);
153
+ break;
154
+ }
155
+ // Durable but not yet readable, or a network error: the same batch is safe to resend.
156
+ // A laptop that sleeps or changes networks loses the connection for minutes, not
157
+ // seconds: keep resending with backoff for up to RETRY_FOR_MS before giving up.
158
+ if ((answer.status === 0 || answer.status >= 500) && io.now() - started < RETRY_FOR_MS) {
159
+ const wait = Math.min(60_000, 2_000 * 2 ** Math.min(attempt - 1, 5));
160
+ io.log(` ${answer.status === 0 ? "Connection lost" : `The workspace answered ${answer.status}`}; trying the same batch again in ${Math.round(wait / 1000)} s…`);
161
+ await io.sleep(wait);
162
+ continue;
163
+ }
164
+ io.log(` Stopped: ${String(answer.json.error ?? `HTTP ${answer.status}`)}. Records already sent stay recovered; run recover again to continue.`);
165
+ totals.stopped = true;
166
+ return false;
167
+ }
168
+ sent += batch.length;
169
+ if (sent % 1000 < batch.length || sent === group.count)
170
+ io.log(` … ${sent.toLocaleString()} of ${group.count.toLocaleString()}`);
171
+ batch = [];
172
+ bytes = 0;
173
+ return true;
174
+ };
175
+ for (let after = 0;;) {
176
+ const page = store.page(after, PAGE);
177
+ if (!page.length)
178
+ break;
179
+ for (const { id: row, receipt } of page) {
180
+ after = row;
181
+ if (kidOf(receipt) !== group.kid)
182
+ continue;
183
+ const json = JSON.stringify(receipt);
184
+ const size = Buffer.byteLength(json);
185
+ if (size > MAX_RECEIPT_BYTES) {
186
+ totals.rejected += 1;
187
+ continue;
188
+ }
189
+ if (batch.length >= MAX_BATCH_COUNT || bytes + size + 1 > MAX_BATCH_BYTES) {
190
+ if (!(await send()))
191
+ return totals;
192
+ }
193
+ batch.push(json);
194
+ bytes += size + 1;
195
+ }
196
+ }
197
+ if (!(await send()))
198
+ return totals;
199
+ return totals;
200
+ }
201
+ //# sourceMappingURL=recover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover.js","sourceRoot":"","sources":["../src/recover.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,yFAAyF;AACzF,2FAA2F;AAC3F,yFAAyF;AACzF,0FAA0F;AAC1F,gBAAgB;AAEhB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAAE,SAAS,EAAuB,MAAM,YAAY,CAAC;AAE5D,wEAAwE;AACxE,MAAM,eAAe,GAAG,GAAG,GAAG,IAAI,CAAC;AACnC,MAAM,eAAe,GAAG,GAAG,CAAC;AAC5B,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,CAAC;AACrC,MAAM,IAAI,GAAG,GAAG,CAAC;AACjB,gFAAgF;AAChF,MAAM,YAAY,GAAG,EAAE,GAAG,MAAM,CAAC;AAWjC,MAAM,KAAK,GAAG,CAAC,CAAgB,EAAiB,EAAE;IAChD,MAAM,GAAG,GAAI,CAAC,CAAC,OAA4C,CAAC,QAAQ,EAAE,GAAG,CAAC;IAC1E,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC,CAAC;AAEF,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,KAAyB,EAAE,UAAkB;IAC7E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,IAAI,KAAK,GAAG,CAAC,IAAK,CAAC;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM;QACxB,KAAK,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;YACnC,KAAK,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,UAAU;gBAAE,SAAS;YACzC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAC5E,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YACjB,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK;gBAAE,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;YACvD,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG,KAAK,CAAC,IAAI;gBAAE,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC;YACpD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3F,CAAC;AAID,KAAK,UAAU,IAAI,CAAC,EAAa,EAAE,UAA0B,EAAE,IAAY,EAAE,IAAc;IACzF,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,EAAE;QACpE,MAAM,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;QAC3C,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,UAAU,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QACjG,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/F,QAAQ,EAAE,OAAO;QACjB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;KACpC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,EAAE,CAAC;AAC/G,CAAC;AAMD,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAW,EAAE,UAA0B,EAAE,EAAa,EAAE,OAAuB;IAE/E,MAAM,MAAM,GAAkB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5H,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAAC,EAAE,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAAC,OAAO,MAAM,CAAC;IAAC,CAAC;IACzG,MAAM,EAAE,QAAQ,EAAE,GAAG,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,EAAE,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAAC,EAAE,CAAC,GAAG,CAAC,qFAAqF,CAAC,CAAC;YAAC,OAAO,MAAM,CAAC;QAAC,CAAC;QACrI,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,sCAAsC,KAAK,CAAC,GAAG,eAAe,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;YACtI,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3J,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC1C,EAAE,CAAC,GAAG,CAAC,2GAA2G,CAAC,CAAC;gBACpH,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;gBAAC,SAAS;YAC1C,CAAC;YACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAAC,EAAE,CAAC,GAAG,CAAC,oGAAoG,CAAC,CAAC;gBAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;gBAAC,OAAO,MAAM,CAAC;YAAC,CAAC;YAChL,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAAC,EAAE,CAAC,GAAG,CAAC,4BAA4B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;gBAAC,SAAS;YAAC,CAAC;YAC/J,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,EAAE,CAAC,GAAG,CAAC,qEAAqE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAClI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;oBAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;oBAAC,EAAE,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;oBAAC,SAAS;gBAAC,CAAC;gBACpH,MAAM,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;gBAC3C,OAAO,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;oBACnD,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;oBAC/B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,eAAe,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;oBACrG,IAAI,MAAM,EAAE,MAAM,KAAK,GAAG;wBAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;gBAC5E,CAAC;YACH,CAAC;YACD,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;gBAAC,EAAE,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;gBAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;gBAAC,SAAS;YAAC,CAAC;YACjI,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;gBAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,6DAA6D,CAAC,CAAC,CAAC,sBAAsB,MAAM,kCAAkC,CAAC,CAAC;gBAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;gBAAC,SAAS;YAAC,CAAC;YACtO,EAAE,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YACjE,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC;YAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC;YAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC;YACzG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;gBAAC,OAAO,MAAM,CAAC;YAAC,CAAC;YAC1D,MAAM,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,eAAe,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACnG,EAAE,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,eAAe,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,mCAAmC,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAC3K,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,KAAyB,EAAE,KAAe,EAAE,EAAU,EAAE,UAA0B,EAAE,EAAa;IAEjG,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC3E,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,KAAK,IAAsB,EAAE;QACxC,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,IAAI,GAAG,gBAAgB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACjD,MAAM,OAAO,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QACzB,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,eAAe,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,EAAa,CAAA,CAAC,CAAC;YACrL,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC1B,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;gBACrD,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;gBACzD,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC;gBAC3D,MAAM;YACR,CAAC;YACD,sFAAsF;YACtF,iFAAiF;YACjF,gFAAgF;YAChF,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;gBACvF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACrE,EAAE,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,0BAA0B,MAAM,CAAC,MAAM,EAAE,oCAAoC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjK,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrB,SAAS;YACX,CAAC;YACD,EAAE,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC,uEAAuE,CAAC,CAAC;YAClJ,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;QACrB,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,KAAK,KAAK,CAAC,KAAK;YAAE,EAAE,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,cAAc,EAAE,OAAO,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QAClI,KAAK,GAAG,EAAE,CAAC;QAAC,KAAK,GAAG,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,KAAK,IAAI,KAAK,GAAG,CAAC,IAAK,CAAC;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM;QACxB,KAAK,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;YACxC,KAAK,GAAG,GAAG,CAAC;YACZ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,GAAG;gBAAE,SAAS;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,IAAI,GAAG,iBAAiB,EAAE,CAAC;gBAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;gBAAC,SAAS;YAAC,CAAC;YACjE,IAAI,KAAK,CAAC,MAAM,IAAI,eAAe,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,eAAe,EAAE,CAAC;gBAAC,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;oBAAE,OAAO,MAAM,CAAC;YAAC,CAAC;YAClH,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// `recover` — deliver receipts this computer recorded but never delivered because the key\n// that signed them was revoked first (the computer was replaced, disconnected or retired\n// while records were still queued). The receipts are already signed; nothing is re-signed.\n// The workspace checks each one against the revoked key it kept, accepts them only after\n// an owner or admin approves, and labels them Recovered. Nothing here makes a revoked key\n// usable again.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SignedReceipt } from \"@scopebond/gateway\";\nimport { SqliteReceiptStore, loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { ingestUrl, type HookConnection } from \"./cloud.js\";\n\n/** The workspace accepts at most 1 MiB and 100 receipts per request. */\nconst MAX_BATCH_BYTES = 900 * 1024;\nconst MAX_BATCH_COUNT = 100;\nconst MAX_RECEIPT_BYTES = 128 * 1024;\nconst PAGE = 500;\n/** How long one batch is retried through lost connections and server errors. */\nconst RETRY_FOR_MS = 15 * 60_000;\n\nexport interface KeyGroup { kid: string; count: number; first: string | null; last: string | null }\n\nexport interface RecoverIo {\n log(line: string): void;\n fetch: typeof fetch;\n sleep(ms: number): Promise<void>;\n now(): number;\n}\n\nconst kidOf = (r: SignedReceipt): string | null => {\n const kid = (r.payload as { attester?: { kid?: unknown } }).attester?.kid;\n return typeof kid === \"string\" ? kid : null;\n};\n\n/** Walk the whole local log once and count receipts per signing key other than `currentKid`. */\nexport function groupByEarlierKey(store: SqliteReceiptStore, currentKid: string): KeyGroup[] {\n const groups = new Map<string, KeyGroup>();\n for (let after = 0; ;) {\n const page = store.page(after, PAGE);\n if (!page.length) break;\n for (const { id, receipt } of page) {\n after = id;\n const kid = kidOf(receipt);\n if (!kid || kid === currentKid) continue;\n const group = groups.get(kid) ?? { kid, count: 0, first: null, last: null };\n group.count += 1;\n const at = receipt.payload.timestamp;\n if (!group.first || at < group.first) group.first = at;\n if (!group.last || at > group.last) group.last = at;\n groups.set(kid, group);\n }\n }\n return [...groups.values()].sort((a, b) => (a.first ?? \"\").localeCompare(b.first ?? \"\"));\n}\n\ninterface Answer { status: number; json: Record<string, unknown> }\n\nasync function call(io: RecoverIo, connection: HookConnection, path: string, body?: unknown): Promise<Answer> {\n const response = await io.fetch(new URL(path, ingestUrl(connection)), {\n method: body === undefined ? \"GET\" : \"POST\",\n headers: { authorization: `Bearer ${connection.credential}`, \"content-type\": \"application/json\" },\n ...(body === undefined ? {} : { body: typeof body === \"string\" ? body : JSON.stringify(body) }),\n redirect: \"error\",\n signal: AbortSignal.timeout(60_000),\n });\n return { status: response.status, json: await response.json().catch(() => ({})) as Record<string, unknown> };\n}\n\nexport interface RecoverOptions { wait: boolean; waitMs: number; pollMs: number }\n\nexport interface RecoverResult { groups: number; accepted: number; duplicates: number; rejected: number; pending: number; skipped: number; failed: boolean }\n\n/** Recover every earlier key's receipts, one approval per key. */\nexport async function recoverEarlierReceipts(\n dir: string, connection: HookConnection, io: RecoverIo, options: RecoverOptions,\n): Promise<RecoverResult> {\n const result: RecoverResult = { groups: 0, accepted: 0, duplicates: 0, rejected: 0, pending: 0, skipped: 0, failed: false };\n const dbPath = join(dir, \"receipts.db\");\n if (!existsSync(dbPath)) { io.log(\"No local log here, so there is nothing to recover.\"); return result; }\n const { attester } = loadOrCreateAttester({ file: join(dir, \"attester.key\") });\n const store = new SqliteReceiptStore(dbPath);\n try {\n io.log(\"Reading the local log…\");\n const groups = groupByEarlierKey(store, attester.kid);\n if (!groups.length) { io.log(\"✓ Every local record was signed by this computer's current key. Nothing to recover.\"); return result; }\n result.groups = groups.length;\n for (const group of groups) {\n io.log(`\\n${group.count.toLocaleString()} records signed by an earlier key (${group.kid}), recorded ${group.first} – ${group.last}.`);\n const asked = await call(io, connection, \"/v1/recover\", { source_kid: group.kid, claimed_count: group.count, first_at: group.first, last_at: group.last });\n if (asked.json.code === \"key_not_revoked\") {\n io.log(\" Skipped: that key is still connected elsewhere, so its records are delivered from there, not recovered.\");\n result.skipped += group.count; continue;\n }\n if (asked.status === 401) { io.log(\" This computer's own connection is not valid. Reconnect it first (login), then run recover again.\"); result.failed = true; return result; }\n if (asked.status >= 400) { io.log(` The workspace refused: ${String(asked.json.error ?? `HTTP ${asked.status}`)}`); result.skipped += group.count; continue; }\n const id = String(asked.json.id ?? \"\");\n let status = String(asked.json.status ?? \"\");\n if (status === \"pending\") {\n io.log(` Waiting for an owner or admin to approve it on Activity:\\n\\n ${String(asked.json.approve_url ?? connection.url)}\\n`);\n if (!options.wait) { result.pending += group.count; io.log(\" Run recover again after it is approved.\"); continue; }\n const deadline = io.now() + options.waitMs;\n while (status === \"pending\" && io.now() < deadline) {\n await io.sleep(options.pollMs);\n const polled = await call(io, connection, `/v1/recover/${encodeURIComponent(id)}`).catch(() => null);\n if (polled?.status === 200) status = String(polled.json.status ?? status);\n }\n }\n if (status === \"declined\") { io.log(\" Declined in the workspace. Nothing was sent.\"); result.skipped += group.count; continue; }\n if (status !== \"approved\") { io.log(status === \"pending\" ? \" Not approved yet. Run recover again after it is approved.\" : ` This recovery is ${status}. Run recover again to ask anew.`); result.pending += group.count; continue; }\n io.log(\" ✓ Approved. Sending…\");\n const sent = await uploadGroup(store, group, id, connection, io);\n result.accepted += sent.accepted; result.duplicates += sent.duplicates; result.rejected += sent.rejected;\n if (sent.stopped) { result.failed = true; return result; }\n await call(io, connection, `/v1/recover/${encodeURIComponent(id)}/complete`, {}).catch(() => null);\n io.log(` ✓ ${sent.accepted.toLocaleString()} recovered, ${sent.duplicates.toLocaleString()} were already in the workspace, ${sent.rejected.toLocaleString()} refused.`);\n }\n return result;\n } finally {\n store.close();\n }\n}\n\nasync function uploadGroup(\n store: SqliteReceiptStore, group: KeyGroup, id: string, connection: HookConnection, io: RecoverIo,\n): Promise<{ accepted: number; duplicates: number; rejected: number; stopped: boolean }> {\n const totals = { accepted: 0, duplicates: 0, rejected: 0, stopped: false };\n let batch: string[] = [];\n let bytes = 0;\n let sent = 0;\n const send = async (): Promise<boolean> => {\n if (!batch.length) return true;\n const body = `{\"receipts\":[${batch.join(\",\")}]}`;\n const started = io.now();\n for (let attempt = 1; ; attempt++) {\n const answer = await call(io, connection, `/v1/recover/${encodeURIComponent(id)}/receipts`, body).catch((error: Error) => ({ status: 0, json: { error: error.message } } as Answer));\n if (answer.status === 200) {\n totals.accepted += Number(answer.json.accepted ?? 0);\n totals.duplicates += Number(answer.json.duplicates ?? 0);\n totals.rejected += Number(answer.json.rejected_count ?? 0);\n break;\n }\n // Durable but not yet readable, or a network error: the same batch is safe to resend.\n // A laptop that sleeps or changes networks loses the connection for minutes, not\n // seconds: keep resending with backoff for up to RETRY_FOR_MS before giving up.\n if ((answer.status === 0 || answer.status >= 500) && io.now() - started < RETRY_FOR_MS) {\n const wait = Math.min(60_000, 2_000 * 2 ** Math.min(attempt - 1, 5));\n io.log(` ${answer.status === 0 ? \"Connection lost\" : `The workspace answered ${answer.status}`}; trying the same batch again in ${Math.round(wait / 1000)} s…`);\n await io.sleep(wait);\n continue;\n }\n io.log(` Stopped: ${String(answer.json.error ?? `HTTP ${answer.status}`)}. Records already sent stay recovered; run recover again to continue.`);\n totals.stopped = true;\n return false;\n }\n sent += batch.length;\n if (sent % 1000 < batch.length || sent === group.count) io.log(` … ${sent.toLocaleString()} of ${group.count.toLocaleString()}`);\n batch = []; bytes = 0;\n return true;\n };\n for (let after = 0; ;) {\n const page = store.page(after, PAGE);\n if (!page.length) break;\n for (const { id: row, receipt } of page) {\n after = row;\n if (kidOf(receipt) !== group.kid) continue;\n const json = JSON.stringify(receipt);\n const size = Buffer.byteLength(json);\n if (size > MAX_RECEIPT_BYTES) { totals.rejected += 1; continue; }\n if (batch.length >= MAX_BATCH_COUNT || bytes + size + 1 > MAX_BATCH_BYTES) { if (!(await send())) return totals; }\n batch.push(json); bytes += size + 1;\n }\n }\n if (!(await send())) return totals;\n return totals;\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -47,6 +47,11 @@ export declare const ci: (s: string) => string;
47
47
  export declare const under: (dir: string) => string;
48
48
  export declare const named: (file: string) => string;
49
49
  export declare const dir: (d: string) => string;
50
+ /** The write protection that is always on, whoever manages the rules: the hook's own settings and those of the agents it
51
+ * guards. A workspace can relax CI-configuration writes; it can never relax these. */
52
+ export declare const GUARDRAIL_WRITE_PATTERN: string;
53
+ /** The always-on floor as bare lookaheads, for a pattern that must also refuse these paths (the hook's own folder included). */
54
+ export declare const GUARDRAIL_LOOKAHEADS: string;
50
55
  export declare const DESTRUCTIVE: string[];
51
56
  /** Upgrade the starter-policy patterns an older hook wrote, in memory, so a user who
52
57
  * installed an earlier version gets the current protections without re-running init.
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAO/E,OAAO,EAAoB,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iFAC6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB;2EACuE;IACvE,yFAAyF;IACzF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7G;AAWD,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,KAAG,MAAiF,CAAC;AACjH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,MAAyC,CAAC;AAC9E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC1E,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAmC,CAAC;AA0CpE,eAAO,MAAM,WAAW,UAGvB,CAAC;AAwBF;;uFAEuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQpD;AAED;;;0BAG0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCvE;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;;IAoCnD;qFACiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAG5B;;;;2DAIuD;aAC9C,IAAI;IAKb,gEAAgE;wBACtC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BpD;8EAC0E;qBACnD,MAAM,GAAG,MAAM,EAAE,YAAW;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,OAAO,CAAC,QAAQ,CAAC;EAiDpG"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAO/E,OAAO,EAAoB,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iFAC6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB;2EACuE;IACvE,yFAAyF;IACzF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7G;AAWD,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,KAAG,MAAiF,CAAC;AACjH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,MAAyC,CAAC;AAC9E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC1E,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAmC,CAAC;AAuBpE;uFACuF;AACvF,eAAO,MAAM,uBAAuB,QAAwC,CAAC;AAC7E,gIAAgI;AAChI,eAAO,MAAM,oBAAoB,QAAmD,CAAC;AAoBrF,eAAO,MAAM,WAAW,UAGvB,CAAC;AAwBF;;uFAEuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQpD;AAED;;;0BAG0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCvE;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;;IAoCnD;qFACiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAG5B;;;;2DAIuD;aAC9C,IAAI;IAKb,gEAAgE;wBACtC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BpD;8EAC0E;qBACnD,MAAM,GAAG,MAAM,EAAE,YAAW;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,OAAO,CAAC,QAAQ,CAAC;EAiDpG"}
package/dist/runtime.js CHANGED
@@ -49,6 +49,11 @@ const CI_WRITE = [
49
49
  // upgraded in memory (see LEGACY_STARTER_PATTERNS).
50
50
  const PROTECTED_WRITE_PREV = "^" + [...GUARDRAIL_WRITE_PREV, ...CI_WRITE_PREV].join("") + ".+";
51
51
  const PROTECTED_WRITE = "^" + [...GUARDRAIL_WRITE, ...CI_WRITE].join("") + ".+";
52
+ /** The write protection that is always on, whoever manages the rules: the hook's own settings and those of the agents it
53
+ * guards. A workspace can relax CI-configuration writes; it can never relax these. */
54
+ export const GUARDRAIL_WRITE_PATTERN = "^" + GUARDRAIL_WRITE.join("") + ".+";
55
+ /** The always-on floor as bare lookaheads, for a pattern that must also refuse these paths (the hook's own folder included). */
56
+ export const GUARDRAIL_LOOKAHEADS = under("\\.scopebond") + GUARDRAIL_WRITE.join("");
52
57
  const PROTECTED_READ = "^" + [
53
58
  under("\\.scopebond"),
54
59
  `(?!.*${ci("\\.(?:key|pem|p12|pfx|jks|keystore)")}$)`,