owlrunkit 0.15.1 → 0.16.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 (76) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +284 -66
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +235 -116
  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 +203 -206
  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 +1198 -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 +704 -0
  28. package/scripts/runkit-contract/project-control-parser.mjs +889 -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 +1155 -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 +167 -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 +62 -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 +166 -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
@@ -4,33 +4,45 @@ import {
4
4
  lstatSync,
5
5
  mkdirSync,
6
6
  readFileSync,
7
- readdirSync,
8
7
  realpathSync,
9
8
  renameSync,
10
9
  rmSync,
11
10
  writeFileSync,
12
11
  } from "node:fs";
13
- import { isAbsolute, relative, resolve, win32 } from "node:path";
12
+ import { isAbsolute, relative, resolve, sep, win32 } from "node:path";
14
13
  import { fileURLToPath } from "node:url";
14
+ import { parseProjectControlState } from "./project-control-parser.mjs";
15
15
 
16
16
  export const CONTRACT_VERSION = "0.2";
17
- export const CORE_VERSION = "0.15.1";
17
+ export const CORE_VERSION = "0.16.1";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
21
21
  "acceptance-evidence.mjs",
22
+ "assurance-router.mjs",
23
+ "core-successor.mjs",
22
24
  "coverage-adoption.mjs",
23
25
  "core-contract.mjs",
24
26
  "delivery-create.mjs",
25
27
  "delivery-selection.mjs",
28
+ "deployment-workflow.mjs",
29
+ "fleet-discovery.mjs",
26
30
  "finalize.mjs",
31
+ "formal-workflow.mjs",
32
+ "human-status.mjs",
27
33
  "inspect-presentation.mjs",
34
+ "install-codex-skill.mjs",
28
35
  "lease-lifecycle.mjs",
29
36
  "lifecycle-orchestration.mjs",
30
37
  "offline-store.mjs",
31
38
  "onboarding-doctor.mjs",
39
+ "owner-authority-trust.mjs",
40
+ "owner-deployment-decision.mjs",
32
41
  "profile-impact.mjs",
33
42
  "profile-onboarding.mjs",
43
+ "project-cli-resolver.mjs",
44
+ "project-control-parser.mjs",
45
+ "project-control-state.mjs",
34
46
  "provenance-common.mjs",
35
47
  "quick-attest.mjs",
36
48
  "quick-canonical.mjs",
@@ -45,24 +57,53 @@ const CORE_FILES = [
45
57
  "repair-execution.mjs",
46
58
  "resume-execution.mjs",
47
59
  "resource-preflight.mjs",
60
+ "remote-deployment.mjs",
61
+ "remote-process-adapter.mjs",
62
+ "ssh-remote-adapter.mjs",
63
+ "runkit-bootstrap.mjs",
48
64
  "runkit-cli.mjs",
49
65
  "snapshot.mjs",
66
+ "source-candidate.mjs",
50
67
  "source-fingerprint.mjs",
68
+ "sync-core016-release-identity.mjs",
51
69
  "verification-receipt-gate.mjs",
52
70
  "verification-context.mjs",
53
71
  "verification-plan.mjs",
54
72
  "visual-smoke.mjs",
73
+ "schemas/assurance-route-v1.schema.json",
55
74
  "schemas/closeout-receipt-v2.schema.json",
56
75
  "schemas/adoption-evidence-v1.schema.json",
57
76
  "schemas/coverage-adopt-request-v1.schema.json",
58
77
  "schemas/core-artifact.schema.json",
59
78
  "schemas/core-artifact-v2.schema.json",
79
+ "schemas/core-successor-batch-receipt-v1.schema.json",
80
+ "schemas/core-successor-plan-v1.schema.json",
81
+ "schemas/core-successor-project-success-receipt-v1.schema.json",
82
+ "schemas/deployment-execute-request-v1.schema.json",
83
+ "schemas/deployment-lineage-v1.schema.json",
84
+ "schemas/deployment-prepare-receipt-v1.schema.json",
60
85
  "schemas/engine-pin.schema.json",
61
86
  "schemas/engine-pin-v2.schema.json",
62
87
  "schemas/evidence-coverage-index-v1.schema.json",
88
+ "schemas/fleet-discovery-v1.schema.json",
89
+ "schemas/fleet-manifest-v1.schema.json",
90
+ "schemas/fleet-root-registry-v1.schema.json",
91
+ "schemas/human-status-v1.schema.json",
92
+ "schemas/owner-authority-trust-v1.schema.json",
93
+ "schemas/owner-migration-authority-v1.schema.json",
94
+ "schemas/owner-migration-authority-v2.schema.json",
95
+ "schemas/owner-deployment-authority-v1.schema.json",
96
+ "schemas/owner-deployment-authority-v2.schema.json",
97
+ "schemas/owner-deployment-decision-v1.schema.json",
98
+ "schemas/profile-apply-receipt-v1.schema.json",
99
+ "schemas/profile-detection-v2.schema.json",
63
100
  "schemas/project-config.schema.json",
64
101
  "schemas/project-config-v2.schema.json",
65
102
  "schemas/profiles.schema.json",
103
+ "schemas/remote-deployment-manifest-v1.schema.json",
104
+ "schemas/remote-deployment-result-v1.schema.json",
105
+ "schemas/remote-deployment-stage-journal-v1.schema.json",
106
+ "schemas/remote-target-v1.schema.json",
66
107
  "schemas/resume-attempt-v1.schema.json",
67
108
  "schemas/resume-request-v1.schema.json",
68
109
  "schemas/resource-preflight-request-v1.schema.json",
@@ -70,7 +111,9 @@ const CORE_FILES = [
70
111
  "schemas/replayable-evidence-v1.schema.json",
71
112
  "schemas/finalize-request-v1.schema.json",
72
113
  "schemas/ready-for-commit-v1.schema.json",
114
+ "schemas/ready-for-commit-v2.schema.json",
73
115
  "schemas/snapshot-v1.schema.json",
116
+ "schemas/source-candidate-v2.schema.json",
74
117
  "schemas/visual-smoke-request-v1.schema.json",
75
118
  "schemas/visual-smoke-result-v1.schema.json",
76
119
  "schemas/verification-context-v1.schema.json",
@@ -80,11 +123,20 @@ const CORE_FILES = [
80
123
  "schemas/verification-receipt-v2.schema.json",
81
124
  "schemas/verify-plan-request-v1.schema.json",
82
125
  "templates/goal-contract.json",
126
+ "templates/assurance-request.json",
127
+ "templates/core-successor-owner-authority.json",
128
+ "templates/owner-authority-trust.json",
83
129
  "templates/coverage-adopt-request.json",
130
+ "templates/deployment-execute-request.json",
131
+ "templates/deployment-owner-authority.json",
132
+ "templates/deployment-owner-decision.json",
133
+ "templates/fleet-manifest.json",
134
+ "templates/fleet-root-registry.json",
84
135
  "templates/finalize-request.json",
85
136
  "templates/ready-for-commit-request.json",
86
137
  "templates/resume-request.json",
87
138
  "templates/resource-preflight-request.json",
139
+ "templates/remote-deployment-manifest.json",
88
140
  "templates/snapshot-request.json",
89
141
  "templates/visual-smoke-request.json",
90
142
  "templates/verify-plan-request.json",
@@ -125,6 +177,80 @@ function atomicWriteJson(filePath, value) {
125
177
  renameSync(temporaryPath, filePath);
126
178
  }
127
179
 
180
+ function pathEntryExists(filePath) {
181
+ try {
182
+ lstatSync(filePath);
183
+ return true;
184
+ } catch (error) {
185
+ if (error?.code === "ENOENT") return false;
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ function canonicalWorkspaceRoot(workspaceRoot) {
191
+ const requested = resolve(workspaceRoot);
192
+ const stat = lstatSync(requested);
193
+ if (
194
+ stat.isSymbolicLink()
195
+ || !stat.isDirectory()
196
+ ) {
197
+ throw new Error("workspaceRoot must be a real directory.");
198
+ }
199
+ return realpathSync(requested);
200
+ }
201
+
202
+ function ensureRealDirectory(root, target, label) {
203
+ const remainder = relative(root, target);
204
+ if (
205
+ remainder === ""
206
+ || remainder === ".."
207
+ || remainder.startsWith(`..${sep}`)
208
+ || isAbsolute(remainder)
209
+ ) {
210
+ throw new Error(`${label} escapes the workspace.`);
211
+ }
212
+ let current = root;
213
+ for (const segment of remainder.split(/[\\/]/u)) {
214
+ current = resolve(current, segment);
215
+ if (!pathEntryExists(current)) {
216
+ try {
217
+ mkdirSync(current);
218
+ } catch (error) {
219
+ if (error?.code !== "EEXIST") throw error;
220
+ }
221
+ }
222
+ const stat = lstatSync(current);
223
+ if (
224
+ stat.isSymbolicLink()
225
+ || !stat.isDirectory()
226
+ || realpathSync(current) !== current
227
+ ) {
228
+ throw new Error(`${label} must be a real directory without symlink ancestors.`);
229
+ }
230
+ }
231
+ return target;
232
+ }
233
+
234
+ function assertRegularRuntimeFile(root, filePath, label) {
235
+ const remainder = relative(root, filePath);
236
+ if (
237
+ remainder === ""
238
+ || remainder === ".."
239
+ || remainder.startsWith(`..${sep}`)
240
+ || isAbsolute(remainder)
241
+ ) {
242
+ throw new Error(`${label} escapes the workspace.`);
243
+ }
244
+ const stat = lstatSync(filePath);
245
+ if (
246
+ stat.isSymbolicLink()
247
+ || !stat.isFile()
248
+ || realpathSync(filePath) !== filePath
249
+ ) {
250
+ throw new Error(`${label} must be a regular file without symlink ancestors.`);
251
+ }
252
+ }
253
+
128
254
  function projectConfig(core = currentCoreIdentity()) {
129
255
  return {
130
256
  schemaVersion: "OwlCodaRunKitConfigV2",
@@ -169,7 +295,11 @@ function migrateProjectConfig({ runtimeRoot, configPath, config, migration }) {
169
295
  authorizationGranted: false,
170
296
  };
171
297
  const receiptsRoot = resolve(runtimeRoot, "config-migration-receipts");
172
- mkdirSync(receiptsRoot, { recursive: true });
298
+ ensureRealDirectory(
299
+ runtimeRoot,
300
+ receiptsRoot,
301
+ "Config migration receipt directory",
302
+ );
173
303
  const receiptName = writeMigrationReceiptAppendOnly(
174
304
  receiptsRoot,
175
305
  migrationReceiptName(migration, config.core, next.core),
@@ -183,17 +313,17 @@ function coreDirectory() {
183
313
  return resolve(fileURLToPath(new URL(".", import.meta.url)));
184
314
  }
185
315
 
186
- function coreManifestStream() {
187
- const coreRoot = coreDirectory();
188
- const projectRoot = resolve(coreRoot, "../..");
316
+ function coreManifestStreamFromRoots(coreRoot, projectRoot) {
189
317
  return [
190
318
  ...CORE_FILES.map((name) => `${name}\tsha256:${sha256(readFileSync(resolve(coreRoot, name)))}\n`),
191
319
  ...CORE_DEPENDENCY_FILES.map((name) => `${name}\tsha256:${sha256(readFileSync(resolve(projectRoot, name)))}\n`),
192
320
  ].join("");
193
321
  }
194
322
 
195
- export function currentCoreIdentity() {
196
- const manifestSha256 = sha256(coreManifestStream());
323
+ export function coreIdentityFromSourceRoot(sourceRoot) {
324
+ const projectRoot = realpathSync(sourceRoot);
325
+ const coreRoot = resolve(projectRoot, "scripts/runkit-contract");
326
+ const manifestSha256 = sha256(coreManifestStreamFromRoots(coreRoot, projectRoot));
197
327
  return {
198
328
  contractVersion: CONTRACT_VERSION,
199
329
  coreVersion: CORE_VERSION,
@@ -202,6 +332,10 @@ export function currentCoreIdentity() {
202
332
  };
203
333
  }
204
334
 
335
+ export function currentCoreIdentity() {
336
+ return coreIdentityFromSourceRoot(resolve(coreDirectory(), "../.."));
337
+ }
338
+
205
339
  export function isDirectExecution(moduleUrl, argvPath = process.argv[1]) {
206
340
  if (!argvPath) return false;
207
341
  try {
@@ -303,193 +437,22 @@ export function validateProjectConfigV2(config) {
303
437
  }
304
438
  }
305
439
 
306
- function inspectTrustedCloseout(executionRoot, runId) {
307
- const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
308
- try {
309
- const closeoutStat = lstatSync(closeoutPath);
310
- if (closeoutStat.isSymbolicLink() || !closeoutStat.isFile()) {
311
- return {
312
- trusted: false,
313
- decision: null,
314
- issues: [`Closeout receipt must be a regular file: ${runId}`],
315
- };
316
- }
317
- const closeout = JSON.parse(readFileSync(closeoutPath, "utf8"));
318
- if (closeout?.artifact?.payload?.runId !== runId) {
319
- return {
320
- trusted: false,
321
- decision: null,
322
- issues: [`Closeout receipt runId mismatch: ${runId}`],
323
- };
324
- }
325
- const artifactGate = validateCoreArtifact(closeout.artifact);
326
- if (!artifactGate.valid) {
327
- return {
328
- trusted: false,
329
- decision: null,
330
- issues: artifactGate.issues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
331
- };
332
- }
333
- if (closeout.acceptanceSha256 !== artifactGate.acceptanceSha256
334
- || closeout.artifactSha256 !== artifactGate.artifactSha256) {
335
- return {
336
- trusted: false,
337
- decision: null,
338
- issues: [`Closeout receipt hash mismatch: ${runId}`],
339
- };
340
- }
341
- const payload = closeout.artifact.payload;
342
- if (!new Set(["accepted", "rejected", "blocked"]).has(payload.decision)
343
- || payload.authorizationGranted !== false) {
344
- return {
345
- trusted: false,
346
- decision: null,
347
- issues: [`Closeout receipt decision or authorization is invalid: ${runId}`],
348
- };
349
- }
350
- const expectedPayloadKeys = payload.decision === "accepted"
351
- ? ["authorizationGranted", "decision", "runId", "verification"]
352
- : ["authorizationGranted", "decision", "runId"];
353
- const unsupportedPayloadKeys = Object.keys(payload)
354
- .filter(key => !expectedPayloadKeys.includes(key));
355
- if (unsupportedPayloadKeys.length > 0) {
356
- return {
357
- trusted: false,
358
- decision: null,
359
- issues: [
360
- `Closeout receipt payload contains unsupported field: ${runId}: ${unsupportedPayloadKeys[0]}`,
361
- ],
362
- };
363
- }
364
- const verificationIssues = acceptedCloseoutVerificationIssues(closeout.artifact);
365
- if (verificationIssues.length > 0) {
366
- return {
367
- trusted: false,
368
- decision: null,
369
- issues: verificationIssues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
370
- };
371
- }
372
- const pinPath = resolve(executionRoot, "engine-pin.json");
373
- const pinStat = lstatSync(pinPath);
374
- if (pinStat.isSymbolicLink() || !pinStat.isFile()) {
375
- return {
376
- trusted: false,
377
- decision: null,
378
- issues: [`Execution engine pin must be a regular file: ${runId}`],
379
- };
380
- }
381
- const pinGate = validateExecutionPin({
382
- expected: JSON.parse(readFileSync(pinPath, "utf8")),
383
- actual: closeout.artifact.core,
384
- });
385
- if (pinGate.status !== "valid") {
386
- return {
387
- trusted: false,
388
- decision: null,
389
- issues: pinGate.issues.map(issue => `Closeout engine pin mismatch: ${runId}: ${issue}`),
390
- };
391
- }
392
- return {
393
- trusted: true,
394
- decision: payload.decision,
395
- issues: [],
396
- };
397
- } catch (error) {
398
- return {
399
- trusted: false,
400
- decision: null,
401
- issues: [
402
- `Closeout receipt or engine pin is invalid: ${runId}: ${
403
- error instanceof Error ? error.message : String(error)
404
- }`,
405
- ],
406
- };
407
- }
440
+ function projectControlParserContract() {
441
+ return {
442
+ runtimeRoot: RUNTIME_ROOT,
443
+ acceptedCloseoutVerificationIssues,
444
+ validateCoreArtifact,
445
+ validateExecutionPin,
446
+ validateLeaseOwnedPaths,
447
+ };
408
448
  }
409
449
 
410
450
  export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
411
- if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
412
- throw new Error("workspaceRoot is required.");
413
- }
414
- const root = realpathSync(workspaceRoot);
415
- const executionsRoot = resolve(root, RUNTIME_ROOT, "executions");
416
- if (!existsSync(executionsRoot)) {
417
- return {
418
- status: "safe",
419
- activeRunIds: [],
420
- activeLeaseIds: [],
421
- issues: [],
422
- };
423
- }
424
- const executionsStat = lstatSync(executionsRoot);
425
- if (executionsStat.isSymbolicLink() || !executionsStat.isDirectory()) {
426
- return {
427
- status: "unsafe",
428
- activeRunIds: [],
429
- activeLeaseIds: [],
430
- issues: ["RunKit executions root must be a regular directory."],
431
- };
432
- }
433
- const activeRunIds = [];
434
- const activeLeaseIds = [];
435
- const issues = [];
436
- const entries = readdirSync(executionsRoot, { withFileTypes: true })
437
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
438
- for (const entry of entries) {
439
- if (entry.isSymbolicLink()) {
440
- issues.push(`Execution directory must not be a symlink: ${entry.name}`);
441
- continue;
442
- }
443
- if (!entry.isDirectory()) continue;
444
- const executionRoot = resolve(executionsRoot, entry.name);
445
- const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
446
- let skipHistoricalLeases = false;
447
- if (!existsSync(closeoutPath)) {
448
- activeRunIds.push(entry.name);
449
- } else {
450
- const closeout = inspectTrustedCloseout(executionRoot, entry.name);
451
- issues.push(...closeout.issues);
452
- skipHistoricalLeases = closeout.trusted
453
- && new Set(["rejected", "blocked"]).has(closeout.decision);
454
- }
455
- if (skipHistoricalLeases) continue;
456
- const leasesRoot = resolve(executionRoot, "leases");
457
- if (!existsSync(leasesRoot)) continue;
458
- const leasesStat = lstatSync(leasesRoot);
459
- if (leasesStat.isSymbolicLink() || !leasesStat.isDirectory()) {
460
- issues.push(`Lease directory must be a regular directory: ${entry.name}`);
461
- continue;
462
- }
463
- const leaseEntries = readdirSync(leasesRoot, { withFileTypes: true })
464
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
465
- for (const leaseEntry of leaseEntries) {
466
- if (!leaseEntry.name.endsWith(".json")) continue;
467
- if (leaseEntry.isSymbolicLink() || !leaseEntry.isFile()) {
468
- issues.push(`Lease artifact must be a regular file: ${entry.name}/${leaseEntry.name}`);
469
- continue;
470
- }
471
- try {
472
- const lease = JSON.parse(readFileSync(resolve(leasesRoot, leaseEntry.name), "utf8"));
473
- if (lease.state === "active") {
474
- activeLeaseIds.push(`${entry.name}:${lease.workItemId ?? leaseEntry.name}`);
475
- } else if (lease.state !== "released") {
476
- issues.push(`Lease state is invalid: ${entry.name}/${leaseEntry.name}`);
477
- }
478
- } catch {
479
- issues.push(`Lease artifact is invalid JSON: ${entry.name}/${leaseEntry.name}`);
480
- }
481
- }
482
- }
483
- return {
484
- status: activeRunIds.length === 0
485
- && activeLeaseIds.length === 0
486
- && issues.length === 0
487
- ? "safe"
488
- : "unsafe",
489
- activeRunIds,
490
- activeLeaseIds,
491
- issues,
492
- };
451
+ return parseProjectControlState({
452
+ workspaceRoot,
453
+ currentCore: currentCoreIdentity(),
454
+ contract: projectControlParserContract(),
455
+ }).upgradeSafety;
493
456
  }
494
457
 
495
458
  function validateProducer(producer) {
@@ -567,12 +530,26 @@ export function validateCoreArtifact(artifact) {
567
530
  export function acceptedCloseoutVerificationIssues(artifact) {
568
531
  if (artifact?.core?.contractVersion !== "0.2" || artifact?.payload?.decision !== "accepted") return [];
569
532
  const verification = artifact.payload.verification;
533
+ const sourceArtifactValid = verification?.sourceArtifact === undefined
534
+ || (
535
+ isRecord(verification.sourceArtifact)
536
+ && new Set(["delivery_packet_v1", "source_candidate_v2"])
537
+ .has(verification.sourceArtifact.kind)
538
+ && verification.sourceArtifact.runId === artifact.payload.runId
539
+ && typeof verification.sourceArtifact.path === "string"
540
+ && verification.sourceArtifact.path.length > 0
541
+ && typeof verification.sourceArtifact.sha256 === "string"
542
+ && /^[a-f0-9]{64}$/.test(verification.sourceArtifact.sha256)
543
+ && verification.sourceArtifact.sourceFingerprint
544
+ === verification.sourceFingerprint
545
+ );
570
546
  const validFields = [
571
547
  verification?.contractVersion === "0.2",
572
548
  verification?.gateDecision === "accepted_passed",
573
549
  typeof verification?.gateInputSha256 === "string" && /^[a-f0-9]{64}$/.test(verification.gateInputSha256),
574
550
  typeof verification?.activeReceiptSha256 === "string" && /^[a-f0-9]{64}$/.test(verification.activeReceiptSha256),
575
551
  typeof verification?.sourceFingerprint === "string" && /^[a-f0-9]{64}$/.test(verification.sourceFingerprint),
552
+ sourceArtifactValid,
576
553
  typeof verification?.verificationContextFingerprint === "string" && /^[a-f0-9]{64}$/.test(verification.verificationContextFingerprint),
577
554
  verification?.leaseState === "released",
578
555
  Array.isArray(verification?.selectedProfileIds)
@@ -588,15 +565,27 @@ export function acceptedCloseoutVerificationIssues(artifact) {
588
565
 
589
566
  export async function initializeProjectRunKit({ workspaceRoot } = {}) {
590
567
  if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) throw new Error("workspaceRoot is required.");
591
- const root = realpathSync(workspaceRoot);
592
- if (existsSync(resolve(root, ".owlrunkit"))) throw new Error("Legacy .owlrunkit exists; explicit migration is required before initialization.");
568
+ const root = canonicalWorkspaceRoot(workspaceRoot);
569
+ if (pathEntryExists(resolve(root, ".owlrunkit"))) throw new Error("Legacy .owlrunkit exists; explicit migration is required before initialization.");
593
570
  const runtimeRoot = resolve(root, RUNTIME_ROOT);
594
- const remainder = relative(root, runtimeRoot);
595
- if (remainder.startsWith("..") || isAbsolute(remainder)) throw new Error("Runtime root escapes the workspace.");
571
+ ensureRealDirectory(root, resolve(root, ".owlcoda"), "OwlCoda control directory");
572
+ ensureRealDirectory(root, runtimeRoot, "RunKit runtime directory");
573
+ ensureRealDirectory(
574
+ runtimeRoot,
575
+ resolve(runtimeRoot, "executions"),
576
+ "RunKit executions directory",
577
+ );
596
578
  const configPath = resolve(runtimeRoot, "config.json");
597
579
  const profilesPath = resolve(runtimeRoot, "profiles.json");
598
- if (!existsSync(configPath)) {
599
- mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
580
+ const configExists = pathEntryExists(configPath);
581
+ const profilesExist = pathEntryExists(profilesPath);
582
+ if (configExists) {
583
+ assertRegularRuntimeFile(runtimeRoot, configPath, "Project config");
584
+ }
585
+ if (profilesExist) {
586
+ assertRegularRuntimeFile(runtimeRoot, profilesPath, "Project profiles");
587
+ }
588
+ if (!configExists) {
600
589
  atomicWriteJson(configPath, projectConfig());
601
590
  } else {
602
591
  const config = JSON.parse(readFileSync(configPath, "utf8"));
@@ -624,8 +613,11 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
624
613
  config,
625
614
  migration: "config-v01-to-v02",
626
615
  });
627
- if (!existsSync(profilesPath)) {
628
- writeFileSync(profilesPath, `${JSON.stringify({ schemaVersion: "OwlCodaRunKitProfilesV1", profiles: [] }, null, 2)}\n`);
616
+ if (!profilesExist) {
617
+ atomicWriteJson(profilesPath, {
618
+ schemaVersion: "OwlCodaRunKitProfilesV1",
619
+ profiles: [],
620
+ });
629
621
  }
630
622
  return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
631
623
  } else if (config.schemaVersion === "OwlCodaRunKitConfigV2" && config.core?.contractVersion === CONTRACT_VERSION) {
@@ -645,17 +637,22 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
645
637
  config,
646
638
  migration: "config-v02-core-refresh",
647
639
  });
648
- if (!existsSync(profilesPath)) {
649
- writeFileSync(profilesPath, `${JSON.stringify({ schemaVersion: "OwlCodaRunKitProfilesV1", profiles: [] }, null, 2)}\n`);
640
+ if (!profilesExist) {
641
+ atomicWriteJson(profilesPath, {
642
+ schemaVersion: "OwlCodaRunKitProfilesV1",
643
+ profiles: [],
644
+ });
650
645
  }
651
646
  return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
652
647
  } else {
653
648
  throw new Error(`Unsupported project config migration: ${config.schemaVersion ?? "missing schemaVersion"}.`);
654
649
  }
655
650
  }
656
- mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
657
- if (!existsSync(profilesPath)) {
658
- writeFileSync(profilesPath, `${JSON.stringify({ schemaVersion: "OwlCodaRunKitProfilesV1", profiles: [] }, null, 2)}\n`);
651
+ if (!profilesExist) {
652
+ atomicWriteJson(profilesPath, {
653
+ schemaVersion: "OwlCodaRunKitProfilesV1",
654
+ profiles: [],
655
+ });
659
656
  }
660
657
  return { status: "initialized", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: currentCoreIdentity() };
661
658
  }