owlrunkit 0.15.0 → 0.16.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 (76) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +274 -66
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
  4. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
  6. package/package.json +2 -2
  7. package/packages/attest/distribution-manifest-v1.json +10 -0
  8. package/packages/attest/src/quick.mjs +10 -0
  9. package/registry-release-policy-v1.json +8 -2
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/assurance-router.mjs +119 -0
  12. package/scripts/runkit-contract/core-contract.mjs +250 -115
  13. package/scripts/runkit-contract/core-successor.mjs +2043 -0
  14. package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
  15. package/scripts/runkit-contract/finalize.mjs +242 -21
  16. package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
  17. package/scripts/runkit-contract/formal-workflow.mjs +850 -0
  18. package/scripts/runkit-contract/human-status.mjs +553 -0
  19. package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
  20. package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
  21. package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
  22. package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
  23. package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
  24. package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
  25. package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
  26. package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
  27. package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
  28. package/scripts/runkit-contract/project-control-parser.mjs +830 -0
  29. package/scripts/runkit-contract/project-control-state.mjs +28 -0
  30. package/scripts/runkit-contract/provenance-common.mjs +19 -2
  31. package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
  32. package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
  33. package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
  36. package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
  37. package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
  38. package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
  39. package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
  40. package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
  41. package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
  42. package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
  43. package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
  44. package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
  45. package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
  46. package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
  47. package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
  48. package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
  49. package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
  50. package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
  51. package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
  52. package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
  53. package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
  54. package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
  55. package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
  56. package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
  57. package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
  58. package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
  59. package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
  60. package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
  61. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
  62. package/scripts/runkit-contract/snapshot.mjs +1 -1
  63. package/scripts/runkit-contract/source-candidate.mjs +1382 -0
  64. package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
  65. package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
  66. package/scripts/runkit-contract/templates/assurance-request.json +13 -0
  67. package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
  68. package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
  69. package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
  70. package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
  71. package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
  72. package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
  73. package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
  74. package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
  75. package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
  76. package/scripts/runkit-contract/verification-receipt-gate.mjs +28 -0
@@ -0,0 +1,2043 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import {
4
+ lstat,
5
+ mkdtemp,
6
+ mkdir,
7
+ readdir,
8
+ readFile,
9
+ realpath,
10
+ rm,
11
+ writeFile,
12
+ } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import path from "node:path";
15
+
16
+ import {
17
+ coreIdentityFromSourceRoot,
18
+ currentCoreIdentity,
19
+ initializeProjectRunKit,
20
+ validateExecutionPin,
21
+ validateProjectConfigV2,
22
+ } from "./core-contract.mjs";
23
+ import { discoverFleet } from "./fleet-discovery.mjs";
24
+ import { withControlTransaction } from "./lease-lifecycle.mjs";
25
+ import { inspectProjectControlState } from "./project-control-state.mjs";
26
+ import {
27
+ freezeSourceCandidateV2,
28
+ materializeSourceCandidateV2,
29
+ verifySourceCandidatePathClosureV2,
30
+ verifySourceCandidateV2,
31
+ } from "./source-candidate.mjs";
32
+ import {
33
+ ownerAuthorityArtifactSha256V1,
34
+ verifyTrustedOwnerAuthorityV1,
35
+ } from "./owner-authority-trust.mjs";
36
+
37
+ const SHA256_REF = /^sha256:[a-f0-9]{64}$/u;
38
+ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
39
+ const MAX_ARTIFACT_BYTES = 1_048_576;
40
+
41
+ const REPOSITORY_ACTIONS = Object.freeze({
42
+ stage: false,
43
+ commit: false,
44
+ push: false,
45
+ publish: false,
46
+ deploy: false,
47
+ });
48
+
49
+ function canonicalJson(value) {
50
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
51
+ if (value && typeof value === "object") {
52
+ return `{${Object.keys(value)
53
+ .sort()
54
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
55
+ .join(",")}}`;
56
+ }
57
+ const encoded = JSON.stringify(value);
58
+ if (encoded === undefined) {
59
+ throw new Error("core_successor_noncanonical_value");
60
+ }
61
+ return encoded;
62
+ }
63
+
64
+ export function coreSuccessorArtifactSha256V1(value) {
65
+ return `sha256:${createHash("sha256")
66
+ .update(canonicalJson(value))
67
+ .digest("hex")}`;
68
+ }
69
+
70
+ function cloneJson(value, label) {
71
+ try {
72
+ return JSON.parse(JSON.stringify(value));
73
+ } catch {
74
+ throw new Error(`core_successor_${label}_not_json`);
75
+ }
76
+ }
77
+
78
+ function assertIdentifier(value, label) {
79
+ if (typeof value !== "string" || !IDENTIFIER.test(value)) {
80
+ throw new Error(`core_successor_${label}_invalid`);
81
+ }
82
+ }
83
+
84
+ async function canonicalWorkspaceRoot(workspaceRoot) {
85
+ if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
86
+ throw new Error("core_successor_controller_workspace_required");
87
+ }
88
+ const requested = path.resolve(workspaceRoot);
89
+ const stat = await lstat(requested);
90
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
91
+ throw new Error("core_successor_controller_workspace_invalid");
92
+ }
93
+ return realpath(requested);
94
+ }
95
+
96
+ function safeRelativePath(value, label) {
97
+ if (
98
+ typeof value !== "string"
99
+ || value.length === 0
100
+ || path.isAbsolute(value)
101
+ || value.includes("\\")
102
+ || value.split("/").some(segment => (
103
+ segment.length === 0 || segment === "." || segment === ".."
104
+ ))
105
+ ) {
106
+ throw new Error(`core_successor_${label}_invalid`);
107
+ }
108
+ return value;
109
+ }
110
+
111
+ function within(root, candidate) {
112
+ const relative = path.relative(root, candidate);
113
+ return relative === ""
114
+ || (
115
+ relative !== ".."
116
+ && !relative.startsWith(`..${path.sep}`)
117
+ && !path.isAbsolute(relative)
118
+ );
119
+ }
120
+
121
+ async function prepareCreateOnlyPath(root, relativePath, existsCode) {
122
+ const safePath = safeRelativePath(relativePath, "artifact_path");
123
+ const absolutePath = path.resolve(root, safePath);
124
+ if (!within(root, absolutePath)) {
125
+ throw new Error("core_successor_artifact_path_escapes_workspace");
126
+ }
127
+ const parent = path.dirname(absolutePath);
128
+ await mkdir(parent, { recursive: true });
129
+ if (await realpath(parent) !== path.resolve(parent)) {
130
+ throw new Error("core_successor_artifact_parent_symlink_rejected");
131
+ }
132
+ try {
133
+ await lstat(absolutePath);
134
+ throw new Error(existsCode);
135
+ } catch (error) {
136
+ if (error?.code !== "ENOENT") throw error;
137
+ }
138
+ return { absolutePath, relativePath: safePath };
139
+ }
140
+
141
+ async function readBoundedJson(root, relativePath, label) {
142
+ const safePath = safeRelativePath(relativePath, label);
143
+ const absolutePath = path.resolve(root, safePath);
144
+ if (!within(root, absolutePath)) {
145
+ throw new Error(`core_successor_${label}_escapes_workspace`);
146
+ }
147
+ const stat = await lstat(absolutePath);
148
+ if (
149
+ stat.isSymbolicLink()
150
+ || !stat.isFile()
151
+ || stat.size > MAX_ARTIFACT_BYTES
152
+ || await realpath(absolutePath) !== path.resolve(absolutePath)
153
+ ) {
154
+ throw new Error(`core_successor_${label}_invalid`);
155
+ }
156
+ try {
157
+ return JSON.parse(await readFile(absolutePath, "utf8"));
158
+ } catch {
159
+ throw new Error(`core_successor_${label}_invalid_json`);
160
+ }
161
+ }
162
+
163
+ async function writeCreateOnlyJson(root, relativePath, value, existsCode) {
164
+ const output = await prepareCreateOnlyPath(root, relativePath, existsCode);
165
+ await writeFile(
166
+ output.absolutePath,
167
+ `${JSON.stringify(value, null, 2)}\n`,
168
+ { flag: "wx" },
169
+ );
170
+ return output.relativePath;
171
+ }
172
+
173
+ function assertCoreIdentity(core, label) {
174
+ const gate = validateProjectConfigV2({
175
+ schemaVersion: "OwlCodaRunKitConfigV2",
176
+ core,
177
+ authorizationPolicy: "external_explicit_authority_required",
178
+ });
179
+ if (!gate.valid) throw new Error(`core_successor_${label}_invalid`);
180
+ }
181
+
182
+ function sameCore(left, right) {
183
+ return validateExecutionPin({ expected: left, actual: right }).status === "valid";
184
+ }
185
+
186
+ function sha256Bytes(bytes) {
187
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
188
+ }
189
+
190
+ async function readWorkspaceArtifact(workspaceRoot, relativePath, label) {
191
+ const root = await canonicalWorkspaceRoot(workspaceRoot);
192
+ const safePath = safeRelativePath(relativePath, label);
193
+ const absolutePath = path.resolve(root, safePath);
194
+ if (!within(root, absolutePath)) {
195
+ throw new Error(`core_successor_${label}_escapes_workspace`);
196
+ }
197
+ const stat = await lstat(absolutePath);
198
+ if (
199
+ stat.isSymbolicLink()
200
+ || !stat.isFile()
201
+ || stat.size > MAX_ARTIFACT_BYTES
202
+ || await realpath(absolutePath) !== path.resolve(absolutePath)
203
+ ) {
204
+ throw new Error(`core_successor_${label}_invalid`);
205
+ }
206
+ const bytes = await readFile(absolutePath);
207
+ let value;
208
+ try {
209
+ value = JSON.parse(bytes.toString("utf8"));
210
+ } catch {
211
+ throw new Error(`core_successor_${label}_invalid_json`);
212
+ }
213
+ return {
214
+ relativePath: safePath,
215
+ bytes,
216
+ sha256: sha256Bytes(bytes),
217
+ value,
218
+ };
219
+ }
220
+
221
+ function receiptAttemptPath(basePath, attemptNumber) {
222
+ const extension = path.extname(basePath);
223
+ const stem = extension.length > 0
224
+ ? basePath.slice(0, -extension.length)
225
+ : basePath;
226
+ return attemptNumber === 1
227
+ ? basePath
228
+ : `${stem}-attempt-${String(attemptNumber).padStart(3, "0")}${extension}`;
229
+ }
230
+
231
+ function projectSuccessReceiptPath(basePath, receiptId, workspaceRoot) {
232
+ const extension = path.extname(basePath);
233
+ const stem = path.basename(
234
+ extension.length > 0
235
+ ? basePath.slice(0, -extension.length)
236
+ : basePath,
237
+ );
238
+ const workspaceSha256 = createHash("sha256")
239
+ .update(workspaceRoot)
240
+ .digest("hex");
241
+ return path.join(
242
+ path.dirname(basePath),
243
+ `${stem}-project-success`,
244
+ `${receiptId}-${workspaceSha256}.json`,
245
+ );
246
+ }
247
+
248
+ async function pathExists(absolutePath) {
249
+ try {
250
+ await lstat(absolutePath);
251
+ return true;
252
+ } catch (error) {
253
+ if (error?.code === "ENOENT") return false;
254
+ throw error;
255
+ }
256
+ }
257
+
258
+ function coreBindingSetSha256(bindings) {
259
+ return coreSuccessorArtifactSha256V1(bindings);
260
+ }
261
+
262
+ function normalizedFleetCoreBindings(fleetDiscovery, bindings) {
263
+ if (
264
+ !Array.isArray(bindings)
265
+ || bindings.length !== fleetDiscovery.workspaceRoots.length
266
+ ) {
267
+ throw new Error("core_successor_fleet_core_bindings_invalid");
268
+ }
269
+ const byWorkspace = new Map();
270
+ for (const binding of bindings) {
271
+ if (
272
+ !binding
273
+ || typeof binding.workspaceRoot !== "string"
274
+ || byWorkspace.has(binding.workspaceRoot)
275
+ ) {
276
+ throw new Error("core_successor_fleet_core_bindings_invalid");
277
+ }
278
+ assertCoreIdentity(binding.fromCore, "fleet_from_core");
279
+ byWorkspace.set(binding.workspaceRoot, {
280
+ workspaceRoot: binding.workspaceRoot,
281
+ fromCore: cloneJson(binding.fromCore, "fleet_from_core"),
282
+ });
283
+ }
284
+ const ordered = fleetDiscovery.workspaceRoots.map((workspaceRoot) => {
285
+ const binding = byWorkspace.get(workspaceRoot);
286
+ if (!binding) throw new Error("core_successor_fleet_core_bindings_invalid");
287
+ return binding;
288
+ });
289
+ return ordered;
290
+ }
291
+
292
+ function uniformFromCore(bindings) {
293
+ const [first] = bindings;
294
+ return first && bindings.every(binding => sameCore(binding.fromCore, first.fromCore))
295
+ ? cloneJson(first.fromCore, "from_core")
296
+ : null;
297
+ }
298
+
299
+ function assertCompleteFleetDiscovery(discovery) {
300
+ if (
301
+ discovery?.schemaVersion !== "OwlCodaRunKitFleetDiscoveryV1"
302
+ || discovery.authorizationGranted !== false
303
+ || discovery.complete !== true
304
+ || !Array.isArray(discovery.workspaceRoots)
305
+ || discovery.workspaceRoots.length === 0
306
+ || !Array.isArray(discovery.unreachableRoots)
307
+ || discovery.unreachableRoots.length !== 0
308
+ || !SHA256_REF.test(discovery.frozenManifestSha256)
309
+ ) {
310
+ throw new Error("core_successor_fleet_incomplete");
311
+ }
312
+ }
313
+
314
+ function assertSourceCandidateBinding(frozen, verified) {
315
+ if (
316
+ frozen?.status !== "source_candidate_frozen"
317
+ || frozen.authorizationGranted !== false
318
+ || typeof frozen.candidatePath !== "string"
319
+ || !SHA256_REF.test(frozen.candidateSha256)
320
+ || verified?.status !== "valid"
321
+ || verified.authorizationGranted !== false
322
+ || verified.candidatePath !== frozen.candidatePath
323
+ || verified.candidateSha256 !== frozen.candidateSha256
324
+ ) {
325
+ throw new Error("core_successor_source_candidate_invalid");
326
+ }
327
+ }
328
+
329
+ function planBody({
330
+ planId,
331
+ fleetCoreBindings,
332
+ toCore,
333
+ sourceCandidate,
334
+ fleetDiscoveryRequest,
335
+ fleetDiscovery,
336
+ }) {
337
+ const fromCore = uniformFromCore(fleetCoreBindings);
338
+ return {
339
+ schemaVersion: "OwlCodaRunKitCoreSuccessorPlanV1",
340
+ planId,
341
+ status: "frozen",
342
+ fromCore,
343
+ fromCoreMode: fromCore === null ? "per_project" : "uniform",
344
+ fromCoreSetSha256: coreBindingSetSha256(fleetCoreBindings),
345
+ fleetCoreBindings: cloneJson(fleetCoreBindings, "fleet_core_bindings"),
346
+ toCore: cloneJson(toCore, "to_core"),
347
+ sourceCandidate: {
348
+ candidatePath: sourceCandidate.candidatePath,
349
+ candidateSha256: sourceCandidate.candidateSha256,
350
+ sourceFingerprint: sourceCandidate.sourceFingerprint,
351
+ ...(sourceCandidate.deliveryPacketPath
352
+ ? { deliveryPacketPath: sourceCandidate.deliveryPacketPath }
353
+ : {}),
354
+ pathClosure: cloneJson(sourceCandidate.pathClosure, "path_closure"),
355
+ materializationProof: cloneJson(
356
+ sourceCandidate.materializationProof,
357
+ "materialization_proof",
358
+ ),
359
+ },
360
+ fleetDiscoveryRequest: cloneJson(
361
+ fleetDiscoveryRequest,
362
+ "fleet_discovery_request",
363
+ ),
364
+ fleetDiscovery: cloneJson(fleetDiscovery, "fleet_discovery"),
365
+ migrationContract: {
366
+ completeFleetRequired: true,
367
+ globalPreflightBeforeWrites: true,
368
+ ownerMigrationAuthorityRequired: true,
369
+ migrationScope: "project_runkit_config_and_local_migration_receipt_only",
370
+ projectOrder: "canonical_workspace_root",
371
+ },
372
+ repositoryActions: { ...REPOSITORY_ACTIONS },
373
+ authorizationGranted: false,
374
+ };
375
+ }
376
+
377
+ function validatePlan(plan) {
378
+ if (
379
+ plan?.schemaVersion !== "OwlCodaRunKitCoreSuccessorPlanV1"
380
+ || plan.status !== "frozen"
381
+ || plan.authorizationGranted !== false
382
+ || !SHA256_REF.test(plan.planSha256)
383
+ || coreSuccessorArtifactSha256V1((({ planSha256, ...body }) => body)(plan))
384
+ !== plan.planSha256
385
+ || plan.migrationContract?.completeFleetRequired !== true
386
+ || plan.migrationContract?.globalPreflightBeforeWrites !== true
387
+ || plan.migrationContract?.ownerMigrationAuthorityRequired !== true
388
+ || JSON.stringify(plan.repositoryActions) !== JSON.stringify(REPOSITORY_ACTIONS)
389
+ ) {
390
+ throw new Error("core_successor_plan_invalid");
391
+ }
392
+ assertIdentifier(plan.planId, "plan_id");
393
+ assertCoreIdentity(plan.toCore, "to_core");
394
+ const bindings = normalizedFleetCoreBindings(
395
+ plan.fleetDiscovery,
396
+ plan.fleetCoreBindings,
397
+ );
398
+ const expectedUniform = uniformFromCore(bindings);
399
+ if (
400
+ plan.fromCoreMode !== (expectedUniform === null ? "per_project" : "uniform")
401
+ || (
402
+ expectedUniform === null
403
+ ? plan.fromCore !== null
404
+ : !sameCore(plan.fromCore, expectedUniform)
405
+ )
406
+ || plan.fromCoreSetSha256 !== coreBindingSetSha256(bindings)
407
+ ) {
408
+ throw new Error("core_successor_fleet_core_bindings_invalid");
409
+ }
410
+ if (bindings.some(binding => sameCore(binding.fromCore, plan.toCore))) {
411
+ throw new Error("core_successor_core_transition_required");
412
+ }
413
+ assertCompleteFleetDiscovery(plan.fleetDiscovery);
414
+ if (
415
+ typeof plan.sourceCandidate?.candidatePath !== "string"
416
+ || !SHA256_REF.test(plan.sourceCandidate?.candidateSha256)
417
+ || plan.sourceCandidate?.pathClosure?.status !== "valid"
418
+ || !SHA256_REF.test(plan.sourceCandidate?.pathClosure?.closureSha256)
419
+ || plan.sourceCandidate?.materializationProof?.matchesToCore !== true
420
+ || !sameCore(
421
+ plan.sourceCandidate?.materializationProof?.materializedCore,
422
+ plan.toCore,
423
+ )
424
+ ) {
425
+ throw new Error("core_successor_plan_source_candidate_invalid");
426
+ }
427
+ }
428
+
429
+ function validateOwnerAuthority(authority, plan) {
430
+ if (authority?.schemaVersion === "OwlCodaRunKitOwnerMigrationAuthorityV1") {
431
+ throw new Error("core_successor_owner_authority_untrusted");
432
+ }
433
+ if (
434
+ authority?.schemaVersion !== "OwlCodaRunKitOwnerMigrationAuthorityV2"
435
+ || typeof authority.authorityId !== "string"
436
+ || authority.authorityId.length === 0
437
+ || authority.decision !== "approved"
438
+ || authority.scope !== "migrate_declared_core_successor_fleet"
439
+ || authority.planSha256 !== plan.planSha256
440
+ || authority.fromCoreSetSha256 !== plan.fromCoreSetSha256
441
+ || authority.toCoreManifestSha256 !== plan.toCore.coreManifestSha256
442
+ || authority.fleetManifestSha256
443
+ !== plan.fleetDiscovery.frozenManifestSha256
444
+ || authority.authorizationGranted !== false
445
+ || typeof authority.signerKeyId !== "string"
446
+ || authority.signerKeyId.length === 0
447
+ || authority.signatureAlgorithm !== "ed25519"
448
+ || typeof authority.signature !== "string"
449
+ || authority.signature.length === 0
450
+ || !SHA256_REF.test(authority.authoritySha256)
451
+ || ownerAuthorityArtifactSha256V1(authority) !== authority.authoritySha256
452
+ ) {
453
+ throw new Error("core_successor_owner_authority_invalid");
454
+ }
455
+ }
456
+
457
+ function defaultDependencies(overrides = {}) {
458
+ return {
459
+ discoverFleet,
460
+ freezeSourceCandidate: freezeSourceCandidateV2,
461
+ verifySourceCandidate: verifySourceCandidateV2,
462
+ verifySourceCandidatePathClosure: verifySourceCandidatePathClosureV2,
463
+ materializeSourceCandidate: materializeSourceCandidateV2,
464
+ materializedCoreIdentity: coreIdentityFromSourceRoot,
465
+ async createMaterializationWorkspace({ sourceRoot }) {
466
+ const scratch = await mkdtemp(path.join(tmpdir(), "owlrunkit-core-successor-"));
467
+ const workspaceRoot = path.join(scratch, "materialized");
468
+ const cloned = spawnSync(
469
+ "git",
470
+ ["clone", "--quiet", "--no-hardlinks", sourceRoot, workspaceRoot],
471
+ { encoding: "utf8" },
472
+ );
473
+ if (cloned.error) throw cloned.error;
474
+ if (cloned.status !== 0) {
475
+ await rm(scratch, { recursive: true, force: true });
476
+ throw new Error("core_successor_materialization_clone_failed");
477
+ }
478
+ return {
479
+ workspaceRoot,
480
+ async cleanup() {
481
+ await rm(scratch, { recursive: true, force: true });
482
+ },
483
+ };
484
+ },
485
+ inspectProjectControlState,
486
+ runtimeCoreIdentity: currentCoreIdentity,
487
+ verifyOwnerAuthority: ({ authority }) => verifyTrustedOwnerAuthorityV1({
488
+ authority,
489
+ expectedScope: "migrate_declared_core_successor_fleet",
490
+ expectedPurpose: "core_successor",
491
+ }),
492
+ migrateWorkspace: ({ workspaceRoot }) => initializeProjectRunKit({
493
+ workspaceRoot,
494
+ }),
495
+ ...overrides,
496
+ };
497
+ }
498
+
499
+ function withFleetControlTransactions(workspaceRoots, operation, index = 0) {
500
+ if (index >= workspaceRoots.length) return operation();
501
+ return withControlTransaction(
502
+ workspaceRoots[index],
503
+ () => withFleetControlTransactions(workspaceRoots, operation, index + 1),
504
+ );
505
+ }
506
+
507
+ async function proveMaterializedSourceCandidate({
508
+ root,
509
+ frozen,
510
+ toCore,
511
+ dependencies,
512
+ }) {
513
+ if (
514
+ !Array.isArray(frozen.ownedPaths)
515
+ || frozen.ownedPaths.length === 0
516
+ ) {
517
+ throw new Error("core_successor_source_candidate_owned_paths_missing");
518
+ }
519
+ const pathClosure = await dependencies.verifySourceCandidatePathClosure({
520
+ workspaceRoot: root,
521
+ candidatePath: frozen.candidatePath,
522
+ includedPaths: frozen.ownedPaths,
523
+ });
524
+ if (
525
+ pathClosure?.status !== "valid"
526
+ || pathClosure.authorizationGranted !== false
527
+ || !SHA256_REF.test(pathClosure.closureSha256)
528
+ ) {
529
+ throw new Error("core_successor_source_candidate_path_closure_invalid");
530
+ }
531
+ const scratch = await dependencies.createMaterializationWorkspace({
532
+ sourceRoot: root,
533
+ });
534
+ const materializedRoot = scratch.workspaceRoot;
535
+ try {
536
+ const materialized = await dependencies.materializeSourceCandidate({
537
+ workspaceRoot: root,
538
+ candidatePath: frozen.candidatePath,
539
+ targetWorkspaceRoot: materializedRoot,
540
+ });
541
+ if (
542
+ materialized?.status !== "source_candidate_materialized"
543
+ || materialized.authorizationGranted !== false
544
+ || materialized.candidateSha256 !== frozen.candidateSha256
545
+ ) {
546
+ throw new Error("core_successor_source_candidate_materialization_invalid");
547
+ }
548
+ const materializedCore = await dependencies.materializedCoreIdentity(
549
+ materializedRoot,
550
+ );
551
+ assertCoreIdentity(materializedCore, "materialized_core");
552
+ if (!sameCore(materializedCore, toCore)) {
553
+ throw new Error("core_successor_materialized_core_identity_mismatch");
554
+ }
555
+ return {
556
+ pathClosure: cloneJson(pathClosure, "path_closure"),
557
+ materializationProof: {
558
+ candidateSha256: frozen.candidateSha256,
559
+ payloadSha256: materialized.payloadSha256,
560
+ materializedCore: cloneJson(materializedCore, "materialized_core"),
561
+ matchesToCore: true,
562
+ },
563
+ };
564
+ } finally {
565
+ await scratch.cleanup();
566
+ }
567
+ }
568
+
569
+ export async function createCoreSuccessorPlanV1({
570
+ controllerWorkspaceRoot,
571
+ planId,
572
+ planPath = null,
573
+ fromCore,
574
+ fleetCoreBindings = null,
575
+ toCore,
576
+ sourceCandidateRequest,
577
+ fleetDiscoveryRequest,
578
+ dependencies: dependencyOverrides = {},
579
+ } = {}) {
580
+ assertIdentifier(planId, "plan_id");
581
+ if (fleetCoreBindings === null) assertCoreIdentity(fromCore, "from_core");
582
+ assertCoreIdentity(toCore, "to_core");
583
+ if (fleetCoreBindings === null && sameCore(fromCore, toCore)) {
584
+ throw new Error("core_successor_core_transition_required");
585
+ }
586
+ const root = await canonicalWorkspaceRoot(controllerWorkspaceRoot);
587
+ const selectedPlanPath = planPath
588
+ ?? `.owlcoda/runkit/core-successors/${planId}/plan.json`;
589
+ await prepareCreateOnlyPath(
590
+ root,
591
+ selectedPlanPath,
592
+ "core_successor_plan_exists",
593
+ );
594
+ const dependencies = defaultDependencies(dependencyOverrides);
595
+ const discoveryRequest = cloneJson(
596
+ fleetDiscoveryRequest,
597
+ "fleet_discovery_request",
598
+ );
599
+ const fleetDiscovery = await dependencies.discoverFleet(discoveryRequest);
600
+ assertCompleteFleetDiscovery(fleetDiscovery);
601
+ const bindings = normalizedFleetCoreBindings(
602
+ fleetDiscovery,
603
+ fleetCoreBindings ?? fleetDiscovery.workspaceRoots.map(workspaceRoot => ({
604
+ workspaceRoot,
605
+ fromCore,
606
+ })),
607
+ );
608
+ if (bindings.some(binding => sameCore(binding.fromCore, toCore))) {
609
+ throw new Error("core_successor_core_transition_required");
610
+ }
611
+ const frozen = await dependencies.freezeSourceCandidate({
612
+ workspaceRoot: root,
613
+ ...sourceCandidateRequest,
614
+ });
615
+ const verified = await dependencies.verifySourceCandidate({
616
+ workspaceRoot: root,
617
+ candidatePath: frozen.candidatePath,
618
+ });
619
+ assertSourceCandidateBinding(frozen, verified);
620
+ const proof = await proveMaterializedSourceCandidate({
621
+ root,
622
+ frozen,
623
+ toCore,
624
+ dependencies,
625
+ });
626
+ const body = planBody({
627
+ planId,
628
+ fleetCoreBindings: bindings,
629
+ toCore,
630
+ sourceCandidate: {
631
+ ...frozen,
632
+ ...proof,
633
+ },
634
+ fleetDiscoveryRequest: discoveryRequest,
635
+ fleetDiscovery,
636
+ });
637
+ const plan = {
638
+ ...body,
639
+ planSha256: coreSuccessorArtifactSha256V1(body),
640
+ };
641
+ const writtenPath = await writeCreateOnlyJson(
642
+ root,
643
+ selectedPlanPath,
644
+ plan,
645
+ "core_successor_plan_exists",
646
+ );
647
+ return {
648
+ status: "core_successor_plan_created",
649
+ exitCode: 0,
650
+ planId,
651
+ planPath: writtenPath,
652
+ planSha256: plan.planSha256,
653
+ sourceCandidateSha256: frozen.candidateSha256,
654
+ fromCoreMode: body.fromCoreMode,
655
+ fromCoreSetSha256: body.fromCoreSetSha256,
656
+ fleetManifestSha256: fleetDiscovery.frozenManifestSha256,
657
+ fleetSize: fleetDiscovery.workspaceRoots.length,
658
+ authorizationGranted: false,
659
+ };
660
+ }
661
+
662
+ export async function createCoreSuccessorPlanFromFleetV1({
663
+ controllerWorkspaceRoot,
664
+ planId,
665
+ planPath = null,
666
+ sourceCandidateRequest,
667
+ fleetDiscoveryRequest,
668
+ dependencies: dependencyOverrides = {},
669
+ } = {}) {
670
+ const dependencies = defaultDependencies(dependencyOverrides);
671
+ const discoveryRequest = cloneJson(
672
+ fleetDiscoveryRequest,
673
+ "fleet_discovery_request",
674
+ );
675
+ const fleetDiscovery = await dependencies.discoverFleet(discoveryRequest);
676
+ assertCompleteFleetDiscovery(fleetDiscovery);
677
+ const fleetCores = [];
678
+ for (const workspaceRoot of fleetDiscovery.workspaceRoots) {
679
+ const config = await readWorkspaceConfig(workspaceRoot);
680
+ const configGate = validateProjectConfigV2(config);
681
+ if (!configGate.valid) {
682
+ throw new Error("core_successor_fleet_config_invalid");
683
+ }
684
+ fleetCores.push({
685
+ workspaceRoot,
686
+ fromCore: config.core,
687
+ });
688
+ }
689
+ if (fleetCores.length === 0) throw new Error("core_successor_fleet_incomplete");
690
+ const toCore = await dependencies.runtimeCoreIdentity();
691
+ assertCoreIdentity(toCore, "to_core");
692
+ const created = await createCoreSuccessorPlanV1({
693
+ controllerWorkspaceRoot,
694
+ planId,
695
+ planPath,
696
+ fleetCoreBindings: fleetCores,
697
+ toCore,
698
+ sourceCandidateRequest,
699
+ fleetDiscoveryRequest: discoveryRequest,
700
+ dependencies: {
701
+ ...dependencyOverrides,
702
+ discoverFleet: async () => cloneJson(
703
+ fleetDiscovery,
704
+ "fleet_discovery",
705
+ ),
706
+ },
707
+ });
708
+ return {
709
+ ...created,
710
+ fromCore: uniformFromCore(fleetCores),
711
+ fromCoreMode: uniformFromCore(fleetCores) === null ? "per_project" : "uniform",
712
+ fromCoreSetSha256: coreBindingSetSha256(fleetCores),
713
+ toCore: cloneJson(toCore, "to_core"),
714
+ };
715
+ }
716
+
717
+ async function readWorkspaceConfig(workspaceRoot) {
718
+ const root = await canonicalWorkspaceRoot(workspaceRoot);
719
+ const configPath = path.join(root, ".owlcoda/runkit/config.json");
720
+ try {
721
+ const stat = await lstat(configPath);
722
+ if (
723
+ stat.isSymbolicLink()
724
+ || !stat.isFile()
725
+ || stat.size > MAX_ARTIFACT_BYTES
726
+ || await realpath(configPath) !== path.resolve(configPath)
727
+ ) {
728
+ throw new Error("invalid");
729
+ }
730
+ return JSON.parse(await readFile(configPath, "utf8"));
731
+ } catch {
732
+ throw new Error("core_successor_project_config_invalid");
733
+ }
734
+ }
735
+
736
+ function discoveryMatchesPlan(current, frozen) {
737
+ return current?.complete === true
738
+ && current.authorizationGranted === false
739
+ && current.frozenManifestSha256 === frozen.frozenManifestSha256
740
+ && JSON.stringify(current.workspaceRoots) === JSON.stringify(frozen.workspaceRoots)
741
+ && JSON.stringify(current.coverageRoots) === JSON.stringify(frozen.coverageRoots)
742
+ && JSON.stringify(current.unreachableRoots) === JSON.stringify(
743
+ frozen.unreachableRoots,
744
+ );
745
+ }
746
+
747
+ async function preflightWorkspace({
748
+ workspaceRoot,
749
+ fromCore,
750
+ toCore,
751
+ successEvidence,
752
+ inspectControl,
753
+ }) {
754
+ const issueCodes = [];
755
+ let config;
756
+ let controlCore = fromCore;
757
+ try {
758
+ config = await readWorkspaceConfig(workspaceRoot);
759
+ const configGate = validateProjectConfigV2(config);
760
+ if (!configGate.valid) issueCodes.push("project_config_invalid");
761
+ if (configGate.valid) {
762
+ if (sameCore(config.core, fromCore)) {
763
+ if (successEvidence) {
764
+ issueCodes.push("project_success_receipt_config_drift");
765
+ }
766
+ } else if (sameCore(config.core, toCore)) {
767
+ controlCore = toCore;
768
+ if (!successEvidence) {
769
+ issueCodes.push("project_target_core_without_success_receipt");
770
+ }
771
+ } else {
772
+ issueCodes.push("project_core_drift");
773
+ }
774
+ }
775
+ } catch {
776
+ issueCodes.push("project_config_missing_or_invalid");
777
+ }
778
+ try {
779
+ const control = await inspectControl({
780
+ workspaceRoot,
781
+ currentCore: controlCore,
782
+ });
783
+ if (control?.upgradeSafety?.status !== "safe") {
784
+ issueCodes.push("project_control_unsafe");
785
+ }
786
+ } catch {
787
+ issueCodes.push("project_control_unreadable");
788
+ }
789
+ const uniqueIssues = [...new Set(issueCodes)].sort();
790
+ return {
791
+ workspaceRoot,
792
+ fromCore: cloneJson(fromCore, "project_from_core"),
793
+ status: uniqueIssues.includes("project_core_drift")
794
+ ? "core_drift"
795
+ : uniqueIssues.length > 0
796
+ ? "unsafe"
797
+ : successEvidence
798
+ ? "already_migrated"
799
+ : "safe",
800
+ issueCodes: uniqueIssues,
801
+ migrationAttempted: false,
802
+ ...(successEvidence
803
+ ? {
804
+ projectSuccessReceiptPath: successEvidence.receiptPath,
805
+ projectSuccessReceiptSha256: successEvidence.receiptSha256,
806
+ }
807
+ : {}),
808
+ };
809
+ }
810
+
811
+ function batchReceiptBody({
812
+ plan,
813
+ ownerAuthority,
814
+ trustedAuthority,
815
+ receiptId,
816
+ status,
817
+ projects,
818
+ migrationAttempted,
819
+ attemptNumber,
820
+ receiptBasePath,
821
+ priorAttemptReceiptPath,
822
+ priorAttemptReceiptSha256,
823
+ projectSuccessReceipts,
824
+ orphanRecovery = false,
825
+ orphanSuccessReceiptCount = 0,
826
+ }) {
827
+ return {
828
+ schemaVersion: "OwlCodaRunKitCoreSuccessorBatchReceiptV1",
829
+ receiptId,
830
+ attemptNumber,
831
+ receiptBasePath,
832
+ priorAttemptReceiptPath,
833
+ priorAttemptReceiptSha256,
834
+ planId: plan.planId,
835
+ planSha256: plan.planSha256,
836
+ ownerAuthorityId: ownerAuthority.authorityId,
837
+ ownerAuthoritySha256: ownerAuthority.authoritySha256,
838
+ ownerAuthoritySignerKeyId: trustedAuthority.signerKeyId,
839
+ ownerAuthorityTrustStoreSha256: trustedAuthority.trustStoreSha256,
840
+ fromCore: cloneJson(plan.fromCore, "from_core"),
841
+ fromCoreMode: plan.fromCoreMode,
842
+ fromCoreSetSha256: plan.fromCoreSetSha256,
843
+ fleetCoreBindings: cloneJson(
844
+ plan.fleetCoreBindings,
845
+ "fleet_core_bindings",
846
+ ),
847
+ toCore: cloneJson(plan.toCore, "to_core"),
848
+ fleetManifestSha256: plan.fleetDiscovery.frozenManifestSha256,
849
+ sourceCandidateSha256: plan.sourceCandidate.candidateSha256,
850
+ status,
851
+ migrationAttempted,
852
+ migratedCount: projectSuccessReceipts.length,
853
+ projectCount: projects.length,
854
+ orphanRecovery,
855
+ orphanSuccessReceiptCount,
856
+ projects: cloneJson(projects, "project_results"),
857
+ projectSuccessReceipts: cloneJson(
858
+ projectSuccessReceipts,
859
+ "project_success_receipts",
860
+ ),
861
+ repositoryActions: { ...REPOSITORY_ACTIONS },
862
+ authorizationGranted: false,
863
+ };
864
+ }
865
+
866
+ async function persistBatchReceipt({
867
+ root,
868
+ receiptPath,
869
+ plan,
870
+ ownerAuthority,
871
+ trustedAuthority,
872
+ receiptId,
873
+ status,
874
+ projects,
875
+ migrationAttempted,
876
+ attemptNumber,
877
+ receiptBasePath,
878
+ priorAttemptReceiptPath,
879
+ priorAttemptReceiptSha256,
880
+ projectSuccessReceipts,
881
+ orphanRecovery = false,
882
+ orphanSuccessReceiptCount = 0,
883
+ }) {
884
+ const body = batchReceiptBody({
885
+ plan,
886
+ ownerAuthority,
887
+ trustedAuthority,
888
+ receiptId,
889
+ status,
890
+ projects,
891
+ migrationAttempted,
892
+ attemptNumber,
893
+ receiptBasePath,
894
+ priorAttemptReceiptPath,
895
+ priorAttemptReceiptSha256,
896
+ projectSuccessReceipts,
897
+ orphanRecovery,
898
+ orphanSuccessReceiptCount,
899
+ });
900
+ const receipt = {
901
+ ...body,
902
+ receiptSha256: coreSuccessorArtifactSha256V1(body),
903
+ };
904
+ const writtenPath = await writeCreateOnlyJson(
905
+ root,
906
+ receiptPath,
907
+ receipt,
908
+ "core_successor_receipt_exists",
909
+ );
910
+ return {
911
+ status,
912
+ exitCode: status === "applied" ? 0 : 2,
913
+ receiptId,
914
+ attemptNumber,
915
+ receiptPath: writtenPath,
916
+ receiptSha256: receipt.receiptSha256,
917
+ projectCount: projects.length,
918
+ migratedCount: receipt.migratedCount,
919
+ authorizationGranted: false,
920
+ };
921
+ }
922
+
923
+ function assertTrustedAuthority(trustedAuthority, ownerAuthority) {
924
+ if (
925
+ trustedAuthority?.status !== "trusted"
926
+ || trustedAuthority.signerKeyId !== ownerAuthority.signerKeyId
927
+ || trustedAuthority.authoritySha256 !== ownerAuthority.authoritySha256
928
+ ) {
929
+ throw new Error("core_successor_owner_authority_untrusted");
930
+ }
931
+ }
932
+
933
+ function assertBatchReceiptSelfHash(receipt) {
934
+ if (
935
+ !SHA256_REF.test(receipt?.receiptSha256)
936
+ || coreSuccessorArtifactSha256V1(
937
+ (({ receiptSha256, ...body }) => body)(receipt),
938
+ ) !== receipt.receiptSha256
939
+ ) {
940
+ throw new Error("core_successor_prior_attempt_receipt_invalid");
941
+ }
942
+ }
943
+
944
+ function validatePriorPartialReceipt({
945
+ receipt,
946
+ receiptPath,
947
+ plan,
948
+ ownerAuthority,
949
+ }) {
950
+ assertBatchReceiptSelfHash(receipt);
951
+ if (
952
+ receipt.schemaVersion !== "OwlCodaRunKitCoreSuccessorBatchReceiptV1"
953
+ || receipt.status !== "partial_failure"
954
+ || receipt.planId !== plan.planId
955
+ || receipt.planSha256 !== plan.planSha256
956
+ || receipt.ownerAuthorityId !== ownerAuthority.authorityId
957
+ || receipt.ownerAuthoritySha256 !== ownerAuthority.authoritySha256
958
+ || receipt.fromCoreSetSha256 !== plan.fromCoreSetSha256
959
+ || receipt.fleetManifestSha256
960
+ !== plan.fleetDiscovery.frozenManifestSha256
961
+ || receipt.sourceCandidateSha256
962
+ !== plan.sourceCandidate.candidateSha256
963
+ || !sameCore(receipt.toCore, plan.toCore)
964
+ || !Number.isInteger(receipt.attemptNumber)
965
+ || receipt.attemptNumber < 1
966
+ || typeof receipt.receiptBasePath !== "string"
967
+ || receiptAttemptPath(
968
+ receipt.receiptBasePath,
969
+ receipt.attemptNumber,
970
+ ) !== receiptPath
971
+ || (
972
+ receipt.attemptNumber === 1
973
+ ? receipt.priorAttemptReceiptPath !== null
974
+ || receipt.priorAttemptReceiptSha256 !== null
975
+ : typeof receipt.priorAttemptReceiptPath !== "string"
976
+ || !SHA256_REF.test(receipt.priorAttemptReceiptSha256)
977
+ || receipt.priorAttemptReceiptPath !== receiptAttemptPath(
978
+ receipt.receiptBasePath,
979
+ receipt.attemptNumber - 1,
980
+ )
981
+ )
982
+ || !Array.isArray(receipt.projects)
983
+ || receipt.projects.length !== plan.fleetDiscovery.workspaceRoots.length
984
+ || !Array.isArray(receipt.projectSuccessReceipts)
985
+ || receipt.projectCount !== receipt.projects.length
986
+ || receipt.migratedCount !== receipt.projectSuccessReceipts.length
987
+ || typeof receipt.orphanRecovery !== "boolean"
988
+ || !Number.isInteger(receipt.orphanSuccessReceiptCount)
989
+ || receipt.orphanSuccessReceiptCount < 0
990
+ || receipt.orphanSuccessReceiptCount > receipt.projectSuccessReceipts.length
991
+ || receipt.orphanRecovery !== (receipt.orphanSuccessReceiptCount > 0)
992
+ || receipt.migrationAttempted !== true
993
+ || JSON.stringify(receipt.repositoryActions)
994
+ !== JSON.stringify(REPOSITORY_ACTIONS)
995
+ || receipt.authorizationGranted !== false
996
+ ) {
997
+ throw new Error("core_successor_prior_attempt_receipt_invalid");
998
+ }
999
+ const orderedRoots = receipt.projects.map(row => row.workspaceRoot);
1000
+ if (
1001
+ JSON.stringify(orderedRoots)
1002
+ !== JSON.stringify(plan.fleetDiscovery.workspaceRoots)
1003
+ ) {
1004
+ throw new Error("core_successor_prior_attempt_project_order_invalid");
1005
+ }
1006
+ let failureSeen = false;
1007
+ const successRoots = [];
1008
+ for (const row of receipt.projects) {
1009
+ const binding = plan.fleetCoreBindings.find(
1010
+ candidate => candidate.workspaceRoot === row.workspaceRoot,
1011
+ );
1012
+ if (!binding || !sameCore(row.fromCore, binding.fromCore)) {
1013
+ throw new Error("core_successor_prior_attempt_receipt_invalid");
1014
+ }
1015
+ if (
1016
+ row.status === "migrated"
1017
+ || row.status === "skipped_as_already_migrated"
1018
+ ) {
1019
+ if (failureSeen) {
1020
+ throw new Error("core_successor_prior_attempt_project_order_invalid");
1021
+ }
1022
+ successRoots.push(row.workspaceRoot);
1023
+ } else if (row.status === "migration_failed") {
1024
+ if (failureSeen) {
1025
+ throw new Error("core_successor_prior_attempt_project_order_invalid");
1026
+ }
1027
+ failureSeen = true;
1028
+ } else if (row.status !== "not_attempted_after_failure" || !failureSeen) {
1029
+ throw new Error("core_successor_prior_attempt_project_order_invalid");
1030
+ }
1031
+ }
1032
+ if (!failureSeen) {
1033
+ throw new Error("core_successor_prior_attempt_receipt_invalid");
1034
+ }
1035
+ const successRefRoots = receipt.projectSuccessReceipts.map(
1036
+ row => row.workspaceRoot,
1037
+ );
1038
+ if (
1039
+ new Set(successRefRoots).size !== successRefRoots.length
1040
+ || JSON.stringify(successRefRoots) !== JSON.stringify(successRoots)
1041
+ || receipt.projectSuccessReceipts.some(reference => (
1042
+ reference.receiptBasePath !== receipt.receiptBasePath
1043
+ || reference.receiptPath !== projectSuccessReceiptPath(
1044
+ receipt.receiptBasePath,
1045
+ receipt.receiptId,
1046
+ reference.workspaceRoot,
1047
+ )
1048
+ ))
1049
+ ) {
1050
+ throw new Error("core_successor_prior_attempt_success_set_invalid");
1051
+ }
1052
+ }
1053
+
1054
+ async function validatePriorAttemptLineage({
1055
+ root,
1056
+ receipt,
1057
+ receiptPath,
1058
+ plan,
1059
+ ownerAuthority,
1060
+ }) {
1061
+ let current = receipt;
1062
+ let currentPath = receiptPath;
1063
+ for (;;) {
1064
+ validatePriorPartialReceipt({
1065
+ receipt: current,
1066
+ receiptPath: currentPath,
1067
+ plan,
1068
+ ownerAuthority,
1069
+ });
1070
+ if (current.attemptNumber === 1) return;
1071
+ const parent = await readBoundedJson(
1072
+ root,
1073
+ current.priorAttemptReceiptPath,
1074
+ "prior_attempt_receipt_path",
1075
+ );
1076
+ if (
1077
+ parent.receiptSha256 !== current.priorAttemptReceiptSha256
1078
+ || parent.attemptNumber !== current.attemptNumber - 1
1079
+ ) {
1080
+ throw new Error("core_successor_prior_attempt_lineage_invalid");
1081
+ }
1082
+ current = parent;
1083
+ currentPath = current.priorAttemptReceiptPath
1084
+ ? receiptAttemptPath(
1085
+ current.receiptBasePath,
1086
+ current.attemptNumber,
1087
+ )
1088
+ : current.receiptBasePath;
1089
+ }
1090
+ }
1091
+
1092
+ async function assertNoResumeBranch({
1093
+ root,
1094
+ priorReceipt,
1095
+ }) {
1096
+ const basePath = safeRelativePath(
1097
+ priorReceipt.receiptBasePath,
1098
+ "receipt_base_path",
1099
+ );
1100
+ const directoryPath = path.resolve(root, path.dirname(basePath));
1101
+ const extension = path.extname(basePath);
1102
+ const baseName = path.basename(
1103
+ extension.length > 0
1104
+ ? basePath.slice(0, -extension.length)
1105
+ : basePath,
1106
+ );
1107
+ const escapedBase = baseName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
1108
+ const pattern = new RegExp(
1109
+ `^${escapedBase}-attempt-(\\d{3,})${extension.replace(
1110
+ /[.*+?^${}()|[\]\\]/gu,
1111
+ "\\$&",
1112
+ )}$`,
1113
+ "u",
1114
+ );
1115
+ let entries = [];
1116
+ try {
1117
+ entries = await readdir(directoryPath);
1118
+ } catch (error) {
1119
+ if (error?.code !== "ENOENT") throw error;
1120
+ }
1121
+ for (const entry of entries) {
1122
+ const match = pattern.exec(entry);
1123
+ if (match && Number(match[1]) > priorReceipt.attemptNumber) {
1124
+ throw new Error("core_successor_resume_branch_exists");
1125
+ }
1126
+ }
1127
+ }
1128
+
1129
+ async function validateProjectSuccessEvidence({
1130
+ root,
1131
+ reference,
1132
+ plan,
1133
+ ownerAuthority,
1134
+ receiptId,
1135
+ receiptBasePath,
1136
+ maximumAttemptNumber,
1137
+ expectedAttemptNumber = null,
1138
+ expectedPriorAttemptReceiptSha256 = undefined,
1139
+ }) {
1140
+ if (
1141
+ !reference
1142
+ || typeof reference.workspaceRoot !== "string"
1143
+ || typeof reference.receiptPath !== "string"
1144
+ || !SHA256_REF.test(reference.receiptSha256)
1145
+ || reference.receiptBasePath !== receiptBasePath
1146
+ || reference.receiptPath !== projectSuccessReceiptPath(
1147
+ receiptBasePath,
1148
+ receiptId,
1149
+ reference.workspaceRoot,
1150
+ )
1151
+ ) {
1152
+ throw new Error("core_successor_project_success_receipt_invalid");
1153
+ }
1154
+ const receipt = await readBoundedJson(
1155
+ root,
1156
+ reference.receiptPath,
1157
+ "project_success_receipt_path",
1158
+ );
1159
+ if (
1160
+ receipt.receiptSha256 !== reference.receiptSha256
1161
+ || !SHA256_REF.test(receipt.receiptSha256)
1162
+ || coreSuccessorArtifactSha256V1(
1163
+ (({ receiptSha256, ...body }) => body)(receipt),
1164
+ ) !== receipt.receiptSha256
1165
+ || receipt.schemaVersion
1166
+ !== "OwlCodaRunKitCoreSuccessorProjectSuccessReceiptV1"
1167
+ || receipt.receiptId !== receiptId
1168
+ || receipt.workspaceRoot !== reference.workspaceRoot
1169
+ || receipt.planSha256 !== plan.planSha256
1170
+ || receipt.ownerAuthoritySha256 !== ownerAuthority.authoritySha256
1171
+ || receipt.fleetManifestSha256
1172
+ !== plan.fleetDiscovery.frozenManifestSha256
1173
+ || !sameCore(receipt.fromCore, plan.fleetCoreBindings.find(
1174
+ row => row.workspaceRoot === receipt.workspaceRoot,
1175
+ )?.fromCore)
1176
+ || !sameCore(receipt.toCore, plan.toCore)
1177
+ || !Number.isInteger(receipt.attemptNumber)
1178
+ || receipt.attemptNumber < 1
1179
+ || receipt.attemptNumber > maximumAttemptNumber
1180
+ || (
1181
+ expectedAttemptNumber !== null
1182
+ && receipt.attemptNumber !== expectedAttemptNumber
1183
+ )
1184
+ || (
1185
+ expectedPriorAttemptReceiptSha256 !== undefined
1186
+ && receipt.priorAttemptReceiptSha256
1187
+ !== expectedPriorAttemptReceiptSha256
1188
+ )
1189
+ || (
1190
+ receipt.attemptNumber === 1
1191
+ ? receipt.priorAttemptReceiptSha256 !== null
1192
+ : !SHA256_REF.test(receipt.priorAttemptReceiptSha256)
1193
+ )
1194
+ || JSON.stringify(receipt.repositoryActions)
1195
+ !== JSON.stringify(REPOSITORY_ACTIONS)
1196
+ || receipt.authorizationGranted !== false
1197
+ ) {
1198
+ throw new Error("core_successor_project_success_receipt_invalid");
1199
+ }
1200
+ const configArtifact = await readWorkspaceArtifact(
1201
+ receipt.workspaceRoot,
1202
+ ".owlcoda/runkit/config.json",
1203
+ "post_config",
1204
+ );
1205
+ const configGate = validateProjectConfigV2(configArtifact.value);
1206
+ const migrationArtifact = await readWorkspaceArtifact(
1207
+ receipt.workspaceRoot,
1208
+ receipt.migrationReceiptPath,
1209
+ "migration_receipt",
1210
+ );
1211
+ if (
1212
+ !configGate.valid
1213
+ || !sameCore(configArtifact.value.core, plan.toCore)
1214
+ || configArtifact.sha256 !== receipt.postConfigSha256
1215
+ || migrationArtifact.sha256 !== receipt.migrationReceiptSha256
1216
+ || migrationArtifact.value?.schemaVersion
1217
+ !== "OwlCodaRunKitConfigMigrationReceiptV1"
1218
+ || !sameCore(migrationArtifact.value.fromCore, receipt.fromCore)
1219
+ || !sameCore(migrationArtifact.value.toCore, receipt.toCore)
1220
+ || migrationArtifact.value.authorizationGranted !== false
1221
+ ) {
1222
+ throw new Error("core_successor_project_success_evidence_drift");
1223
+ }
1224
+ return {
1225
+ ...reference,
1226
+ receipt,
1227
+ };
1228
+ }
1229
+
1230
+ function orderProjectSuccessReferences(plan, references) {
1231
+ const byRoot = new Map(
1232
+ references.map(reference => [reference.workspaceRoot, reference]),
1233
+ );
1234
+ return plan.fleetDiscovery.workspaceRoots
1235
+ .filter(workspaceRoot => byRoot.has(workspaceRoot))
1236
+ .map(workspaceRoot => byRoot.get(workspaceRoot));
1237
+ }
1238
+
1239
+ async function discoverOrphanProjectSuccessEvidence({
1240
+ root,
1241
+ plan,
1242
+ ownerAuthority,
1243
+ receiptId,
1244
+ receiptBasePath,
1245
+ attemptNumber,
1246
+ priorAttemptReceiptSha256,
1247
+ existingReferences,
1248
+ }) {
1249
+ const referencedRoots = new Set(
1250
+ existingReferences.map(reference => reference.workspaceRoot),
1251
+ );
1252
+ const discovered = [];
1253
+ for (const workspaceRoot of plan.fleetDiscovery.workspaceRoots) {
1254
+ if (referencedRoots.has(workspaceRoot)) continue;
1255
+ const receiptPath = projectSuccessReceiptPath(
1256
+ receiptBasePath,
1257
+ receiptId,
1258
+ workspaceRoot,
1259
+ );
1260
+ if (!await pathExists(path.resolve(root, receiptPath))) continue;
1261
+ const receipt = await readBoundedJson(
1262
+ root,
1263
+ receiptPath,
1264
+ "project_success_receipt_path",
1265
+ );
1266
+ const reference = {
1267
+ workspaceRoot,
1268
+ receiptBasePath,
1269
+ receiptPath,
1270
+ receiptSha256: receipt.receiptSha256,
1271
+ };
1272
+ const evidence = await validateProjectSuccessEvidence({
1273
+ root,
1274
+ reference,
1275
+ plan,
1276
+ ownerAuthority,
1277
+ receiptId,
1278
+ receiptBasePath,
1279
+ maximumAttemptNumber: attemptNumber,
1280
+ expectedAttemptNumber: attemptNumber,
1281
+ expectedPriorAttemptReceiptSha256: priorAttemptReceiptSha256,
1282
+ });
1283
+ discovered.push({ reference, evidence });
1284
+ }
1285
+ return discovered;
1286
+ }
1287
+
1288
+ async function matchingCommittedMigrationReceipt({
1289
+ workspaceRoot,
1290
+ fromCore,
1291
+ toCore,
1292
+ }) {
1293
+ const workspace = await canonicalWorkspaceRoot(workspaceRoot);
1294
+ const relativeRoot = ".owlcoda/runkit/config-migration-receipts";
1295
+ const absoluteRoot = path.resolve(workspace, relativeRoot);
1296
+ let rootStat;
1297
+ try {
1298
+ rootStat = await lstat(absoluteRoot);
1299
+ } catch (error) {
1300
+ if (error?.code === "ENOENT") return null;
1301
+ throw error;
1302
+ }
1303
+ if (
1304
+ rootStat.isSymbolicLink()
1305
+ || !rootStat.isDirectory()
1306
+ || await realpath(absoluteRoot) !== absoluteRoot
1307
+ ) {
1308
+ throw new Error("core_successor_migration_receipts_root_invalid");
1309
+ }
1310
+ const matches = [];
1311
+ for (const entry of (await readdir(absoluteRoot, {
1312
+ withFileTypes: true,
1313
+ })).sort((left, right) => left.name.localeCompare(right.name))) {
1314
+ if (
1315
+ entry.isSymbolicLink()
1316
+ || !entry.isFile()
1317
+ || !entry.name.endsWith(".json")
1318
+ ) {
1319
+ continue;
1320
+ }
1321
+ const relativePath = `${relativeRoot}/${entry.name}`;
1322
+ const artifact = await readWorkspaceArtifact(
1323
+ workspace,
1324
+ relativePath,
1325
+ "migration_receipt",
1326
+ );
1327
+ if (
1328
+ artifact.value?.schemaVersion
1329
+ === "OwlCodaRunKitConfigMigrationReceiptV1"
1330
+ && sameCore(artifact.value.fromCore, fromCore)
1331
+ && sameCore(artifact.value.toCore, toCore)
1332
+ && artifact.value.authorizationGranted === false
1333
+ ) {
1334
+ matches.push(relativePath);
1335
+ }
1336
+ }
1337
+ return matches[0] ?? null;
1338
+ }
1339
+
1340
+ async function recoverCommittedProjectSuccessEvidence({
1341
+ root,
1342
+ plan,
1343
+ ownerAuthority,
1344
+ receiptId,
1345
+ receiptBasePath,
1346
+ attemptNumber,
1347
+ priorAttemptReceiptSha256,
1348
+ existingReferences,
1349
+ }) {
1350
+ const referencedRoots = new Set(
1351
+ existingReferences.map(reference => reference.workspaceRoot),
1352
+ );
1353
+ const recovered = [];
1354
+ for (const binding of plan.fleetCoreBindings) {
1355
+ const workspaceRoot = binding.workspaceRoot;
1356
+ if (referencedRoots.has(workspaceRoot)) continue;
1357
+ let config;
1358
+ try {
1359
+ config = await readWorkspaceConfig(workspaceRoot);
1360
+ } catch {
1361
+ continue;
1362
+ }
1363
+ if (!sameCore(config.core, plan.toCore)) continue;
1364
+ const migrationReceipt = await matchingCommittedMigrationReceipt({
1365
+ workspaceRoot,
1366
+ fromCore: binding.fromCore,
1367
+ toCore: plan.toCore,
1368
+ });
1369
+ if (migrationReceipt === null) continue;
1370
+ const reference = await writeProjectSuccessReceipt({
1371
+ root,
1372
+ receiptBasePath,
1373
+ receiptId,
1374
+ attemptNumber,
1375
+ priorAttemptReceiptSha256,
1376
+ plan,
1377
+ ownerAuthority,
1378
+ workspaceRoot,
1379
+ fromCore: binding.fromCore,
1380
+ migrationResult: {
1381
+ status: "upgraded",
1382
+ migrationReceipt,
1383
+ },
1384
+ });
1385
+ const evidence = await validateProjectSuccessEvidence({
1386
+ root,
1387
+ reference,
1388
+ plan,
1389
+ ownerAuthority,
1390
+ receiptId,
1391
+ receiptBasePath,
1392
+ maximumAttemptNumber: attemptNumber,
1393
+ expectedAttemptNumber: attemptNumber,
1394
+ expectedPriorAttemptReceiptSha256: priorAttemptReceiptSha256,
1395
+ });
1396
+ recovered.push({ reference, evidence });
1397
+ }
1398
+ return recovered;
1399
+ }
1400
+
1401
+ async function writeProjectSuccessReceipt({
1402
+ root,
1403
+ receiptBasePath,
1404
+ receiptId,
1405
+ attemptNumber,
1406
+ priorAttemptReceiptSha256,
1407
+ plan,
1408
+ ownerAuthority,
1409
+ workspaceRoot,
1410
+ fromCore,
1411
+ migrationResult,
1412
+ }) {
1413
+ const migrationArtifact = await readWorkspaceArtifact(
1414
+ workspaceRoot,
1415
+ migrationResult.migrationReceipt,
1416
+ "migration_receipt",
1417
+ );
1418
+ const configArtifact = await readWorkspaceArtifact(
1419
+ workspaceRoot,
1420
+ ".owlcoda/runkit/config.json",
1421
+ "post_config",
1422
+ );
1423
+ const configGate = validateProjectConfigV2(configArtifact.value);
1424
+ if (
1425
+ migrationArtifact.value?.schemaVersion
1426
+ !== "OwlCodaRunKitConfigMigrationReceiptV1"
1427
+ || !sameCore(migrationArtifact.value.fromCore, fromCore)
1428
+ || !sameCore(migrationArtifact.value.toCore, plan.toCore)
1429
+ || migrationArtifact.value.authorizationGranted !== false
1430
+ || !configGate.valid
1431
+ || !sameCore(configArtifact.value.core, plan.toCore)
1432
+ ) {
1433
+ throw new Error("core_successor_migration_evidence_invalid");
1434
+ }
1435
+ const body = {
1436
+ schemaVersion: "OwlCodaRunKitCoreSuccessorProjectSuccessReceiptV1",
1437
+ receiptId,
1438
+ attemptNumber,
1439
+ priorAttemptReceiptSha256,
1440
+ workspaceRoot,
1441
+ fromCore: cloneJson(fromCore, "project_from_core"),
1442
+ toCore: cloneJson(plan.toCore, "to_core"),
1443
+ planSha256: plan.planSha256,
1444
+ ownerAuthoritySha256: ownerAuthority.authoritySha256,
1445
+ fleetManifestSha256: plan.fleetDiscovery.frozenManifestSha256,
1446
+ migrationStatus: migrationResult.status,
1447
+ migrationReceiptPath: migrationArtifact.relativePath,
1448
+ migrationReceiptSha256: migrationArtifact.sha256,
1449
+ postConfigSha256: configArtifact.sha256,
1450
+ repositoryActions: { ...REPOSITORY_ACTIONS },
1451
+ authorizationGranted: false,
1452
+ };
1453
+ const receipt = {
1454
+ ...body,
1455
+ receiptSha256: coreSuccessorArtifactSha256V1(body),
1456
+ };
1457
+ const receiptPath = projectSuccessReceiptPath(
1458
+ receiptBasePath,
1459
+ receiptId,
1460
+ workspaceRoot,
1461
+ );
1462
+ await writeCreateOnlyJson(
1463
+ root,
1464
+ receiptPath,
1465
+ receipt,
1466
+ "core_successor_project_success_receipt_exists",
1467
+ );
1468
+ return {
1469
+ workspaceRoot,
1470
+ receiptBasePath,
1471
+ receiptPath,
1472
+ receiptSha256: receipt.receiptSha256,
1473
+ };
1474
+ }
1475
+
1476
+ async function performCoreSuccessorAttempt({
1477
+ root,
1478
+ planPath,
1479
+ plan,
1480
+ ownerAuthority,
1481
+ receiptId,
1482
+ receiptBasePath,
1483
+ selectedReceiptPath,
1484
+ attemptNumber,
1485
+ priorAttemptReceiptPath,
1486
+ priorAttemptReceiptSha256,
1487
+ priorReceipt,
1488
+ adoptOrphanSuccessReceipts = false,
1489
+ dependencies,
1490
+ }) {
1491
+ return withFleetControlTransactions(
1492
+ plan.fleetDiscovery.workspaceRoots,
1493
+ async () => {
1494
+ const currentPlan = await readBoundedJson(root, planPath, "plan_path");
1495
+ validatePlan(currentPlan);
1496
+ if (currentPlan.planSha256 !== plan.planSha256) {
1497
+ throw new Error("core_successor_plan_drift");
1498
+ }
1499
+ validateOwnerAuthority(ownerAuthority, currentPlan);
1500
+ let trustedAuthority;
1501
+ try {
1502
+ trustedAuthority = await dependencies.verifyOwnerAuthority({
1503
+ authority: ownerAuthority,
1504
+ plan: currentPlan,
1505
+ });
1506
+ } catch {
1507
+ throw new Error("core_successor_owner_authority_untrusted");
1508
+ }
1509
+ assertTrustedAuthority(trustedAuthority, ownerAuthority);
1510
+
1511
+ if (priorReceipt) {
1512
+ const currentPrior = await readBoundedJson(
1513
+ root,
1514
+ priorAttemptReceiptPath,
1515
+ "prior_attempt_receipt_path",
1516
+ );
1517
+ validatePriorPartialReceipt({
1518
+ receipt: currentPrior,
1519
+ receiptPath: priorAttemptReceiptPath,
1520
+ plan: currentPlan,
1521
+ ownerAuthority,
1522
+ });
1523
+ if (currentPrior.receiptSha256 !== priorAttemptReceiptSha256) {
1524
+ throw new Error("core_successor_prior_attempt_receipt_drift");
1525
+ }
1526
+ await validatePriorAttemptLineage({
1527
+ root,
1528
+ receipt: currentPrior,
1529
+ receiptPath: priorAttemptReceiptPath,
1530
+ plan: currentPlan,
1531
+ ownerAuthority,
1532
+ });
1533
+ await assertNoResumeBranch({ root, priorReceipt: currentPrior });
1534
+ } else if (await pathExists(path.resolve(root, selectedReceiptPath))) {
1535
+ const existing = await readBoundedJson(
1536
+ root,
1537
+ selectedReceiptPath,
1538
+ "receipt_path",
1539
+ );
1540
+ if (
1541
+ existing.receiptId === receiptId
1542
+ && existing.planSha256 === currentPlan.planSha256
1543
+ && existing.ownerAuthoritySha256 === ownerAuthority.authoritySha256
1544
+ && existing.status === "partial_failure"
1545
+ ) {
1546
+ throw new Error("core_successor_resume_required");
1547
+ }
1548
+ throw new Error("core_successor_receipt_exists");
1549
+ }
1550
+ await prepareCreateOnlyPath(
1551
+ root,
1552
+ selectedReceiptPath,
1553
+ "core_successor_receipt_exists",
1554
+ );
1555
+
1556
+ const inheritedSuccessReferences = priorReceipt
1557
+ ? priorReceipt.projectSuccessReceipts
1558
+ : [];
1559
+ const projectSuccessByRoot = new Map();
1560
+ for (const reference of inheritedSuccessReferences) {
1561
+ const evidence = await validateProjectSuccessEvidence({
1562
+ root,
1563
+ reference,
1564
+ plan: currentPlan,
1565
+ ownerAuthority,
1566
+ receiptId,
1567
+ receiptBasePath,
1568
+ maximumAttemptNumber: attemptNumber - 1,
1569
+ });
1570
+ projectSuccessByRoot.set(reference.workspaceRoot, evidence);
1571
+ }
1572
+ if (!priorReceipt && !adoptOrphanSuccessReceipts) {
1573
+ for (const workspaceRoot of currentPlan.fleetDiscovery.workspaceRoots) {
1574
+ const receiptPath = projectSuccessReceiptPath(
1575
+ receiptBasePath,
1576
+ receiptId,
1577
+ workspaceRoot,
1578
+ );
1579
+ if (await pathExists(path.resolve(root, receiptPath))) {
1580
+ throw new Error("core_successor_orphan_resume_required");
1581
+ }
1582
+ }
1583
+ }
1584
+ const orphanSuccessEvidence = (
1585
+ priorReceipt || adoptOrphanSuccessReceipts
1586
+ )
1587
+ ? await discoverOrphanProjectSuccessEvidence({
1588
+ root,
1589
+ plan: currentPlan,
1590
+ ownerAuthority,
1591
+ receiptId,
1592
+ receiptBasePath,
1593
+ attemptNumber,
1594
+ priorAttemptReceiptSha256,
1595
+ existingReferences: inheritedSuccessReferences,
1596
+ })
1597
+ : [];
1598
+ const recoveredCommittedEvidence = (
1599
+ priorReceipt || adoptOrphanSuccessReceipts
1600
+ )
1601
+ ? await recoverCommittedProjectSuccessEvidence({
1602
+ root,
1603
+ plan: currentPlan,
1604
+ ownerAuthority,
1605
+ receiptId,
1606
+ receiptBasePath,
1607
+ attemptNumber,
1608
+ priorAttemptReceiptSha256,
1609
+ existingReferences: [
1610
+ ...inheritedSuccessReferences,
1611
+ ...orphanSuccessEvidence.map(row => row.reference),
1612
+ ],
1613
+ })
1614
+ : [];
1615
+ if (
1616
+ adoptOrphanSuccessReceipts
1617
+ && !priorReceipt
1618
+ && orphanSuccessEvidence.length === 0
1619
+ && recoveredCommittedEvidence.length === 0
1620
+ ) {
1621
+ throw new Error("core_successor_orphan_success_receipts_missing");
1622
+ }
1623
+ for (const { reference, evidence } of [
1624
+ ...orphanSuccessEvidence,
1625
+ ...recoveredCommittedEvidence,
1626
+ ]) {
1627
+ projectSuccessByRoot.set(reference.workspaceRoot, evidence);
1628
+ }
1629
+ const priorSuccessReferences = orderProjectSuccessReferences(
1630
+ currentPlan,
1631
+ [
1632
+ ...inheritedSuccessReferences,
1633
+ ...orphanSuccessEvidence.map(row => row.reference),
1634
+ ...recoveredCommittedEvidence.map(row => row.reference),
1635
+ ],
1636
+ );
1637
+ const orphanSuccessReceiptCount = (
1638
+ orphanSuccessEvidence.length + recoveredCommittedEvidence.length
1639
+ );
1640
+ const orphanRecovery = orphanSuccessReceiptCount > 0;
1641
+
1642
+ const runtimeGate = validateExecutionPin({
1643
+ expected: currentPlan.toCore,
1644
+ actual: await dependencies.runtimeCoreIdentity(),
1645
+ });
1646
+ if (runtimeGate.status !== "valid") {
1647
+ const projects = currentPlan.fleetDiscovery.workspaceRoots.map(
1648
+ workspaceRoot => ({
1649
+ workspaceRoot,
1650
+ fromCore: cloneJson(
1651
+ currentPlan.fleetCoreBindings.find(
1652
+ binding => binding.workspaceRoot === workspaceRoot,
1653
+ ).fromCore,
1654
+ "project_from_core",
1655
+ ),
1656
+ status: "core_drift",
1657
+ issueCodes: ["runtime_core_drift"],
1658
+ migrationAttempted: false,
1659
+ }),
1660
+ );
1661
+ return persistBatchReceipt({
1662
+ root,
1663
+ receiptPath: selectedReceiptPath,
1664
+ plan: currentPlan,
1665
+ ownerAuthority,
1666
+ trustedAuthority,
1667
+ receiptId,
1668
+ status: "blocked_preflight",
1669
+ projects,
1670
+ migrationAttempted: false,
1671
+ attemptNumber,
1672
+ receiptBasePath,
1673
+ priorAttemptReceiptPath,
1674
+ priorAttemptReceiptSha256,
1675
+ projectSuccessReceipts: priorSuccessReferences,
1676
+ orphanRecovery,
1677
+ orphanSuccessReceiptCount,
1678
+ });
1679
+ }
1680
+
1681
+ const candidateGate = await dependencies.verifySourceCandidate({
1682
+ workspaceRoot: root,
1683
+ candidatePath: currentPlan.sourceCandidate.candidatePath,
1684
+ });
1685
+ const candidateValid = candidateGate?.status === "valid"
1686
+ && candidateGate.authorizationGranted === false
1687
+ && candidateGate.candidateSha256
1688
+ === currentPlan.sourceCandidate.candidateSha256;
1689
+ const currentFleet = await dependencies.discoverFleet(
1690
+ cloneJson(
1691
+ currentPlan.fleetDiscoveryRequest,
1692
+ "fleet_discovery_request",
1693
+ ),
1694
+ );
1695
+ const fleetValid = discoveryMatchesPlan(
1696
+ currentFleet,
1697
+ currentPlan.fleetDiscovery,
1698
+ );
1699
+ if (!candidateValid || !fleetValid) {
1700
+ const issueCodes = [
1701
+ ...(!candidateValid ? ["source_candidate_drift"] : []),
1702
+ ...(!fleetValid ? ["fleet_manifest_drift_or_incomplete"] : []),
1703
+ ];
1704
+ const projects = currentPlan.fleetDiscovery.workspaceRoots.map(
1705
+ workspaceRoot => ({
1706
+ workspaceRoot,
1707
+ fromCore: cloneJson(
1708
+ currentPlan.fleetCoreBindings.find(
1709
+ binding => binding.workspaceRoot === workspaceRoot,
1710
+ ).fromCore,
1711
+ "project_from_core",
1712
+ ),
1713
+ status: "unsafe",
1714
+ issueCodes,
1715
+ migrationAttempted: false,
1716
+ }),
1717
+ );
1718
+ return persistBatchReceipt({
1719
+ root,
1720
+ receiptPath: selectedReceiptPath,
1721
+ plan: currentPlan,
1722
+ ownerAuthority,
1723
+ trustedAuthority,
1724
+ receiptId,
1725
+ status: "blocked_preflight",
1726
+ projects,
1727
+ migrationAttempted: false,
1728
+ attemptNumber,
1729
+ receiptBasePath,
1730
+ priorAttemptReceiptPath,
1731
+ priorAttemptReceiptSha256,
1732
+ projectSuccessReceipts: priorSuccessReferences,
1733
+ orphanRecovery,
1734
+ orphanSuccessReceiptCount,
1735
+ });
1736
+ }
1737
+
1738
+ const preflight = [];
1739
+ for (const workspaceRoot of currentFleet.workspaceRoots) {
1740
+ const binding = currentPlan.fleetCoreBindings.find(
1741
+ candidate => candidate.workspaceRoot === workspaceRoot,
1742
+ );
1743
+ const successEvidence = projectSuccessByRoot.get(workspaceRoot) ?? null;
1744
+ const row = await preflightWorkspace({
1745
+ workspaceRoot,
1746
+ fromCore: binding.fromCore,
1747
+ toCore: currentPlan.toCore,
1748
+ successEvidence,
1749
+ inspectControl: dependencies.inspectProjectControlState,
1750
+ });
1751
+ preflight.push(row);
1752
+ }
1753
+ if (preflight.some(row => !new Set([
1754
+ "safe",
1755
+ "already_migrated",
1756
+ ]).has(row.status))) {
1757
+ return persistBatchReceipt({
1758
+ root,
1759
+ receiptPath: selectedReceiptPath,
1760
+ plan: currentPlan,
1761
+ ownerAuthority,
1762
+ trustedAuthority,
1763
+ receiptId,
1764
+ status: "blocked_preflight",
1765
+ projects: preflight,
1766
+ migrationAttempted: false,
1767
+ attemptNumber,
1768
+ receiptBasePath,
1769
+ priorAttemptReceiptPath,
1770
+ priorAttemptReceiptSha256,
1771
+ projectSuccessReceipts: priorSuccessReferences,
1772
+ orphanRecovery,
1773
+ orphanSuccessReceiptCount,
1774
+ });
1775
+ }
1776
+
1777
+ const projects = [];
1778
+ const projectSuccessReceipts = [...priorSuccessReferences];
1779
+ let failed = false;
1780
+ let migrationAttempted = false;
1781
+ for (const row of preflight) {
1782
+ if (row.status === "already_migrated") {
1783
+ projects.push({
1784
+ ...row,
1785
+ status: "skipped_as_already_migrated",
1786
+ });
1787
+ continue;
1788
+ }
1789
+ if (failed) {
1790
+ projects.push({
1791
+ ...row,
1792
+ status: "not_attempted_after_failure",
1793
+ });
1794
+ continue;
1795
+ }
1796
+ migrationAttempted = true;
1797
+ try {
1798
+ const result = await dependencies.migrateWorkspace({
1799
+ workspaceRoot: row.workspaceRoot,
1800
+ fromCore: cloneJson(row.fromCore, "from_core"),
1801
+ toCore: cloneJson(currentPlan.toCore, "to_core"),
1802
+ planSha256: currentPlan.planSha256,
1803
+ ownerAuthoritySha256: ownerAuthority.authoritySha256,
1804
+ });
1805
+ const config = await readWorkspaceConfig(row.workspaceRoot);
1806
+ if (
1807
+ !new Set(["upgraded", "initialized"]).has(result?.status)
1808
+ || typeof result.migrationReceipt !== "string"
1809
+ || validateExecutionPin({
1810
+ expected: currentPlan.toCore,
1811
+ actual: result.core,
1812
+ }).status !== "valid"
1813
+ || validateExecutionPin({
1814
+ expected: currentPlan.toCore,
1815
+ actual: config.core,
1816
+ }).status !== "valid"
1817
+ ) {
1818
+ throw new Error("migration_result_invalid");
1819
+ }
1820
+ const successReference = await writeProjectSuccessReceipt({
1821
+ root,
1822
+ receiptBasePath,
1823
+ receiptId,
1824
+ attemptNumber,
1825
+ priorAttemptReceiptSha256,
1826
+ plan: currentPlan,
1827
+ ownerAuthority,
1828
+ workspaceRoot: row.workspaceRoot,
1829
+ fromCore: row.fromCore,
1830
+ migrationResult: result,
1831
+ });
1832
+ projectSuccessReceipts.push(successReference);
1833
+ projects.push({
1834
+ workspaceRoot: row.workspaceRoot,
1835
+ fromCore: cloneJson(row.fromCore, "project_from_core"),
1836
+ status: "migrated",
1837
+ issueCodes: [],
1838
+ migrationAttempted: true,
1839
+ migrationStatus: result.status,
1840
+ migrationReceipt: result.migrationReceipt,
1841
+ migrationReceiptSha256: (
1842
+ await readWorkspaceArtifact(
1843
+ row.workspaceRoot,
1844
+ result.migrationReceipt,
1845
+ "migration_receipt",
1846
+ )
1847
+ ).sha256,
1848
+ projectSuccessReceiptPath: successReference.receiptPath,
1849
+ projectSuccessReceiptSha256: successReference.receiptSha256,
1850
+ });
1851
+ } catch {
1852
+ failed = true;
1853
+ projects.push({
1854
+ workspaceRoot: row.workspaceRoot,
1855
+ fromCore: cloneJson(row.fromCore, "project_from_core"),
1856
+ status: "migration_failed",
1857
+ issueCodes: ["project_migration_failed"],
1858
+ migrationAttempted: true,
1859
+ });
1860
+ }
1861
+ }
1862
+ return persistBatchReceipt({
1863
+ root,
1864
+ receiptPath: selectedReceiptPath,
1865
+ plan: currentPlan,
1866
+ ownerAuthority,
1867
+ trustedAuthority,
1868
+ receiptId,
1869
+ status: failed ? "partial_failure" : "applied",
1870
+ projects,
1871
+ migrationAttempted,
1872
+ attemptNumber,
1873
+ receiptBasePath,
1874
+ priorAttemptReceiptPath,
1875
+ priorAttemptReceiptSha256,
1876
+ projectSuccessReceipts,
1877
+ orphanRecovery,
1878
+ orphanSuccessReceiptCount,
1879
+ });
1880
+ },
1881
+ );
1882
+ }
1883
+
1884
+ export async function applyCoreSuccessorPlanV1({
1885
+ controllerWorkspaceRoot,
1886
+ planPath,
1887
+ receiptId,
1888
+ receiptPath = null,
1889
+ ownerAuthority,
1890
+ dependencies: dependencyOverrides = {},
1891
+ } = {}) {
1892
+ assertIdentifier(receiptId, "receipt_id");
1893
+ const root = await canonicalWorkspaceRoot(controllerWorkspaceRoot);
1894
+ const plan = await readBoundedJson(root, planPath, "plan_path");
1895
+ validatePlan(plan);
1896
+ validateOwnerAuthority(ownerAuthority, plan);
1897
+ const receiptBasePath = receiptPath
1898
+ ?? `${path.dirname(safeRelativePath(planPath, "plan_path"))}`
1899
+ + `/apply-receipts/${receiptId}.json`;
1900
+ const selectedReceiptPath = safeRelativePath(
1901
+ receiptBasePath,
1902
+ "receipt_path",
1903
+ );
1904
+ if (await pathExists(path.resolve(root, selectedReceiptPath))) {
1905
+ const existing = await readBoundedJson(
1906
+ root,
1907
+ selectedReceiptPath,
1908
+ "receipt_path",
1909
+ );
1910
+ assertBatchReceiptSelfHash(existing);
1911
+ if (
1912
+ existing.receiptId === receiptId
1913
+ && existing.planSha256 === plan.planSha256
1914
+ && existing.ownerAuthoritySha256 === ownerAuthority.authoritySha256
1915
+ && existing.status === "partial_failure"
1916
+ ) {
1917
+ throw new Error("core_successor_resume_required");
1918
+ }
1919
+ throw new Error("core_successor_receipt_exists");
1920
+ }
1921
+ return performCoreSuccessorAttempt({
1922
+ root,
1923
+ planPath,
1924
+ plan,
1925
+ ownerAuthority,
1926
+ receiptId,
1927
+ receiptBasePath: selectedReceiptPath,
1928
+ selectedReceiptPath,
1929
+ attemptNumber: 1,
1930
+ priorAttemptReceiptPath: null,
1931
+ priorAttemptReceiptSha256: null,
1932
+ priorReceipt: null,
1933
+ dependencies: defaultDependencies(dependencyOverrides),
1934
+ });
1935
+ }
1936
+
1937
+ export async function resumeCoreSuccessorPlanV1({
1938
+ controllerWorkspaceRoot,
1939
+ planPath,
1940
+ fromReceiptPath = null,
1941
+ receiptId = null,
1942
+ receiptPath = null,
1943
+ adoptOrphanSuccessReceipts = false,
1944
+ ownerAuthority,
1945
+ dependencies: dependencyOverrides = {},
1946
+ } = {}) {
1947
+ const root = await canonicalWorkspaceRoot(controllerWorkspaceRoot);
1948
+ const plan = await readBoundedJson(root, planPath, "plan_path");
1949
+ validatePlan(plan);
1950
+ validateOwnerAuthority(ownerAuthority, plan);
1951
+ if (fromReceiptPath === null) {
1952
+ if (!adoptOrphanSuccessReceipts) {
1953
+ throw new Error("core_successor_prior_attempt_receipt_required");
1954
+ }
1955
+ assertIdentifier(receiptId, "receipt_id");
1956
+ const receiptBasePath = receiptPath
1957
+ ?? `${path.dirname(safeRelativePath(planPath, "plan_path"))}`
1958
+ + `/apply-receipts/${receiptId}.json`;
1959
+ const selectedReceiptPath = safeRelativePath(
1960
+ receiptBasePath,
1961
+ "receipt_path",
1962
+ );
1963
+ if (await pathExists(path.resolve(root, selectedReceiptPath))) {
1964
+ const existing = await readBoundedJson(
1965
+ root,
1966
+ selectedReceiptPath,
1967
+ "receipt_path",
1968
+ );
1969
+ assertBatchReceiptSelfHash(existing);
1970
+ if (
1971
+ existing.receiptId === receiptId
1972
+ && existing.planSha256 === plan.planSha256
1973
+ && existing.ownerAuthoritySha256 === ownerAuthority.authoritySha256
1974
+ && existing.status === "partial_failure"
1975
+ ) {
1976
+ throw new Error("core_successor_resume_from_receipt_required");
1977
+ }
1978
+ throw new Error("core_successor_receipt_exists");
1979
+ }
1980
+ return performCoreSuccessorAttempt({
1981
+ root,
1982
+ planPath,
1983
+ plan,
1984
+ ownerAuthority,
1985
+ receiptId,
1986
+ receiptBasePath: selectedReceiptPath,
1987
+ selectedReceiptPath,
1988
+ attemptNumber: 1,
1989
+ priorAttemptReceiptPath: null,
1990
+ priorAttemptReceiptSha256: null,
1991
+ priorReceipt: null,
1992
+ adoptOrphanSuccessReceipts: true,
1993
+ dependencies: defaultDependencies(dependencyOverrides),
1994
+ });
1995
+ }
1996
+ if (adoptOrphanSuccessReceipts || receiptId !== null || receiptPath !== null) {
1997
+ throw new Error("core_successor_resume_mode_ambiguous");
1998
+ }
1999
+ const selectedPriorPath = safeRelativePath(
2000
+ fromReceiptPath,
2001
+ "prior_attempt_receipt_path",
2002
+ );
2003
+ const priorReceipt = await readBoundedJson(
2004
+ root,
2005
+ selectedPriorPath,
2006
+ "prior_attempt_receipt_path",
2007
+ );
2008
+ validatePriorPartialReceipt({
2009
+ receipt: priorReceipt,
2010
+ receiptPath: selectedPriorPath,
2011
+ plan,
2012
+ ownerAuthority,
2013
+ });
2014
+ await validatePriorAttemptLineage({
2015
+ root,
2016
+ receipt: priorReceipt,
2017
+ receiptPath: selectedPriorPath,
2018
+ plan,
2019
+ ownerAuthority,
2020
+ });
2021
+ assertIdentifier(priorReceipt.receiptId, "receipt_id");
2022
+ await assertNoResumeBranch({ root, priorReceipt });
2023
+ const attemptNumber = priorReceipt.attemptNumber + 1;
2024
+ const selectedReceiptPath = receiptAttemptPath(
2025
+ priorReceipt.receiptBasePath,
2026
+ attemptNumber,
2027
+ );
2028
+ return performCoreSuccessorAttempt({
2029
+ root,
2030
+ planPath,
2031
+ plan,
2032
+ ownerAuthority,
2033
+ receiptId: priorReceipt.receiptId,
2034
+ receiptBasePath: priorReceipt.receiptBasePath,
2035
+ selectedReceiptPath,
2036
+ attemptNumber,
2037
+ priorAttemptReceiptPath: selectedPriorPath,
2038
+ priorAttemptReceiptSha256: priorReceipt.receiptSha256,
2039
+ priorReceipt,
2040
+ adoptOrphanSuccessReceipts: false,
2041
+ dependencies: defaultDependencies(dependencyOverrides),
2042
+ });
2043
+ }