clinkx 1.1.1 → 1.2.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 (199) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +97 -13
  3. package/clinkx-workflows/dist/allowed-roots.js +2 -0
  4. package/clinkx-workflows/dist/allowed-roots.js.map +1 -1
  5. package/clinkx-workflows/dist/architecture-comparison/admission.d.ts +5 -0
  6. package/clinkx-workflows/dist/architecture-comparison/admission.js +50 -0
  7. package/clinkx-workflows/dist/architecture-comparison/admission.js.map +1 -0
  8. package/clinkx-workflows/dist/architecture-comparison/contract.d.ts +995 -0
  9. package/clinkx-workflows/dist/architecture-comparison/contract.js +149 -0
  10. package/clinkx-workflows/dist/architecture-comparison/contract.js.map +1 -0
  11. package/clinkx-workflows/dist/architecture-comparison/runtime.d.ts +29 -0
  12. package/clinkx-workflows/dist/architecture-comparison/runtime.js +387 -0
  13. package/clinkx-workflows/dist/architecture-comparison/runtime.js.map +1 -0
  14. package/clinkx-workflows/dist/child-env.d.ts +3 -1
  15. package/clinkx-workflows/dist/child-env.js +6 -2
  16. package/clinkx-workflows/dist/child-env.js.map +1 -1
  17. package/clinkx-workflows/dist/config.d.ts +28 -5
  18. package/clinkx-workflows/dist/config.js +137 -20
  19. package/clinkx-workflows/dist/config.js.map +1 -1
  20. package/clinkx-workflows/dist/contained-file.js +10 -1
  21. package/clinkx-workflows/dist/contained-file.js.map +1 -1
  22. package/clinkx-workflows/dist/council-execution-policy.d.ts +4 -2
  23. package/clinkx-workflows/dist/council-execution-policy.js +5 -1
  24. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -1
  25. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +72 -72
  26. package/clinkx-workflows/dist/dashboard/contracts.d.ts +1564 -122
  27. package/clinkx-workflows/dist/dashboard/contracts.js +4 -0
  28. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
  29. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +12 -12
  30. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +8 -8
  31. package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
  32. package/clinkx-workflows/dist/dashboard/scanner.js +27 -0
  33. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
  34. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +48 -48
  35. package/clinkx-workflows/dist/dashboard/session-log-identity.js +2 -1
  36. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -1
  37. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +2 -0
  38. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +4 -0
  39. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
  40. package/clinkx-workflows/dist/dashboard/verified-repair-contract.d.ts +191 -0
  41. package/clinkx-workflows/dist/dashboard/verified-repair-contract.js +28 -0
  42. package/clinkx-workflows/dist/dashboard/verified-repair-contract.js.map +1 -0
  43. package/clinkx-workflows/dist/dashboard-ui/assets/index-BdpmbPps.js +115 -0
  44. package/clinkx-workflows/dist/dashboard-ui/assets/style-Im5i1iAs.css +1 -0
  45. package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
  46. package/clinkx-workflows/dist/engine.d.ts +6 -0
  47. package/clinkx-workflows/dist/engine.js +78 -7
  48. package/clinkx-workflows/dist/engine.js.map +1 -1
  49. package/clinkx-workflows/dist/index.d.ts +3 -0
  50. package/clinkx-workflows/dist/index.js +3 -2
  51. package/clinkx-workflows/dist/index.js.map +1 -1
  52. package/clinkx-workflows/dist/reducers/index.d.ts +9 -1
  53. package/clinkx-workflows/dist/reducers/index.js +22 -0
  54. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  55. package/clinkx-workflows/dist/root-path-validation.d.ts +12 -0
  56. package/clinkx-workflows/dist/root-path-validation.js +63 -0
  57. package/clinkx-workflows/dist/root-path-validation.js.map +1 -0
  58. package/clinkx-workflows/dist/rosters.d.ts +1 -0
  59. package/clinkx-workflows/dist/rosters.js +16 -6
  60. package/clinkx-workflows/dist/rosters.js.map +1 -1
  61. package/clinkx-workflows/dist/server.js +5 -2
  62. package/clinkx-workflows/dist/server.js.map +1 -1
  63. package/clinkx-workflows/dist/state-schema.d.ts +13 -0
  64. package/clinkx-workflows/dist/state.d.ts +7 -1
  65. package/clinkx-workflows/dist/state.js +90 -19
  66. package/clinkx-workflows/dist/state.js.map +1 -1
  67. package/clinkx-workflows/dist/validators/architecture-comparison.d.ts +2 -0
  68. package/clinkx-workflows/dist/validators/architecture-comparison.js +12 -0
  69. package/clinkx-workflows/dist/validators/architecture-comparison.js.map +1 -0
  70. package/clinkx-workflows/dist/validators/index.js +4 -0
  71. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  72. package/clinkx-workflows/dist/validators/verified-repair.d.ts +2 -0
  73. package/clinkx-workflows/dist/validators/verified-repair.js +12 -0
  74. package/clinkx-workflows/dist/validators/verified-repair.js.map +1 -0
  75. package/clinkx-workflows/dist/verification-proof.d.ts +254 -0
  76. package/clinkx-workflows/dist/verification-proof.js +195 -0
  77. package/clinkx-workflows/dist/verification-proof.js.map +1 -0
  78. package/clinkx-workflows/dist/verified-repair/admission.d.ts +72 -0
  79. package/clinkx-workflows/dist/verified-repair/admission.js +63 -0
  80. package/clinkx-workflows/dist/verified-repair/admission.js.map +1 -0
  81. package/clinkx-workflows/dist/verified-repair/contract.d.ts +1910 -0
  82. package/clinkx-workflows/dist/verified-repair/contract.js +127 -0
  83. package/clinkx-workflows/dist/verified-repair/contract.js.map +1 -0
  84. package/clinkx-workflows/dist/verified-repair/host.d.ts +32 -0
  85. package/clinkx-workflows/dist/verified-repair/host.js +2 -0
  86. package/clinkx-workflows/dist/verified-repair/host.js.map +1 -0
  87. package/clinkx-workflows/dist/verified-repair/prompts.d.ts +12 -0
  88. package/clinkx-workflows/dist/verified-repair/prompts.js +58 -0
  89. package/clinkx-workflows/dist/verified-repair/prompts.js.map +1 -0
  90. package/clinkx-workflows/dist/verified-repair/result.d.ts +16 -0
  91. package/clinkx-workflows/dist/verified-repair/result.js +102 -0
  92. package/clinkx-workflows/dist/verified-repair/result.js.map +1 -0
  93. package/clinkx-workflows/dist/verified-repair/runtime.d.ts +28 -0
  94. package/clinkx-workflows/dist/verified-repair/runtime.js +186 -0
  95. package/clinkx-workflows/dist/verified-repair/runtime.js.map +1 -0
  96. package/clinkx-workflows/dist/workflow-completion.d.ts +83 -0
  97. package/clinkx-workflows/dist/workflow-completion.js +250 -0
  98. package/clinkx-workflows/dist/workflow-completion.js.map +1 -0
  99. package/clinkx-workflows/dist/workflow-tools.d.ts +7 -3
  100. package/clinkx-workflows/dist/workflow-tools.js +487 -46
  101. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  102. package/clinkx-workflows/dist/workflow-verification-contract.d.ts +539 -0
  103. package/clinkx-workflows/dist/workflow-verification-contract.js +52 -0
  104. package/clinkx-workflows/dist/workflow-verification-contract.js.map +1 -0
  105. package/clinkx-workflows/dist/workflow-verification.d.ts +15 -0
  106. package/clinkx-workflows/dist/workflow-verification.js +218 -0
  107. package/clinkx-workflows/dist/workflow-verification.js.map +1 -0
  108. package/clinkx-workflows/templates/architecture-comparison/candidate.txt +29 -0
  109. package/clinkx-workflows/templates/architecture-comparison/judge.txt +31 -0
  110. package/clinkx-workflows/templates/verified-repair/investigate.txt +20 -0
  111. package/clinkx-workflows/templates/verified-repair/repair.txt +11 -0
  112. package/clinkx-workflows/templates/verified-repair/review.txt +18 -0
  113. package/clinkx-workflows/workflows/architecture-comparison.yaml +133 -0
  114. package/clinkx-workflows/workflows/verified-repair.yaml +104 -0
  115. package/dist/allowed-roots.d.ts +1 -1
  116. package/dist/allowed-roots.js +128 -15
  117. package/dist/allowed-roots.js.map +1 -1
  118. package/dist/codex/app-server-client.d.ts +1 -0
  119. package/dist/codex/app-server-client.js +3 -2
  120. package/dist/codex/app-server-client.js.map +1 -1
  121. package/dist/context-builder/config.d.ts +1 -0
  122. package/dist/context-builder/config.js +21 -9
  123. package/dist/context-builder/config.js.map +1 -1
  124. package/dist/context-builder/diagnostics.d.ts +1 -0
  125. package/dist/context-builder/diagnostics.js +1 -1
  126. package/dist/context-builder/diagnostics.js.map +1 -1
  127. package/dist/context-builder/discovery.d.ts +18 -1
  128. package/dist/context-builder/discovery.js +69 -1
  129. package/dist/context-builder/discovery.js.map +1 -1
  130. package/dist/context-builder/handler.js +8 -1
  131. package/dist/context-builder/handler.js.map +1 -1
  132. package/dist/context-builder/lifecycle-contract.d.ts +24 -24
  133. package/dist/context-builder/mutation-lock.d.ts +4 -0
  134. package/dist/context-builder/mutation-lock.js +7 -1
  135. package/dist/context-builder/mutation-lock.js.map +1 -1
  136. package/dist/context-builder/refine-handler.js +16 -1
  137. package/dist/context-builder/refine-handler.js.map +1 -1
  138. package/dist/context-builder/secrets.d.ts +11 -0
  139. package/dist/context-builder/secrets.js +75 -0
  140. package/dist/context-builder/secrets.js.map +1 -1
  141. package/dist/context-builder/storage.d.ts +10 -2
  142. package/dist/context-builder/storage.js +213 -16
  143. package/dist/context-builder/storage.js.map +1 -1
  144. package/dist/context-builder/versions.d.ts +5 -2
  145. package/dist/context-builder/versions.js +5 -2
  146. package/dist/context-builder/versions.js.map +1 -1
  147. package/dist/dashboard-cli.d.ts +6 -0
  148. package/dist/dashboard-cli.js +18 -32
  149. package/dist/dashboard-cli.js.map +1 -1
  150. package/dist/dashboard-gateway/main.js +8 -3
  151. package/dist/dashboard-gateway/main.js.map +1 -1
  152. package/dist/index.d.ts +2 -0
  153. package/dist/index.js +8 -2
  154. package/dist/index.js.map +1 -1
  155. package/dist/project-verification/contract.d.ts +537 -0
  156. package/dist/project-verification/contract.js +40 -0
  157. package/dist/project-verification/contract.js.map +1 -0
  158. package/dist/project-verification/files.d.ts +23 -0
  159. package/dist/project-verification/files.js +80 -0
  160. package/dist/project-verification/files.js.map +1 -0
  161. package/dist/project-verification/process.d.ts +7 -0
  162. package/dist/project-verification/process.js +115 -0
  163. package/dist/project-verification/process.js.map +1 -0
  164. package/dist/project-verification/runner.d.ts +35 -0
  165. package/dist/project-verification/runner.js +185 -0
  166. package/dist/project-verification/runner.js.map +1 -0
  167. package/dist/project-verification/service.d.ts +37 -0
  168. package/dist/project-verification/service.js +151 -0
  169. package/dist/project-verification/service.js.map +1 -0
  170. package/dist/provider-doctor.d.ts +22 -0
  171. package/dist/provider-doctor.js +22 -0
  172. package/dist/provider-doctor.js.map +1 -0
  173. package/dist/registry.d.ts +1 -1
  174. package/dist/registry.js +5 -1
  175. package/dist/registry.js.map +1 -1
  176. package/dist/server.js +41 -6
  177. package/dist/server.js.map +1 -1
  178. package/dist/verification-tools.d.ts +90 -0
  179. package/dist/verification-tools.js +120 -0
  180. package/dist/verification-tools.js.map +1 -0
  181. package/dist/verified-repair/files.d.ts +22 -0
  182. package/dist/verified-repair/files.js +114 -0
  183. package/dist/verified-repair/files.js.map +1 -0
  184. package/dist/verified-repair/host.d.ts +14 -0
  185. package/dist/verified-repair/host.js +612 -0
  186. package/dist/verified-repair/host.js.map +1 -0
  187. package/docs/configuration.md +224 -12
  188. package/docs/engineering-recipes.md +415 -0
  189. package/docs/security.md +3 -0
  190. package/docs/verification/README.md +59 -0
  191. package/docs/verification/dashboard-real-disk.md +436 -0
  192. package/docs/verification/project-checks.example.json +14 -0
  193. package/docs/verification/project-checks.md +444 -0
  194. package/docs/verification/workflow-handoff.md +113 -0
  195. package/package.json +10 -2
  196. package/scripts/provider-doctor-core.mjs +380 -0
  197. package/scripts/provider-doctor-probe.mjs +34 -0
  198. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +0 -106
  199. package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +0 -1
@@ -0,0 +1,612 @@
1
+ import { lstatSync, mkdtempSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { z } from "zod";
5
+ import { assertIndependentRepairReview, assertRepairBounded, assertRepairCitations, FixProposalSchema, InvestigationSchema, MAX_REPAIR_CONTRACT_BYTES, MAX_REPAIR_PACKET_BYTES, MAX_REPAIR_PROPOSAL_BYTES, MAX_REPAIR_RECORD_BYTES, parseRegressionOutcome, RepairInputSchema, RepairPacketSchema, RepairRecordSchema, RepairReviewSchema, repairSourceDigest, RelativePathSchema, RepairRoutesSchema, RepairSourceFactSchema, ReplacementBatchSchema, } from "../../clinkx-workflows/dist/verified-repair/contract.js";
6
+ import { readContainedAbsoluteFile } from "../../clinkx-workflows/dist/contained-file.js";
7
+ import { parseAllowedRootsEnv } from "../allowed-roots.js";
8
+ import { hasDisallowedSecretMaterial, scanSecretPath } from "../context-builder/secrets.js";
9
+ import { hasCredentialTokenShape } from "../../clinkx-workflows/dist/dashboard/session-log-redaction.js";
10
+ import { ExpectedSchema, ManifestSchema, ResultSchema } from "../project-verification/contract.js";
11
+ import { json, readBounded, runtimeDigest, safePath, sha } from "../project-verification/files.js";
12
+ import { assertCurrent, assertSeparated, authorizedDirectory, captureFiles, materialize, reconcileAppliedPaths, text, writeReplacement } from "./files.js";
13
+ class RepairGateError extends Error {
14
+ status;
15
+ constructor(message, status = "failed") {
16
+ super(message);
17
+ this.status = status;
18
+ }
19
+ }
20
+ const proofRef = (proof) => ({ phase: proof.phase,
21
+ evidence_directory: proof.evidence_directory, expectations_sha256: proof.expectations_sha256 });
22
+ const facts = (files, replacements) => files.map((file) => {
23
+ const bytes = replacements.get(file.path) ?? file.bytes;
24
+ return { path: file.path, sha256: sha(bytes), size_bytes: bytes.length };
25
+ });
26
+ const SourceModesSchema = z.array(z.object({ path: RelativePathSchema, mode: z.number().int().min(0).max(0o7777) }).strict()).min(1).max(1000);
27
+ export class NativeVerifiedRepairHost {
28
+ service;
29
+ rename;
30
+ constructor(service, rename = renameSync) {
31
+ this.service = service;
32
+ this.rename = rename;
33
+ }
34
+ async prepare(options) {
35
+ return this.service.operation(async (native) => {
36
+ const read = await readContainedAbsoluteFile(parseAllowedRootsEnv({ rejectBroadRoots: true }), options.contractPath, { wantContent: true, requireRegular: true, requireExactRequestedPath: true, maxBytes: MAX_REPAIR_CONTRACT_BYTES });
37
+ const contractText = read.content;
38
+ if (hasCredentialTokenShape(contractText) || hasDisallowedSecretMaterial(contractText, false))
39
+ throw new Error("repair contract contains credential-like material; remove it before capture");
40
+ let input;
41
+ try {
42
+ input = RepairInputSchema.parse(JSON.parse(contractText));
43
+ }
44
+ catch {
45
+ throw new Error("invalid reviewed repair contract; use the strict clinkx-verified-repair-input-v1 schema");
46
+ }
47
+ const project = authorizedDirectory(input.project_path, true);
48
+ const parent = authorizedDirectory(input.evidence_parent, false);
49
+ const copyParent = authorizedDirectory(input.verification_copy_parent, true);
50
+ assertSeparated(project, parent, copyParent);
51
+ const manifests = [...new Set([input.regression_manifest, input.required_manifest,
52
+ ...(input.copy_preparation.status === "declared" ? [input.copy_preparation.manifest] : [])])];
53
+ const declared = [...new Set([...input.source_inputs, ...manifests])];
54
+ if (declared.some((path) => scanSecretPath(path).length !== 0))
55
+ throw new Error("credential-like paths cannot be captured or copied by verified repair");
56
+ const files = captureFiles(project, declared);
57
+ if (files.some((file) => hasCredentialTokenShape(text(file.bytes)) || hasDisallowedSecretMaterial(text(file.bytes), false)))
58
+ throw new Error("declared source contains credential-like material; it cannot enter a repair capture or copy");
59
+ for (const path of manifests) {
60
+ const captured = files.find((file) => file.path === path);
61
+ if (captured.bytes.length > MAX_REPAIR_CONTRACT_BYTES)
62
+ throw new Error("reviewed manifest exceeds 256 KiB");
63
+ const manifest = ManifestSchema.parse(JSON.parse(text(captured.bytes)));
64
+ if (manifest.inputs.some((path) => !input.source_inputs.includes(path))
65
+ || input.source_inputs.some((path) => !manifest.inputs.includes(path))) {
66
+ throw new Error("each reviewed manifest must include every declared source input and no undeclared inputs");
67
+ }
68
+ if (path === input.regression_manifest && (manifest.checks.length !== 1 || manifest.checks[0].id !== input.observation.check_id)) {
69
+ throw new Error("regression manifest must have exactly the bound observation check");
70
+ }
71
+ }
72
+ for (const path of [...input.regression_paths, ...input.implementation_paths]) {
73
+ const file = files.find((file) => file.path === path);
74
+ if (file.bytes.length > 64 * 1024)
75
+ throw new Error("writable repair file exceeds 64 KiB; narrow the repair scope");
76
+ text(file.bytes);
77
+ }
78
+ const originalFacts = facts(files, new Map());
79
+ // Dispatch uses this frozen data, never the original contract or live source path.
80
+ const runtime = { sha256: runtimeDigest(), node_version: process.version };
81
+ const binding = { contract_sha256: sha(contractText), input, source_facts: originalFacts,
82
+ source_modes: files.map((file) => ({ path: file.path, mode: file.mode })), routes: options.routes, runtime };
83
+ const frozenDigest = sha(json(binding));
84
+ const context = json({ ...binding, sources: files.map((file) => ({ path: file.path, content: text(file.bytes), original_content: text(file.bytes) })) });
85
+ assertRepairBounded({ context, source_facts: originalFacts }, MAX_REPAIR_PACKET_BYTES);
86
+ const reread = await readContainedAbsoluteFile(parseAllowedRootsEnv({ rejectBroadRoots: true }), options.contractPath, { wantContent: true, requireRegular: true, requireExactRequestedPath: true, maxBytes: MAX_REPAIR_CONTRACT_BYTES });
87
+ if (sha(reread.content) !== binding.contract_sha256)
88
+ throw new Error("repair contract changed during capture");
89
+ assertCurrent(project, new Map(files.map((file) => [file.path, file.sha256])), new Map(files.map((file) => [file.path, file.mode])));
90
+ native.signal.throwIfAborted();
91
+ const evidence = mkdtempSync(join(parent, `repair-${options.runId}-`));
92
+ try {
93
+ mkdirSync(join(evidence, "capture"), { mode: 0o700 });
94
+ for (const [index, file] of files.entries())
95
+ writeFileSync(join(evidence, "capture", `${index}.bytes`), file.bytes, { flag: "wx", mode: 0o600 });
96
+ writeFileSync(join(evidence, "contract.json"), contractText, { flag: "wx", mode: 0o600 });
97
+ writeFileSync(join(evidence, "capture.json"), json({ frozen_digest: frozenDigest, ...binding }), { flag: "wx", mode: 0o600 });
98
+ return new PreparedNativeRepair(this.service, options, { ...input, project_path: project, evidence_parent: parent,
99
+ verification_copy_parent: copyParent }, files, frozenDigest, evidence, runtime, this.rename);
100
+ }
101
+ catch (error) {
102
+ rmSync(evidence, { recursive: true, force: true });
103
+ throw error;
104
+ }
105
+ }, options.signal);
106
+ }
107
+ async readInterruptedRun(options) {
108
+ return this.service.operation(async () => {
109
+ let descriptorBytes;
110
+ try {
111
+ descriptorBytes = readBounded(options.runDir, "verified-repair-capture.v1.json", 4096);
112
+ }
113
+ catch (error) {
114
+ if (error.code === "ENOENT")
115
+ return undefined;
116
+ throw error;
117
+ }
118
+ const descriptor = JSON.parse(descriptorBytes.toString("utf8"));
119
+ if (descriptor["schema_version"] !== "clinkx-verified-repair-capture-v1" || descriptor["run_id"] !== options.runId
120
+ || typeof descriptor["evidence_directory"] !== "string" || typeof descriptor["capture_sha256"] !== "string")
121
+ throw new Error("interrupted capture descriptor is invalid");
122
+ const evidence = authorizedDirectory(descriptor["evidence_directory"], false);
123
+ const captureBytes = readBounded(evidence, "capture.json", MAX_REPAIR_CONTRACT_BYTES);
124
+ if (sha(captureBytes) !== descriptor["capture_sha256"])
125
+ throw new Error("interrupted capture identity is unavailable");
126
+ const capture = JSON.parse(captureBytes.toString("utf8"));
127
+ const input = RepairInputSchema.parse(capture["input"]);
128
+ const routes = RepairRoutesSchema.parse(capture["routes"]);
129
+ const sourceFacts = RepairSourceFactSchema.array().min(1).max(1000).parse(capture["source_facts"]);
130
+ const modes = SourceModesSchema.parse(capture["source_modes"]);
131
+ if (modes.length !== sourceFacts.length || new Set(modes.map((entry) => entry.path)).size !== modes.length
132
+ || modes.some((entry) => !sourceFacts.some((fact) => fact.path === entry.path)))
133
+ throw new Error("interrupted mode capture is invalid");
134
+ const { frozen_digest: frozenDigest, ...binding } = capture;
135
+ if (typeof frozenDigest !== "string" || sha(json(binding)) !== frozenDigest)
136
+ throw new Error("interrupted frozen capture does not bind its facts");
137
+ const project = authorizedDirectory(input.project_path, true);
138
+ if (relative(input.evidence_parent, evidence).startsWith("..") || !evidence.startsWith(join(input.evidence_parent, `repair-${options.runId}-`)))
139
+ throw new Error("interrupted evidence path does not belong to the captured run");
140
+ const applied = [];
141
+ const unauthenticated = new Set();
142
+ for (const phase of ["regression", "implementation"]) {
143
+ let bytes;
144
+ try {
145
+ bytes = readBounded(evidence, `${phase}-proposal.json`, MAX_REPAIR_PROPOSAL_BYTES);
146
+ }
147
+ catch (error) {
148
+ if (error.code === "ENOENT")
149
+ continue;
150
+ throw error;
151
+ }
152
+ const batch = ReplacementBatchSchema.parse(JSON.parse(bytes.toString("utf8")));
153
+ let authenticated = false;
154
+ try {
155
+ const pin = JSON.parse(readBounded(options.runDir, `verified-repair-${phase}.v1.json`, 4096).toString("utf8"));
156
+ authenticated = pin["schema_version"] === "clinkx-verified-repair-batch-v1" && pin["run_id"] === options.runId
157
+ && pin["phase"] === phase && pin["frozen_digest"] === frozenDigest && pin["proposal_sha256"] === sha(bytes);
158
+ }
159
+ catch { /* An uncommitted or altered proposal cannot authenticate replacement bytes. */ }
160
+ const allowed = phase === "regression" ? input.regression_paths : input.implementation_paths;
161
+ for (const replacement of batch) {
162
+ if (!allowed.includes(replacement.path) || sourceFacts.find((fact) => fact.path === replacement.path)?.sha256 !== replacement.original_sha256)
163
+ throw new Error("interrupted proposal is outside captured scope");
164
+ applied.push({ phase, path: replacement.path, original_sha256: replacement.original_sha256,
165
+ replacement_sha256: sha(Buffer.from(replacement.replacement_text)), status: "unknown" });
166
+ if (!authenticated)
167
+ unauthenticated.add(replacement.path);
168
+ }
169
+ }
170
+ const current = [];
171
+ let total = 0;
172
+ for (const fact of sourceFacts) {
173
+ try {
174
+ const bytes = readBounded(project, fact.path, Math.min(16 * 1024 * 1024, 64 * 1024 * 1024 - total));
175
+ total += bytes.length;
176
+ if (total > 64 * 1024 * 1024)
177
+ throw new Error("interrupted declared inputs exceed capture bound");
178
+ current.push({ path: fact.path, sha256: sha(bytes), size_bytes: bytes.length });
179
+ }
180
+ catch { /* Missing declared files remain unknown; no reads become mutation authority. */ }
181
+ }
182
+ const reconciled = reconcileAppliedPaths(project, applied);
183
+ for (const entry of reconciled)
184
+ if (unauthenticated.has(entry.path) && entry.status !== "original")
185
+ entry.status = "unknown";
186
+ const unexplained = current.filter((fact) => {
187
+ const original = sourceFacts.find((source) => source.path === fact.path);
188
+ const edit = reconciled.find((entry) => entry.path === fact.path && !unauthenticated.has(entry.path));
189
+ return fact.sha256 !== original.sha256 && (edit === undefined || fact.sha256 !== edit.replacement_sha256);
190
+ });
191
+ const modeDrift = modes.filter((entry) => {
192
+ try {
193
+ return (lstatSync(safePath(project, entry.path)).mode & 0o7777) !== entry.mode;
194
+ }
195
+ catch {
196
+ return true;
197
+ }
198
+ });
199
+ const unknown = reconciled.some((entry) => entry.status === "unknown") || current.length !== sourceFacts.length || unexplained.length > 0 || modeDrift.length > 0;
200
+ return RepairRecordSchema.parse({ schema_version: "clinkx-verified-repair-record-v1", run_id: options.runId,
201
+ execution_status: "interrupted", repair_status: unknown ? "failed" : "incomplete", frozen_digest: frozenDigest,
202
+ project_path: project, routes, source_facts: sourceFacts, current_source_facts: current, applied_paths: reconciled,
203
+ proofs: [], gaps: ["Worker ended without terminal publication; current hashes only classify the retained admitted edits.",
204
+ "Original live proof pins and cleanup completion are unavailable; no verification or mutation replay is authorized.",
205
+ ...[...unauthenticated].slice(0, 12).map((path) => `Uncommitted or altered proposal: ${path}; replacement bytes are unknown.`),
206
+ ...unexplained.slice(0, 12).map((fact) => `Unexplained declared input drift: ${fact.path}`),
207
+ ...modeDrift.slice(0, 12).map((entry) => `Declared input mode drift: ${entry.path}`)],
208
+ error_phase: "interrupted", owner_acceptance: "not_recorded",
209
+ owner_next_steps: ["Inspect current edits and retained private capture; preserve unknown changes and submit a fresh explicit run contract.",
210
+ "Inspect possible .clinkx-repair-*.tmp residue from a killed write; confirm ownership before deleting anything."],
211
+ limitations: ["Read-only interrupted-run reconciliation establishes current declared byte classifications, not successful phase execution or acceptance.",
212
+ "Owned copy or command resources from a killed worker may require operator inspection; this read does not delete or replay anything."],
213
+ });
214
+ });
215
+ }
216
+ }
217
+ class PreparedNativeRepair {
218
+ service;
219
+ options;
220
+ input;
221
+ files;
222
+ frozenDigest;
223
+ evidence;
224
+ runtime;
225
+ rename;
226
+ step = "captured";
227
+ activePhase = "capture";
228
+ failure;
229
+ gaps = [];
230
+ replacements = new Map();
231
+ expected;
232
+ modes;
233
+ applied = [];
234
+ proofs = [];
235
+ integrity = new Map();
236
+ investigation;
237
+ fix;
238
+ review;
239
+ copy;
240
+ closed = false;
241
+ constructor(service, options, input, files, frozenDigest, evidence, runtime, rename) {
242
+ this.service = service;
243
+ this.options = options;
244
+ this.input = input;
245
+ this.files = files;
246
+ this.frozenDigest = frozenDigest;
247
+ this.evidence = evidence;
248
+ this.runtime = runtime;
249
+ this.rename = rename;
250
+ this.expected = new Map(files.map((file) => [file.path, file.sha256]));
251
+ this.modes = new Map(files.map((file) => [file.path, file.mode]));
252
+ }
253
+ async capture(signal) {
254
+ await this.assertProviderPhase("investigate", signal);
255
+ const captureBytes = readBounded(this.evidence, "capture.json", MAX_REPAIR_CONTRACT_BYTES);
256
+ writeFileSync(join(this.options.runDir, "verified-repair-capture.v1.json"), json({ schema_version: "clinkx-verified-repair-capture-v1",
257
+ run_id: this.options.runId, evidence_directory: this.evidence, capture_sha256: sha(captureBytes) }), { mode: 0o600, flag: "wx" });
258
+ return this.packet("capture");
259
+ }
260
+ validateProviderOutput(value) {
261
+ assertRepairBounded(value, MAX_REPAIR_PROPOSAL_BYTES);
262
+ const content = JSON.stringify(value);
263
+ if (hasCredentialTokenShape(content) || hasDisallowedSecretMaterial(content, false))
264
+ throw new RepairGateError("repair artifact contains credential-like material; publication is rejected");
265
+ }
266
+ async assertProviderPhase(phase, signal) {
267
+ signal?.throwIfAborted();
268
+ this.requireStep(phase === "investigate" ? "captured" : phase === "repair" ? "reproduced" : "fixed");
269
+ try {
270
+ this.assertRuntime();
271
+ assertCurrent(this.input.project_path, this.expected, this.modes);
272
+ }
273
+ catch (error) {
274
+ this.fail(error);
275
+ throw error;
276
+ }
277
+ if (phase === "review") {
278
+ try {
279
+ await this.service.operation((native) => this.inspectFixed(native), signal);
280
+ }
281
+ catch (error) {
282
+ this.fail(error);
283
+ throw error;
284
+ }
285
+ }
286
+ }
287
+ async reproduce(proposal, signal) {
288
+ return this.phase("reproduce", signal, async (native) => {
289
+ this.requireStep("captured");
290
+ assertRepairBounded(proposal, MAX_REPAIR_PROPOSAL_BYTES);
291
+ const parsed = InvestigationSchema.parse(proposal);
292
+ this.assertIdentity(parsed.frozen_digest);
293
+ assertRepairCitations(parsed.diagnosis.citations, facts(this.files, this.replacements), this.sourceText());
294
+ this.investigation = parsed;
295
+ this.apply(parsed.regression_replacements, "regression", this.input.regression_paths, native.signal);
296
+ await this.run(native, "reproduce", this.input.project_path, this.input.regression_manifest, "assertion_failed");
297
+ this.step = "reproduced";
298
+ return this.packet("reproduce");
299
+ });
300
+ }
301
+ async fixed(proposal, signal) {
302
+ return this.phase("fixed", signal, async (native) => {
303
+ this.requireStep("reproduced");
304
+ assertRepairBounded(proposal, MAX_REPAIR_PROPOSAL_BYTES);
305
+ const parsed = FixProposalSchema.parse(proposal);
306
+ this.assertIdentity(parsed.frozen_digest);
307
+ assertRepairCitations(parsed.citations, facts(this.files, this.replacements), this.sourceText());
308
+ this.fix = parsed;
309
+ this.apply(parsed.implementation_replacements, "implementation", this.input.implementation_paths, native.signal);
310
+ for (const proof of this.proofs)
311
+ proof.applicable = false;
312
+ await this.run(native, "fixed_regression", this.input.project_path, this.input.regression_manifest, "assertion_passed");
313
+ await this.run(native, "fixed_required", this.input.project_path, this.input.required_manifest);
314
+ this.inspectFixed(native);
315
+ this.step = "fixed";
316
+ return this.packet("fixed");
317
+ });
318
+ }
319
+ async independently(review, signal) {
320
+ return this.phase("independent", signal, async (native) => {
321
+ this.requireStep("fixed");
322
+ assertRepairBounded(review, MAX_REPAIR_PROPOSAL_BYTES);
323
+ this.review = RepairReviewSchema.parse(review);
324
+ assertIndependentRepairReview(this.review, this.frozenDigest, facts(this.files, this.replacements), this.reviewProofs(), this.sourceText());
325
+ this.inspectFixed(native);
326
+ if (this.input.copy_preparation.status === "unavailable")
327
+ throw new RepairGateError("independent verification unavailable: " + this.input.copy_preparation.reason, "blocked");
328
+ this.copy = mkdtempSync(join(this.input.verification_copy_parent, `clinkx-repair-${this.options.runId}-`));
329
+ try {
330
+ materialize(this.copy, this.files, this.replacements);
331
+ this.checkCopy();
332
+ await this.run(native, "copy_preparation", this.copy, this.input.copy_preparation.manifest);
333
+ this.checkCopy();
334
+ await this.run(native, "copy_fixed", this.copy, this.input.regression_manifest, "assertion_passed");
335
+ for (const path of this.input.implementation_paths) {
336
+ const file = this.files.find((file) => file.path === path);
337
+ writeReplacement(this.copy, path, file.bytes, file.mode, this.expected.get(path), native.signal);
338
+ }
339
+ this.checkCopy(true);
340
+ await this.run(native, "copy_without_fix", this.copy, this.input.regression_manifest, "assertion_failed", true);
341
+ for (const path of this.input.implementation_paths) {
342
+ const file = this.files.find((file) => file.path === path);
343
+ writeReplacement(this.copy, path, this.replacements.get(path) ?? file.bytes, file.mode, file.sha256, native.signal);
344
+ }
345
+ this.checkCopy();
346
+ await this.run(native, "copy_restored", this.copy, this.input.regression_manifest, "assertion_passed");
347
+ this.inspectFixed(native);
348
+ await this.run(native, "final_required", this.input.project_path, this.input.required_manifest);
349
+ const final = this.proofs.find((proof) => proof.phase === "final_required");
350
+ this.inspectProof(native, final);
351
+ const attached = await native.attach({ project: this.input.project_path, manifest: this.input.required_manifest,
352
+ evidence: final.evidence_directory }, { runId: this.options.runId, runsRoot: resolve(this.options.runDir, ".."),
353
+ requestId: randomUUID(), pin: final.expectations_sha256 });
354
+ if (attached.status !== "complete")
355
+ throw new RepairGateError("final verification attachment is not complete");
356
+ this.step = "independent";
357
+ return this.packet("independent");
358
+ }
359
+ finally {
360
+ this.removeCopy();
361
+ }
362
+ });
363
+ }
364
+ async abort(reason) {
365
+ if (reason !== null && typeof reason === "object" && "kind" in reason && reason.kind === "invalid_artifact") {
366
+ this.failure = "failed";
367
+ this.activePhase = "phase" in reason && typeof reason.phase === "string" ? reason.phase : "provider_artifact";
368
+ const message = "message" in reason && typeof reason.message === "string" ? reason.message : "invalid provider artifact";
369
+ if (this.gaps.length < 64)
370
+ this.gaps.push(message.slice(0, 6000));
371
+ return;
372
+ }
373
+ if (this.failure === undefined) {
374
+ this.failure = "incomplete";
375
+ this.gaps.push(reason instanceof Error ? reason.message.slice(0, 6000) || "workflow interrupted" : "workflow interrupted or provider delivery unavailable");
376
+ }
377
+ }
378
+ async cleanup() { this.removeCopy(); this.closed = true; }
379
+ async record(executionStatus) {
380
+ const current = [];
381
+ for (const file of this.files) {
382
+ try {
383
+ const bytes = readBounded(this.input.project_path, file.path);
384
+ current.push({ path: file.path, sha256: sha(bytes), size_bytes: bytes.length });
385
+ }
386
+ catch {
387
+ if (!this.gaps.includes(`declared input unavailable: ${file.path}`))
388
+ this.gaps.push(`declared input unavailable: ${file.path}`);
389
+ this.failure = "failed";
390
+ }
391
+ }
392
+ try {
393
+ assertCurrent(this.input.project_path, this.expected, this.modes);
394
+ }
395
+ catch (error) {
396
+ this.failure = "failed";
397
+ this.fail(error);
398
+ for (const proof of this.proofs)
399
+ if (proof.project_path === this.input.project_path)
400
+ proof.applicable = false;
401
+ }
402
+ for (const [index, entry] of reconcileAppliedPaths(this.input.project_path, this.applied).entries())
403
+ this.applied[index] = entry;
404
+ if (current.some((file) => this.expected.get(file.path) !== file.sha256)) {
405
+ this.failure = "failed";
406
+ for (const proof of this.proofs)
407
+ if (proof.project_path === this.input.project_path)
408
+ proof.applicable = false;
409
+ if (!this.gaps.includes("declared inputs changed before terminal publication"))
410
+ this.gaps.push("declared inputs changed before terminal publication");
411
+ }
412
+ const verified = this.step === "independent" && this.failure === undefined && executionStatus === "completed" && this.closed;
413
+ if (!verified && this.closed && this.gaps.length === 0)
414
+ this.gaps.push("repair did not complete every required phase");
415
+ const record = RepairRecordSchema.parse({ schema_version: "clinkx-verified-repair-record-v1", run_id: this.options.runId,
416
+ execution_status: executionStatus, repair_status: verified ? "verified" : this.failure ?? "incomplete", frozen_digest: this.frozenDigest,
417
+ project_path: this.input.project_path, routes: this.options.routes, source_facts: facts(this.files, new Map()), current_source_facts: current,
418
+ ...(this.step === "fixed" || this.step === "independent" ? { fixed_source_digest: repairSourceDigest(facts(this.files, this.replacements)) } : {}),
419
+ applied_paths: this.applied, proofs: this.proofs, ...(this.review === undefined ? {} : { review: this.review }),
420
+ gaps: this.gaps, ...(verified ? {} : { error_phase: this.activePhase }), owner_acceptance: "not_recorded",
421
+ owner_next_steps: ["Inspect the retained edits, causal proof and independent findings before recording acceptance or integrating.",
422
+ ...(verified ? [] : ["Preserve current edits; resolve the reported gap and submit a fresh explicit repair contract. Interrupted mutations cannot resume."])],
423
+ limitations: ["Scope is limited to declared inputs and reviewed commands; external services and the full environment are not authenticated.",
424
+ "Providers have prompt restrictions, without an enforced filesystem sandbox.", "In-place writes are per-file replacements, not a multi-file transaction; failed or cancelled runs preserve edits.",
425
+ "Copy evidence identifies the disposable copy; final required proof identifies the selected working tree.", "Verification is separate from owner acceptance and general engineering benefit remains unknown."],
426
+ });
427
+ assertRepairBounded(record, MAX_REPAIR_RECORD_BYTES);
428
+ if (this.closed) {
429
+ const temporary = join(this.evidence, `.terminal-${randomUUID()}.tmp`);
430
+ writeFileSync(temporary, json(record), { mode: 0o600, flag: "wx" });
431
+ renameSync(temporary, join(this.evidence, "terminal-record.json"));
432
+ }
433
+ return record;
434
+ }
435
+ requireStep(step) {
436
+ if (this.closed || this.failure !== undefined || this.step !== step)
437
+ throw new RepairGateError(`repair phase prerequisite unavailable: expected ${step}`, "blocked");
438
+ }
439
+ assertIdentity(digest) { if (digest !== this.frozenDigest)
440
+ throw new RepairGateError("proposal frozen identity mismatch"); }
441
+ sourceText() { return new Map(this.files.map((file) => [file.path, text(this.replacements.get(file.path) ?? file.bytes)])); }
442
+ reviewProofs() { return this.proofs.filter((proof) => proof.phase === "fixed_regression" || proof.phase === "fixed_required").map(proofRef); }
443
+ packet(phase, replacements = this.replacements) {
444
+ const packet = RepairPacketSchema.parse({ schema_version: "clinkx-verified-repair-packet-v1", frozen_digest: this.frozenDigest,
445
+ phase, source_facts: facts(this.files, replacements), proofs: this.proofs,
446
+ context: json({ defect: this.input.defect, observation: this.input.observation, runtime: this.runtime,
447
+ regression_paths: this.input.regression_paths, implementation_paths: this.input.implementation_paths,
448
+ sources: this.files.map((file) => ({ path: file.path, content: text(replacements.get(file.path) ?? file.bytes),
449
+ original_sha256: file.sha256, original_content: text(file.bytes) })),
450
+ ...(this.investigation === undefined ? {} : { diagnosis: InvestigationSchema.parse(this.investigation).diagnosis }),
451
+ ...(this.fix === undefined ? {} : { fix_explanation: FixProposalSchema.parse(this.fix).explanation }),
452
+ fixed_source_digest: repairSourceDigest(facts(this.files, replacements)), review_proof_refs: this.reviewProofs() }),
453
+ });
454
+ assertRepairBounded(packet, MAX_REPAIR_PACKET_BYTES);
455
+ return packet;
456
+ }
457
+ apply(batch, phase, allowed, signal) {
458
+ assertCurrent(this.input.project_path, this.expected, this.modes);
459
+ const validated = batch.map((replacement) => {
460
+ if (!allowed.includes(replacement.path) || replacement.original_sha256 !== this.expected.get(replacement.path))
461
+ throw new RepairGateError("replacement path or original hash is not admitted");
462
+ const file = this.files.find((file) => file.path === replacement.path);
463
+ const bytes = Buffer.from(replacement.replacement_text, "utf8");
464
+ if (hasCredentialTokenShape(replacement.replacement_text) || hasDisallowedSecretMaterial(replacement.replacement_text, false))
465
+ throw new RepairGateError("replacement contains credential-like material; no batch writes were admitted");
466
+ if (text(bytes) !== replacement.replacement_text || sha(bytes) === replacement.original_sha256)
467
+ throw new RepairGateError("replacement must change exact regular UTF-8 bytes");
468
+ return { replacement, file, bytes };
469
+ });
470
+ const proposed = new Map(this.replacements);
471
+ for (const item of validated)
472
+ proposed.set(item.replacement.path, item.bytes);
473
+ this.packet(phase === "regression" ? "reproduce" : "fixed", proposed);
474
+ // Retain the entire admitted batch before the first rename for crash reconciliation.
475
+ const proposalBytes = json(batch);
476
+ writeFileSync(join(this.evidence, `${phase}-proposal.json`), proposalBytes, { mode: 0o600, flag: "wx" });
477
+ writeFileSync(join(this.options.runDir, `verified-repair-${phase}.v1.json`), json({ schema_version: "clinkx-verified-repair-batch-v1",
478
+ run_id: this.options.runId, phase, frozen_digest: this.frozenDigest, proposal_sha256: sha(proposalBytes) }), { mode: 0o600, flag: "wx" });
479
+ for (const { replacement, bytes } of validated)
480
+ this.applied.push({ phase, path: replacement.path,
481
+ original_sha256: replacement.original_sha256, replacement_sha256: sha(bytes), status: "original" });
482
+ for (const { replacement, file, bytes } of validated) {
483
+ assertCurrent(this.input.project_path, this.expected, this.modes);
484
+ writeReplacement(this.input.project_path, replacement.path, bytes, file.mode, replacement.original_sha256, signal, this.rename);
485
+ this.replacements.set(replacement.path, bytes);
486
+ this.expected.set(replacement.path, sha(bytes));
487
+ const entry = this.applied.find((entry) => entry.path === replacement.path);
488
+ entry.status = "applied";
489
+ // An interrupted receipt never authorizes replay. Current hashes classify the edit.
490
+ writeFileSync(join(this.evidence, `write-${this.applied.indexOf(entry)}.json`), json(entry), { mode: 0o600, flag: "wx" });
491
+ assertCurrent(this.input.project_path, this.expected, this.modes);
492
+ }
493
+ }
494
+ checkCopy(withoutFix = false) {
495
+ if (this.copy === undefined)
496
+ throw new Error("owned verification copy is unavailable");
497
+ const expected = new Map(this.expected);
498
+ if (withoutFix)
499
+ for (const path of this.input.implementation_paths)
500
+ expected.set(path, this.files.find((file) => file.path === path).sha256);
501
+ assertCurrent(this.copy, expected, this.modes);
502
+ }
503
+ async run(native, phase, project, manifest, outcome, withoutFix = false) {
504
+ this.activePhase = phase;
505
+ this.assertRuntime();
506
+ assertCurrent(this.input.project_path, this.expected, this.modes);
507
+ if (project !== this.input.project_path)
508
+ this.checkCopy(withoutFix);
509
+ const evidence = join(this.evidence, `attempt-${phase}`);
510
+ const options = { project, manifest, evidence };
511
+ const result = await native.run(options);
512
+ // The original pin comes from the runner return, never a later evidence read.
513
+ const pin = result.expectations_sha256;
514
+ const expected = ExpectedSchema.parse(JSON.parse(readBounded(evidence, "expected.json", 2 * 1024 * 1024).toString("utf8")));
515
+ const proof = { phase, evidence_directory: evidence, expectations_sha256: pin,
516
+ project_path: project, source_digest: expected.expectations.identity.source_sha256,
517
+ coverage_status: result.status, applicable: result.status !== "inapplicable" };
518
+ this.proofs.push(proof);
519
+ const integrity = new Map();
520
+ for (const path of ["expected.json", "observations.json", "completion.json", "proof.json",
521
+ ...expected.manifest.checks.flatMap((check) => [`checks/${check.id}/result.json`, `checks/${check.id}/stdout.log`, `checks/${check.id}/stderr.log`])]) {
522
+ integrity.set(path, sha(readBounded(evidence, path, path.endsWith(".log") ? 1024 * 1024 : 2 * 1024 * 1024)));
523
+ }
524
+ this.integrity.set(evidence, integrity);
525
+ assertCurrent(this.input.project_path, this.expected, this.modes);
526
+ if (project !== this.input.project_path)
527
+ this.checkCopy(withoutFix);
528
+ const inspected = native.inspect(options, pin);
529
+ proof.coverage_status = inspected.status;
530
+ proof.applicable = !inspected.reasons.some((reason) => ["inputs_changed_during_run", "current_identity_mismatch", "current_identity_unavailable"].includes(reason.code));
531
+ if (outcome !== undefined) {
532
+ const id = this.input.observation.check_id;
533
+ const command = ResultSchema.parse(JSON.parse(readBounded(evidence, `checks/${id}/result.json`, 16 * 1024).toString("utf8")));
534
+ let observation;
535
+ try {
536
+ observation = parseRegressionOutcome(readBounded(evidence, `checks/${id}/stdout.log`, 1024 * 1024).toString("utf8"), this.input.observation);
537
+ }
538
+ catch {
539
+ throw new RepairGateError("regression outcome is invalid or does not bind the reviewed case and assertion; raw captures remain private");
540
+ }
541
+ proof.outcome = observation;
542
+ if (observation.outcome !== outcome || command.signal !== null || !command.cleanup_ok
543
+ || (outcome === "assertion_failed" ? command.reason !== "nonzero_exit" || command.exit_code === null || command.exit_code === 0
544
+ : command.reason !== "passed" || command.exit_code !== 0))
545
+ throw new RepairGateError("exact regression assertion was not observed with a clean harness");
546
+ if (outcome === "assertion_failed") {
547
+ if (inspected.status !== "failed" || inspected.reasons.some((reason) => !["failed_check", "command_failed"].includes(reason.code)))
548
+ throw new RepairGateError("failed regression proof has invalid or inapplicable evidence");
549
+ }
550
+ else if (inspected.status !== "complete")
551
+ throw new RepairGateError("fixed regression native coverage is not complete");
552
+ }
553
+ else if (inspected.status !== "complete")
554
+ throw new RepairGateError("required native coverage is not complete");
555
+ }
556
+ inspectProof(native, proof) {
557
+ const digests = this.integrity.get(proof.evidence_directory);
558
+ if (digests === undefined)
559
+ throw new RepairGateError("original phase capture authority unavailable");
560
+ for (const [path, digest] of digests)
561
+ if (sha(readBounded(proof.evidence_directory, path, path.endsWith(".log") ? 1024 * 1024 : 2 * 1024 * 1024)) !== digest)
562
+ throw new RepairGateError("phase capture changed since the host retained it");
563
+ const manifest = proof.phase === "fixed_regression" ? this.input.regression_manifest : this.input.required_manifest;
564
+ const inspected = native.inspect({ project: this.input.project_path, manifest, evidence: proof.evidence_directory }, proof.expectations_sha256);
565
+ proof.coverage_status = inspected.status;
566
+ proof.applicable = inspected.status === "complete";
567
+ if (inspected.status !== "complete")
568
+ throw new RepairGateError("fixed proof no longer qualifies with its original pin");
569
+ }
570
+ inspectFixed(native) {
571
+ this.assertRuntime();
572
+ assertCurrent(this.input.project_path, this.expected, this.modes);
573
+ for (const phase of ["fixed_regression", "fixed_required"]) {
574
+ const proof = this.proofs.find((proof) => proof.phase === phase);
575
+ if (proof === undefined)
576
+ throw new RepairGateError("fixed phase proof missing", "blocked");
577
+ this.inspectProof(native, proof);
578
+ }
579
+ }
580
+ async phase(phase, signal, operation) {
581
+ this.activePhase = phase;
582
+ try {
583
+ return await this.service.operation(operation, signal);
584
+ }
585
+ catch (error) {
586
+ this.fail(error);
587
+ throw error;
588
+ }
589
+ }
590
+ fail(error) {
591
+ if (this.failure === undefined)
592
+ this.failure = error instanceof RepairGateError ? error.status : "failed";
593
+ const message = error instanceof Error ? error.message : "repair host operation failed";
594
+ if (this.gaps.length < 64 && !this.gaps.includes(message))
595
+ this.gaps.push(message.slice(0, 6000) || "repair host operation failed");
596
+ }
597
+ assertRuntime() {
598
+ if (process.version !== this.runtime.node_version || runtimeDigest() !== this.runtime.sha256)
599
+ throw new RepairGateError("repair runtime identity changed since capture");
600
+ }
601
+ removeCopy() {
602
+ if (this.copy !== undefined) {
603
+ const removed = this.copy;
604
+ rmSync(removed, { recursive: true, force: true });
605
+ this.copy = undefined;
606
+ for (const proof of this.proofs)
607
+ if (proof.project_path === removed)
608
+ proof.applicable = false;
609
+ }
610
+ }
611
+ }
612
+ //# sourceMappingURL=host.js.map