@abelxiaoxing/cadence 1.8.4 → 2.0.1

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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,213 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import {
3
- chmodSync,
4
- closeSync,
5
- constants,
6
- copyFileSync,
7
- fsyncSync,
8
- lstatSync,
9
- mkdirSync,
10
- openSync,
11
- readFileSync,
12
- renameSync,
13
- unlinkSync,
14
- writeFileSync,
15
- } from "node:fs";
16
- import path from "node:path";
17
- import { syncDirectory } from "./directory-sync.ts";
18
-
19
- const SHA256 = /^[a-f0-9]{64}$/u;
20
-
21
- export interface ArtifactIdentity {
22
- hash: string;
23
- bytes: number;
24
- }
25
-
26
- function digest(bytes: Uint8Array): string {
27
- return createHash("sha256").update(bytes).digest("hex");
28
- }
29
-
30
- function requireHash(hash: string): void {
31
- if (!SHA256.test(hash)) throw new Error("artifact-hash-invalid");
32
- }
33
-
34
- function ensureDirectory(directory: string): void {
35
- const stat = lstatSync(directory, { throwIfNoEntry: false });
36
- if (stat) {
37
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
38
- throw new Error("artifact-store-path-unsafe");
39
- }
40
- chmodSync(directory, 0o700);
41
- return;
42
- }
43
- let cursor = path.dirname(directory);
44
- while (cursor !== path.dirname(cursor)) {
45
- const ancestor = lstatSync(cursor, { throwIfNoEntry: false });
46
- if (ancestor) {
47
- if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
48
- throw new Error("artifact-store-path-unsafe");
49
- }
50
- break;
51
- }
52
- cursor = path.dirname(cursor);
53
- }
54
- mkdirSync(directory, { recursive: true, mode: 0o700 });
55
- const created = lstatSync(directory);
56
- if (created.isSymbolicLink() || !created.isDirectory()) {
57
- throw new Error("artifact-store-path-unsafe");
58
- }
59
- chmodSync(directory, 0o700);
60
- }
61
-
62
- function writeAtomic(target: string, bytes: Uint8Array): void {
63
- const directory = path.dirname(target);
64
- ensureDirectory(directory);
65
- const temporary = path.join(
66
- directory,
67
- `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`,
68
- );
69
- const descriptor = openSync(
70
- temporary,
71
- constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
72
- 0o600,
73
- );
74
- try {
75
- writeFileSync(descriptor, bytes);
76
- fsyncSync(descriptor);
77
- } finally {
78
- closeSync(descriptor);
79
- }
80
- try {
81
- renameSync(temporary, target);
82
- chmodSync(target, 0o600);
83
- syncDirectory(directory);
84
- } catch (error) {
85
- try {
86
- unlinkSync(temporary);
87
- } catch {
88
- // The rename may already have consumed the temporary file.
89
- }
90
- throw error;
91
- }
92
- }
93
-
94
- /** Durable content-addressed bytes used by workspace revisions and sealed results. */
95
- export class ArtifactStore {
96
- readonly root: string;
97
- readonly #blobs: string;
98
- readonly #references: string;
99
-
100
- constructor(root: string) {
101
- if (!path.isAbsolute(root)) throw new Error("artifact-store-root-invalid");
102
- this.root = path.resolve(root);
103
- this.#blobs = path.join(this.root, "blobs");
104
- this.#references = path.join(this.root, "references");
105
- ensureDirectory(this.root);
106
- ensureDirectory(this.#blobs);
107
- ensureDirectory(this.#references);
108
- }
109
-
110
- #blobPath(hash: string): string {
111
- requireHash(hash);
112
- return path.join(this.#blobs, hash.slice(0, 2), hash);
113
- }
114
-
115
- #referencePath(hash: string): string {
116
- requireHash(hash);
117
- return path.join(this.#references, hash.slice(0, 2), `${hash}.ref`);
118
- }
119
-
120
- put(input: Uint8Array): ArtifactIdentity {
121
- if (!(input instanceof Uint8Array))
122
- throw new Error("artifact-bytes-invalid");
123
- const bytes = Buffer.from(input);
124
- const hash = digest(bytes);
125
- const target = this.#blobPath(hash);
126
- const existing = lstatSync(target, { throwIfNoEntry: false });
127
- if (existing) {
128
- if (existing.isSymbolicLink() || !existing.isFile()) {
129
- throw new Error("artifact-store-path-unsafe");
130
- }
131
- const current = readFileSync(target);
132
- if (current.length !== bytes.length || digest(current) !== hash) {
133
- throw new Error("artifact-integrity-invalid");
134
- }
135
- } else {
136
- writeAtomic(target, bytes);
137
- const stored = readFileSync(target);
138
- if (stored.length !== bytes.length || digest(stored) !== hash) {
139
- throw new Error("artifact-integrity-invalid");
140
- }
141
- }
142
- return Object.freeze({ hash, bytes: bytes.length });
143
- }
144
-
145
- read(hash: string): Uint8Array {
146
- const target = this.#blobPath(hash);
147
- const stat = lstatSync(target, { throwIfNoEntry: false });
148
- if (!stat?.isFile() || stat.isSymbolicLink()) {
149
- throw new Error("artifact-unavailable");
150
- }
151
- const bytes = readFileSync(target);
152
- if (digest(bytes) !== hash) throw new Error("artifact-integrity-invalid");
153
- return Buffer.from(bytes);
154
- }
155
-
156
- /** Clone when supported; always use a distinct inode and verify the copied bytes. */
157
- copyVerified(hash: string, destination: string): number {
158
- const source = this.#blobPath(hash);
159
- const stat = lstatSync(source, { throwIfNoEntry: false });
160
- if (!stat?.isFile() || stat.isSymbolicLink())
161
- throw new Error("artifact-unavailable");
162
- copyFileSync(
163
- source,
164
- destination,
165
- constants.COPYFILE_EXCL | constants.COPYFILE_FICLONE,
166
- );
167
- const bytes = readFileSync(destination);
168
- if (digest(bytes) !== hash) throw new Error("artifact-integrity-invalid");
169
- return bytes.byteLength;
170
- }
171
-
172
- referenceCount(hash: string): number {
173
- requireHash(hash);
174
- const referencePath = this.#referencePath(hash);
175
- const stat = lstatSync(referencePath, { throwIfNoEntry: false });
176
- if (!stat) return 0;
177
- if (!stat.isFile() || stat.isSymbolicLink()) {
178
- throw new Error("artifact-reference-invalid");
179
- }
180
- const raw = readFileSync(referencePath, "utf8");
181
- if (!/^(?:0|[1-9][0-9]*)\n$/u.test(raw)) {
182
- throw new Error("artifact-reference-invalid");
183
- }
184
- const count = Number(raw.trim());
185
- if (!Number.isSafeInteger(count))
186
- throw new Error("artifact-reference-invalid");
187
- return count;
188
- }
189
-
190
- #setReferenceCount(hash: string, count: number): number {
191
- if (!Number.isSafeInteger(count) || count < 0) {
192
- throw new Error("artifact-reference-invalid");
193
- }
194
- writeAtomic(this.#referencePath(hash), Buffer.from(`${count}\n`));
195
- return count;
196
- }
197
-
198
- retain(hash: string): number {
199
- this.read(hash);
200
- const current = this.referenceCount(hash);
201
- if (current === Number.MAX_SAFE_INTEGER) {
202
- throw new Error("artifact-reference-overflow");
203
- }
204
- return this.#setReferenceCount(hash, current + 1);
205
- }
206
-
207
- release(hash: string): number {
208
- this.read(hash);
209
- const current = this.referenceCount(hash);
210
- if (current === 0) throw new Error("artifact-reference-underflow");
211
- return this.#setReferenceCount(hash, current - 1);
212
- }
213
- }
@@ -1,95 +0,0 @@
1
- // Explicit operator/release build only. Never invoked on import, install or verification.
2
- import { spawnSync } from "node:child_process";
3
- import { createHash } from "node:crypto";
4
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
- import path from "node:path";
6
- import { fileURLToPath } from "node:url";
7
-
8
- function main() {
9
- const args = process.argv.slice(2);
10
- if (
11
- process.platform !== "win32" ||
12
- process.arch !== "x64" ||
13
- args.length !== 2 ||
14
- args[0] !== "--output"
15
- )
16
- throw new Error(
17
- "Windows x64 required; usage: node build-windows-job.mjs --output <directory>",
18
- );
19
- const output = path.resolve(args[1]);
20
- const compiler = path.join(
21
- process.env.VCToolsInstallDir ?? "",
22
- "bin/Hostx64/x64/cl.exe",
23
- );
24
- if (!path.isAbsolute(compiler))
25
- throw new Error(
26
- "Select the installed MSVC x64 developer environment first",
27
- );
28
- const sdkVersion = process.env.WindowsSDKVersion?.replace(/[\\/]+$/u, "");
29
- if (!sdkVersion || !/^10\.0\.\d+\.0$/u.test(sdkVersion))
30
- throw new Error("Windows 10 SDK required");
31
- const version = spawnSync(compiler, [], {
32
- shell: false,
33
- encoding: "utf8",
34
- timeout: 10000,
35
- maxBuffer: 16384,
36
- });
37
- const compilerVersion =
38
- `${version.stdout ?? ""}${version.stderr ?? ""}`.match(
39
- /19\.(?:3\d|4\d)\.\d+/u,
40
- )?.[0];
41
- if (!compilerVersion) throw new Error("MSVC 19.3x/19.4x required");
42
- mkdirSync(output, { recursive: true });
43
- const source = fileURLToPath(new URL("./windows-job.c", import.meta.url));
44
- const helper = path.join(output, "windows-job.exe");
45
- rmSync(`${helper}.json`, { force: true });
46
- rmSync(helper, { force: true });
47
- const result = spawnSync(
48
- compiler,
49
- [
50
- "/nologo",
51
- "/W4",
52
- "/WX",
53
- "/O2",
54
- "/MT",
55
- "/utf-8",
56
- "/DUNICODE",
57
- "/D_UNICODE",
58
- source,
59
- `/Fe:${helper}`,
60
- `/Fo:${path.join(output, "windows-job.obj")}`,
61
- ],
62
- {
63
- shell: false,
64
- cwd: output,
65
- encoding: "utf8",
66
- timeout: 60000,
67
- maxBuffer: 65536,
68
- },
69
- );
70
- writeFileSync(
71
- path.join(output, "build.log"),
72
- `${result.stdout ?? ""}${result.stderr ?? ""}`,
73
- );
74
- if (result.error || result.status !== 0)
75
- throw new Error("Native build failed; inspect build.log");
76
- const hash = (file) =>
77
- createHash("sha256").update(readFileSync(file)).digest("hex");
78
- writeFileSync(
79
- `${helper}.json`,
80
- `${JSON.stringify({ version: 2, arch: "x64", sourceSha256: hash(source), helperSha256: hash(helper), compilerSha256: hash(compiler), compilerVersion, sdkVersion }, null, 2)}\n`,
81
- { flag: "wx" },
82
- );
83
- console.log(`Built ${helper}`);
84
- }
85
- if (
86
- process.argv[1] &&
87
- path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
88
- ) {
89
- try {
90
- main();
91
- } catch (error) {
92
- console.error(error.message);
93
- process.exitCode = 1;
94
- }
95
- }
@@ -1,143 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { diffWritePaths } from "./contracts.ts";
3
- import {
4
- type BeginCandidateInput,
5
- TASK_LEDGER_LIMITS,
6
- type TaskLedger,
7
- } from "./task-ledger.ts";
8
- import type { DurableCandidateProposal } from "./workflow-policy.ts";
9
-
10
- type ArtifactProposal = Extract<
11
- DurableCandidateProposal,
12
- { kind: "candidate" | "sealed-candidate" }
13
- >;
14
- interface CandidateArtifactInput {
15
- ledger: TaskLedger;
16
- identity: BeginCandidateInput;
17
- proposal: ArtifactProposal;
18
- }
19
-
20
- const sha256 = (bytes: Uint8Array) =>
21
- createHash("sha256").update(bytes).digest("hex");
22
-
23
- /** Inspect the same sealed bytes for normal phases and repairs. Callers own recovery policy. */
24
- export function inspectCandidateArtifact({
25
- ledger,
26
- identity,
27
- proposal,
28
- approvedPaths,
29
- }: CandidateArtifactInput & { approvedPaths: readonly string[] }):
30
- | { ok: true; bytes: Buffer; paths: string[] }
31
- | {
32
- ok: false;
33
- code: "candidate-diff-invalid" | "write-set-mismatch";
34
- stage: string;
35
- } {
36
- const invalid = (stage: string) => ({
37
- ok: false as const,
38
- code: "candidate-diff-invalid" as const,
39
- stage,
40
- });
41
- ledger.beginCandidate(identity);
42
- let bytes: Buffer;
43
- if (proposal.kind === "sealed-candidate") {
44
- if (
45
- proposal.candidateId !== identity.candidateId ||
46
- !/^[a-f0-9]{64}$/u.test(proposal.artifactHash) ||
47
- !Number.isSafeInteger(proposal.bytes) ||
48
- proposal.bytes < 1 ||
49
- !Array.isArray(proposal.paths)
50
- )
51
- return invalid("candidate-seal");
52
- bytes = Buffer.from(ledger.readSealedCandidate(identity.candidateId));
53
- if (
54
- bytes.length !== proposal.bytes ||
55
- sha256(bytes) !== proposal.artifactHash
56
- ) {
57
- throw new Error("candidate-seal-integrity-invalid");
58
- }
59
- } else {
60
- if (
61
- !(proposal.bytes instanceof Uint8Array) ||
62
- proposal.bytes.length === 0
63
- ) {
64
- return invalid("candidate-submit");
65
- }
66
- bytes = Buffer.from(proposal.bytes);
67
- }
68
- let paths: string[];
69
- try {
70
- paths = diffWritePaths(
71
- new TextDecoder("utf-8", { fatal: true }).decode(bytes),
72
- ).paths;
73
- } catch {
74
- return invalid("candidate-diff");
75
- }
76
- if (paths.some((relative) => !approvedPaths.includes(relative))) {
77
- return {
78
- ok: false,
79
- code: "write-set-mismatch",
80
- stage: "candidate-boundary",
81
- };
82
- }
83
- if (
84
- proposal.kind === "sealed-candidate" &&
85
- JSON.stringify([...proposal.paths].sort()) !==
86
- JSON.stringify([...paths].sort())
87
- ) {
88
- return invalid("candidate-seal");
89
- }
90
- return { ok: true, bytes, paths };
91
- }
92
-
93
- /** Seal after isolated patch/dependency checks, before any phase verification. */
94
- export function sealCandidateArtifact({
95
- ledger,
96
- identity,
97
- proposal,
98
- bytes,
99
- }: CandidateArtifactInput & { bytes: Buffer }):
100
- | { ok: true; artifactHash: string }
101
- | { ok: false; kind: "paused" | "retryable"; code: string; stage: string } {
102
- if (proposal.kind === "sealed-candidate")
103
- return { ok: true, artifactHash: proposal.artifactHash };
104
- let sequence = 0;
105
- for (
106
- let offset = 0;
107
- offset < bytes.length;
108
- offset += TASK_LEDGER_LIMITS.maxSegmentBytes
109
- ) {
110
- const segment = bytes.subarray(
111
- offset,
112
- Math.min(offset + TASK_LEDGER_LIMITS.maxSegmentBytes, bytes.length),
113
- );
114
- const accepted = ledger.appendCandidateSegment({
115
- ...identity,
116
- sequence,
117
- bytes: segment,
118
- segmentHash: sha256(segment),
119
- });
120
- if (!accepted.ok)
121
- return {
122
- ok: false,
123
- kind: "paused",
124
- code: accepted.code,
125
- stage: "candidate-segment",
126
- };
127
- sequence++;
128
- }
129
- const sealed = ledger.sealCandidate({
130
- ...identity,
131
- segmentCount: sequence,
132
- totalBytes: bytes.length,
133
- candidateHash: sha256(bytes),
134
- });
135
- return sealed.ok
136
- ? { ok: true, artifactHash: sealed.artifactHash }
137
- : {
138
- ok: false,
139
- kind: "retryable",
140
- code: sealed.code,
141
- stage: "candidate-seal",
142
- };
143
- }