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
@@ -52,12 +52,29 @@ import {
52
52
  restoreLeaseWithinControlTransaction,
53
53
  withControlTransaction,
54
54
  } from "./lease-lifecycle.mjs";
55
- import { writeJsonExclusiveAtomically } from "./provenance-common.mjs";
55
+ import {
56
+ relativeToWorkspace,
57
+ safeRelativePath,
58
+ writeJsonExclusiveAtomically,
59
+ } from "./provenance-common.mjs";
56
60
  import { createDeliveryFromLease } from "./delivery-create.mjs";
57
61
  import {
58
62
  activeAcceptedGate,
59
63
  runHighLevelVerify,
60
64
  } from "./lifecycle-orchestration.mjs";
65
+ import {
66
+ finalizeFormalChecksV1,
67
+ recordFormalCheckV1,
68
+ resumeCompletedFormalFinishV1,
69
+ validateLegacyVerificationSideEffectsV1,
70
+ } from "./formal-workflow.mjs";
71
+ import {
72
+ freezeSourceCandidateV1,
73
+ freezeSourceCandidateV2,
74
+ materializeSourceCandidateV2,
75
+ verifySourceCandidateV1,
76
+ verifySourceCandidateV2,
77
+ } from "./source-candidate.mjs";
61
78
  import { buildInspectSummary, formatInspectHuman } from "./inspect-presentation.mjs";
62
79
  import { attestQuickReceiptDetails } from "./quick-attest.mjs";
63
80
  import { readLocalQuickMetrics } from "./quick-metrics.mjs";
@@ -68,10 +85,42 @@ import {
68
85
  importOfflineReceipt,
69
86
  } from "./offline-store.mjs";
70
87
  import {
88
+ applyDetectedProfilesV2,
71
89
  detectProfiles,
90
+ detectProfilesV2,
72
91
  resolveProfilesImpact,
73
92
  validateProfiles,
74
93
  } from "./profile-onboarding.mjs";
94
+ import {
95
+ discoverFleet,
96
+ registerFleetCoverageRoot,
97
+ } from "./fleet-discovery.mjs";
98
+ import { routeRunKitAssuranceV1 } from "./assurance-router.mjs";
99
+ import { deriveHumanStatusFromInspectV1 } from "./human-status.mjs";
100
+ import {
101
+ closeDeploymentExecuteChildRun,
102
+ createDeploymentExecuteChildRun,
103
+ createDeploymentExecuteLineageFromActiveRun,
104
+ createDeploymentPrepareReceipt,
105
+ createDeploymentPrepareReceiptFromClosedRun,
106
+ inspectDeploymentOwnerDecisionStateV1,
107
+ } from "./deployment-workflow.mjs";
108
+ import {
109
+ createRemoteDeploymentStageJournalV1,
110
+ executeRemoteDeployment,
111
+ validateRemoteDeploymentManifest,
112
+ } from "./remote-deployment.mjs";
113
+ import { createRemoteProcessAdapterV1 } from "./remote-process-adapter.mjs";
114
+ import {
115
+ builtInSshRemoteAdapterIdentityV1,
116
+ createSshRemoteAdapterV1,
117
+ } from "./ssh-remote-adapter.mjs";
118
+ import { inspectProjectControlState } from "./project-control-state.mjs";
119
+ import {
120
+ applyCoreSuccessorPlanV1,
121
+ createCoreSuccessorPlanFromFleetV1,
122
+ resumeCoreSuccessorPlanV1,
123
+ } from "./core-successor.mjs";
75
124
  import {
76
125
  formatDoctorHuman,
77
126
  readAdoptionReadiness,
@@ -92,6 +141,8 @@ Usage:
92
141
  Start here:
93
142
  owlrunkit doctor --workspace .
94
143
  owlrunkit profiles detect --workspace . --dry-run
144
+ owlrunkit profiles detect --workspace . --apply
145
+ owlrunkit assurance route --workspace . --request <risk-facts.json>
95
146
  owlrunkit adopt --workspace . --exact owlrunkit@${CORE_VERSION}
96
147
 
97
148
  Quick Verification
@@ -99,11 +150,25 @@ Quick Verification
99
150
  Low-risk, source-bound reusable evidence without writer leases or acceptance.
100
151
 
101
152
  Formal Delivery
102
- owlrunkit start --workspace . --run-id <id> --goal <file> \\
103
- --work-item <id> --owned-path <path>
153
+ owlrunkit formal <start|check|finish> --workspace . --run-id <id> [...]
104
154
  Use for multi-writer, long-running, migration, integration, release, funds,
105
155
  production, or formal acceptance work.
106
156
 
157
+ Frozen dirty source
158
+ owlrunkit candidate <freeze|verify|materialize> --workspace . [...]
159
+
160
+ Two-stage deployment
161
+ owlrunkit deployment prepare --workspace . --run-id <accepted-run> --artifact <file> --media-type <type> --owner-decision <decision-v1.json> --output <receipt>
162
+ owlrunkit deployment execute --workspace . [--resume] --prepare <receipt> --owner-decision <decision-v1.json> --owner-authority <signed-v2.json> --manifest <remote.json>
163
+
164
+ Fleet and status
165
+ owlrunkit fleet register-root --fleet-root <directory>
166
+ owlrunkit fleet discover
167
+ owlrunkit status --workspace .
168
+
169
+ Core successor
170
+ owlrunkit core-successor <plan|apply|resume> --workspace . [...]
171
+
107
172
  Other onboarding commands:
108
173
  owlrunkit profiles validate --workspace .
109
174
  owlrunkit profiles impact --workspace . --changed <path>
@@ -111,9 +176,132 @@ Other onboarding commands:
111
176
 
112
177
  Quick receipts cannot become Formal acceptance.
113
178
  Formal accepted does not grant Git or release authority.
114
- Those always require separate explicit authorization.
115
179
  `;
116
180
 
181
+ const COMMAND_HELP = Object.freeze({
182
+ assurance: `Usage:
183
+ owlrunkit assurance route --workspace <directory> --request <risk-facts.json>
184
+
185
+ Selects none, Quick, or Formal from structured risk facts. It does not grant
186
+ repository, release, deployment, or destructive authority.
187
+ `,
188
+ "assurance route": `Usage:
189
+ owlrunkit assurance route --workspace <directory> --request <risk-facts.json>
190
+
191
+ Required:
192
+ --request <file> Structured assurance risk facts.
193
+ `,
194
+ fleet: `Usage:
195
+ owlrunkit fleet register-root --fleet-root <directory>
196
+ owlrunkit fleet discover [--fleet-root <directory>] [--fleet-manifest <file>]
197
+ `,
198
+ "fleet register-root": `Usage:
199
+ owlrunkit fleet register-root --fleet-root <directory>
200
+
201
+ Registers one durable fleet coverage root for later no-argument discovery.
202
+ `,
203
+ "fleet discover": `Usage:
204
+ owlrunkit fleet discover [--fleet-root <directory>] [--fleet-manifest <file>]
205
+ [--workspace-root <directory>]
206
+
207
+ With no source option, discovers the complete fleet inside registered roots.
208
+ `,
209
+ profiles: `Usage:
210
+ owlrunkit profiles detect --workspace <directory> [--dry-run | --apply]
211
+ owlrunkit profiles validate --workspace <directory>
212
+ owlrunkit profiles impact --workspace <directory> --changed <path>
213
+ `,
214
+ "profiles detect": `Usage:
215
+ owlrunkit profiles detect --workspace <directory> [--dry-run | --apply]
216
+
217
+ --dry-run reports deterministic candidates. --apply atomically adopts only a
218
+ high-confidence unambiguous profile.
219
+ `,
220
+ formal: `Usage:
221
+ owlrunkit formal start --workspace <directory> --run-id <id> [...]
222
+ owlrunkit formal check --workspace <directory> --run-id <id> -- <command>
223
+ owlrunkit formal finish --workspace <directory> --run-id <id>
224
+ `,
225
+ "formal start": `Usage:
226
+ owlrunkit formal start --workspace <directory> --run-id <id>
227
+ --goal <goal.json> --work-item <id> --owned-path <path>
228
+
229
+ Creates the execution and exact writer lease.
230
+ `,
231
+ "formal check": `Usage:
232
+ owlrunkit formal check --workspace <directory> --run-id <id>
233
+ --work-item <id> --check-id <id> --cwd <directory> -- <command> [args...]
234
+ `,
235
+ "formal finish": `Usage:
236
+ owlrunkit formal finish --workspace <directory> --run-id <id>
237
+
238
+ Runs the final source, evidence, permission, closeout, and lease-release path.
239
+ `,
240
+ candidate: `Usage:
241
+ owlrunkit candidate freeze --workspace <directory> --run-id <id>
242
+ --from-lease <id> --candidate-id <id>
243
+ owlrunkit candidate verify --workspace <directory> --candidate <file>
244
+ owlrunkit candidate materialize --workspace <directory> --candidate <file>
245
+ --target-workspace <directory>
246
+ `,
247
+ "candidate freeze": `Usage:
248
+ owlrunkit candidate freeze --workspace <directory> --run-id <id>
249
+ --from-lease <id> --candidate-id <id>
250
+
251
+ Freezes the exact dirty source manifest, including deletions and renames.
252
+ `,
253
+ "candidate verify": `Usage:
254
+ owlrunkit candidate verify --workspace <directory> --candidate <file>
255
+ `,
256
+ "candidate materialize": `Usage:
257
+ owlrunkit candidate materialize --workspace <directory> --candidate <file>
258
+ --target-workspace <directory>
259
+ `,
260
+ deployment: `Usage:
261
+ owlrunkit deployment prepare --workspace <directory> --run-id <accepted-run>
262
+ --artifact <file> --media-type <type> --owner-decision <decision-v1.json> --output <receipt>
263
+ owlrunkit deployment execute --workspace <directory> --prepare <receipt>
264
+ --owner-decision <decision-v1.json> --owner-authority <signed-v2.json> --manifest <remote.json> [--resume]
265
+ `,
266
+ "deployment prepare": `Usage:
267
+ owlrunkit deployment prepare --workspace <directory> --run-id <accepted-run>
268
+ --artifact <file> --media-type <type> --owner-decision <decision-v1.json> --output <receipt>
269
+ `,
270
+ "deployment execute": `Usage:
271
+ owlrunkit deployment execute --workspace <directory> --prepare <receipt>
272
+ --owner-decision <decision-v1.json> --owner-authority <signed-v2.json> --manifest <remote.json> [--resume]
273
+
274
+ The default happy path creates and closes the deployment child, goal, lease,
275
+ profile, preflight, lineage, and result. --request is an advanced legacy path.
276
+ `,
277
+ "core-successor": `Usage:
278
+ owlrunkit core-successor plan --workspace <directory> --plan-id <id> [...]
279
+ owlrunkit core-successor apply --workspace <directory> --plan <file>
280
+ --owner-authority <signed-v2.json> --receipt-id <id>
281
+ owlrunkit core-successor resume --workspace <directory> --plan <file>
282
+ --owner-authority <signed-v2.json> [...]
283
+ `,
284
+ "core-successor plan": `Usage:
285
+ owlrunkit core-successor plan --workspace <directory> --plan-id <id>
286
+ --run-id <id> --from-lease <id> --candidate-id <id>
287
+ [--fleet-root <directory> | --fleet-manifest <file>]
288
+ `,
289
+ "core-successor apply": `Usage:
290
+ owlrunkit core-successor apply --workspace <directory> --plan <file>
291
+ --owner-authority <signed-v2.json> --receipt-id <id>
292
+ `,
293
+ "core-successor resume": `Usage:
294
+ owlrunkit core-successor resume --workspace <directory> --plan <file>
295
+ --owner-authority <signed-v2.json> [--from-receipt <file>]
296
+ `,
297
+ status: `Usage:
298
+ owlrunkit status --workspace <directory> [--json]
299
+
300
+ Shows completed milestones, current state, remaining gates, and the next allowed
301
+ action without granting any authority.
302
+ `,
303
+ });
304
+
117
305
  function withHumanOutput(result, humanOutput) {
118
306
  Object.defineProperty(result, "humanOutput", {
119
307
  configurable: false,
@@ -216,6 +404,38 @@ function requireOption(options, name) {
216
404
  return value;
217
405
  }
218
406
 
407
+ function readWorkspaceArtifact(root, value, label) {
408
+ const relativePath = safeRelativePath(value, label);
409
+ return readJson(realpathSync(path.join(root, relativePath)));
410
+ }
411
+
412
+ function writeWorkspaceArtifact(root, value, label, artifact) {
413
+ const relativePath = safeRelativePath(value, label);
414
+ const outputPath = path.join(root, relativePath);
415
+ writeJsonExclusiveAtomically(outputPath, artifact);
416
+ return relativeToWorkspace(root, outputPath);
417
+ }
418
+
419
+ function writeOrVerifyWorkspaceArtifact(root, value, label, artifact) {
420
+ const relativePath = safeRelativePath(value, label);
421
+ const outputPath = path.join(root, relativePath);
422
+ const expectedBytes = Buffer.from(`${JSON.stringify(artifact, null, 2)}\n`);
423
+ if (!pathEntryExists(outputPath)) {
424
+ writeJsonExclusiveAtomically(outputPath, artifact);
425
+ return relativeToWorkspace(root, outputPath);
426
+ }
427
+ const stat = lstatSync(outputPath);
428
+ if (
429
+ stat.isSymbolicLink()
430
+ || !stat.isFile()
431
+ || realpathSync(outputPath) !== outputPath
432
+ || !readFileSync(outputPath).equals(expectedBytes)
433
+ ) {
434
+ throw new Error(`${label} existing artifact does not match exact bytes.`);
435
+ }
436
+ return relativeToWorkspace(root, outputPath);
437
+ }
438
+
219
439
  function safeRunId(value) {
220
440
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value) || value === "." || value === "..") {
221
441
  throw new Error("run-id must use letters, digits, dot, underscore, or hyphen without path separators.");
@@ -249,6 +469,97 @@ function sha256(value) {
249
469
  return createHash("sha256").update(value).digest("hex");
250
470
  }
251
471
 
472
+ function exactRegularFile(filePath, expectedSha256, label) {
473
+ const resolved = realpathSync(filePath);
474
+ if (resolved !== filePath) {
475
+ throw new Error(`${label} must already use its canonical real path.`);
476
+ }
477
+ const stat = lstatSync(resolved);
478
+ if (stat.isSymbolicLink() || !stat.isFile()) {
479
+ throw new Error(`${label} must be a regular non-symlink file.`);
480
+ }
481
+ if (sha256(readFileSync(resolved)) !== expectedSha256) {
482
+ throw new Error(`${label} hash mismatch.`);
483
+ }
484
+ return resolved;
485
+ }
486
+
487
+ function declaredAdapterIdentity(adapter) {
488
+ return {
489
+ adapterId: adapter.adapterId,
490
+ version: adapter.version,
491
+ executable: adapter.executable,
492
+ sha256: adapter.sha256,
493
+ };
494
+ }
495
+
496
+ function createDeclaredRemoteAdapter(manifest, workspaceRoot) {
497
+ const { normalized } = validateRemoteDeploymentManifest(manifest);
498
+ const declaration = normalized.adapter;
499
+ if (declaration.kind !== "builtin_ssh") {
500
+ exactRegularFile(
501
+ declaration.executable,
502
+ declaration.sha256,
503
+ "Remote deployment adapter executable",
504
+ );
505
+ return createRemoteProcessAdapterV1({
506
+ identity: declaredAdapterIdentity(declaration),
507
+ workspaceRoot,
508
+ });
509
+ }
510
+ const builtInIdentity = builtInSshRemoteAdapterIdentityV1();
511
+ if (
512
+ JSON.stringify(declaredAdapterIdentity(declaration))
513
+ !== JSON.stringify(builtInIdentity)
514
+ ) {
515
+ throw new Error(
516
+ "Built-in SSH adapter declaration does not match this exact OwlRunKit Core.",
517
+ );
518
+ }
519
+ exactRegularFile(
520
+ declaration.executable,
521
+ declaration.sha256,
522
+ "Built-in SSH adapter module",
523
+ );
524
+ const knownHostsPath = exactRegularFile(
525
+ declaration.knownHostsPath,
526
+ normalized.target.hostKeySha256,
527
+ "Built-in SSH known_hosts file",
528
+ );
529
+ const sshExecutable = exactRegularFile(
530
+ declaration.sshExecutable,
531
+ declaration.sshExecutableSha256,
532
+ "Built-in SSH executable",
533
+ );
534
+ const credential = declaration.authentication.mode === "agent"
535
+ ? {
536
+ ref: normalized.credentialRef,
537
+ mode: "agent",
538
+ }
539
+ : {
540
+ ref: normalized.credentialRef,
541
+ mode: "identity_file",
542
+ identityFile: {
543
+ path: exactRegularFile(
544
+ declaration.authentication.identityFile.path,
545
+ declaration.authentication.identityFile.sha256,
546
+ "Built-in SSH identity file",
547
+ ),
548
+ sha256: declaration.authentication.identityFile.sha256,
549
+ },
550
+ };
551
+ return createSshRemoteAdapterV1({
552
+ target: normalized.target,
553
+ credential,
554
+ knownHostsPath,
555
+ workspaceRoot,
556
+ remoteHelper: declaration.remoteHelper,
557
+ stageContracts: declaration.stageContracts,
558
+ sshExecutable,
559
+ sshExecutableSha256: declaration.sshExecutableSha256,
560
+ });
561
+ }
562
+
252
563
  function writeJson(filePath, value, flag = "wx") {
253
564
  writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { flag });
254
565
  }
@@ -811,6 +1122,10 @@ function inspect(options) {
811
1122
  if (!configGate.valid) controlIssues.push(...configGate.issues);
812
1123
  const config = controlIssues.length === 0 ? candidateConfig : null;
813
1124
  const current = currentCoreIdentity();
1125
+ const sharedControl = inspectProjectControlState({
1126
+ workspaceRoot: root,
1127
+ currentCore: current,
1128
+ });
814
1129
  const configCore = config
815
1130
  ? validateExecutionPin({ expected: config.core, actual: current })
816
1131
  : { status: "invalid_config", exitCode: 2, issues: [...controlIssues] };
@@ -819,7 +1134,7 @@ function inspect(options) {
819
1134
  ? readdirSync(executionsRoot, { withFileTypes: true }).sort((left, right) => compareCodeUnits(left.name, right.name))
820
1135
  : [];
821
1136
  const runIds = executionEntries.map((entry) => entry.name);
822
- const executions = executionEntries.map((entry) => {
1137
+ const inspectedExecutions = executionEntries.map((entry) => {
823
1138
  const runId = entry.name;
824
1139
  const executionRoot = runtimePath(root, "executions", runId);
825
1140
  const entryIssues = [];
@@ -907,11 +1222,57 @@ function inspect(options) {
907
1222
  },
908
1223
  };
909
1224
  });
1225
+ const sharedByRunId = new Map(
1226
+ sharedControl.executions.map((execution) => [execution.runId, execution]),
1227
+ );
1228
+ const lifecycleParityIssues = [];
1229
+ const executions = inspectedExecutions.map((execution) => {
1230
+ const shared = sharedByRunId.get(execution.runId);
1231
+ if (!shared) {
1232
+ lifecycleParityIssues.push(
1233
+ `Shared lifecycle state is missing execution: ${execution.runId}`,
1234
+ );
1235
+ return execution;
1236
+ }
1237
+ return {
1238
+ ...execution,
1239
+ lifecycle: shared.lifecycle,
1240
+ historical: shared.historical,
1241
+ closeout: {
1242
+ ...execution.closeout,
1243
+ ...(shared.closeout.statusCode === undefined
1244
+ ? {}
1245
+ : {
1246
+ statusCode: shared.closeout.statusCode,
1247
+ businessGoalIncomplete:
1248
+ shared.closeout.businessGoalIncomplete,
1249
+ replacementPlanRequired:
1250
+ shared.closeout.replacementPlanRequired,
1251
+ nextAllowedAction: shared.closeout.nextAllowedAction,
1252
+ }),
1253
+ },
1254
+ recovery: {
1255
+ ...execution.recovery,
1256
+ lease: structuredClone(shared.lease),
1257
+ },
1258
+ controlState: {
1259
+ lifecycle: shared.lifecycle,
1260
+ historical: shared.historical,
1261
+ closeout: structuredClone(shared.closeout),
1262
+ lease: structuredClone(shared.lease),
1263
+ },
1264
+ };
1265
+ });
910
1266
  const invalidClosedIssues = executions
911
1267
  .filter((execution) => execution.lifecycle === "closed" && execution.closeout?.status === "invalid")
912
1268
  .flatMap((execution) => execution.closeout.issues ?? []);
913
- const recoveryControlIssues = [...controlIssues, ...invalidClosedIssues];
914
- const activeRunIds = executions.filter((execution) => execution.lifecycle !== "closed").map((execution) => execution.runId);
1269
+ const recoveryControlIssues = [
1270
+ ...controlIssues,
1271
+ ...invalidClosedIssues,
1272
+ ...sharedControl.issues,
1273
+ ...lifecycleParityIssues,
1274
+ ];
1275
+ const activeRunIds = [...sharedControl.activeRunIds];
915
1276
  const selectedRun = activeRunIds.length === 1
916
1277
  ? executions.find((execution) => execution.runId === activeRunIds[0])
917
1278
  : null;
@@ -946,7 +1307,9 @@ function inspect(options) {
946
1307
  nextAllowedAction: "select_active_execution",
947
1308
  authorizationGranted: false,
948
1309
  };
949
- if (recoveryControlIssues.length > 0 && activeRunIds.length > 0) {
1310
+ if (sharedControl.status === "invalid") {
1311
+ recovery = structuredClone(sharedControl.recovery);
1312
+ } else if (recoveryControlIssues.length > 0 && activeRunIds.length > 0) {
950
1313
  recovery = { ...recovery, nextAllowedAction: "repair_execution_artifacts" };
951
1314
  }
952
1315
  const result = {
@@ -959,6 +1322,7 @@ function inspect(options) {
959
1322
  runtimeRoot: RUNTIME_ROOT,
960
1323
  config,
961
1324
  configCore,
1325
+ controlState: sharedControl,
962
1326
  controlIssues: recoveryControlIssues,
963
1327
  runIds,
964
1328
  executions,
@@ -1047,6 +1411,13 @@ function closeout(options) {
1047
1411
  sourceFingerprint: gate.sourceFingerprint,
1048
1412
  verificationContextFingerprint: gate.verificationContextFingerprint,
1049
1413
  selectedProfileIds: [...gate.selectedProfileIds],
1414
+ ...(gate.lineage.active.receipt.sourceArtifact === undefined
1415
+ ? {}
1416
+ : {
1417
+ sourceArtifact: structuredClone(
1418
+ gate.lineage.active.receipt.sourceArtifact,
1419
+ ),
1420
+ }),
1050
1421
  leaseState: "released",
1051
1422
  releasedLeaseIds,
1052
1423
  };
@@ -1175,27 +1546,68 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1175
1546
  }, `owlrunkit ${CORE_VERSION}\n`);
1176
1547
  }
1177
1548
  const separatorIndex = rest.indexOf("--");
1178
- if (separatorIndex >= 0 && command !== "verify" && command !== "quick-verify") {
1179
- throw new Error("Only runkit verify accepts an exact command after --.");
1549
+ const acceptsExactCommand = command === "verify"
1550
+ || command === "quick-verify"
1551
+ || (command === "formal" && rest[0] === "check");
1552
+ if (separatorIndex >= 0 && !acceptsExactCommand) {
1553
+ throw new Error("Only verify, quick-verify, and formal check accept an exact command after --.");
1180
1554
  }
1181
1555
  const optionValues = separatorIndex >= 0 ? rest.slice(0, separatorIndex) : rest;
1182
1556
  const commandArgv = separatorIndex >= 0 ? rest.slice(separatorIndex + 1) : [];
1183
- const nested = new Set(["lease", "delivery", "profiles"]).has(command);
1557
+ const nested = new Set([
1558
+ "assurance",
1559
+ "candidate",
1560
+ "core-successor",
1561
+ "delivery",
1562
+ "deployment",
1563
+ "fleet",
1564
+ "formal",
1565
+ "lease",
1566
+ "profiles",
1567
+ ]).has(command);
1184
1568
  const [action, ...nestedOptions] = nested ? optionValues : [null, ...optionValues];
1569
+ const helpRequested = optionValues.includes("--help")
1570
+ || optionValues.includes("-h");
1571
+ if (helpRequested) {
1572
+ const normalizedAction = action === "--help" || action === "-h"
1573
+ ? null
1574
+ : action;
1575
+ const help = COMMAND_HELP[
1576
+ normalizedAction ? `${command} ${normalizedAction}` : command
1577
+ ];
1578
+ if (help) {
1579
+ return withHumanOutput({
1580
+ status: "help",
1581
+ exitCode: 0,
1582
+ authorizationGranted: false,
1583
+ }, help);
1584
+ }
1585
+ }
1185
1586
  const options = parseOptions(nested ? nestedOptions : optionValues, {
1186
1587
  multi: command === "profiles" && action === "impact"
1187
1588
  ? ["changed"]
1188
- : (command === "lease" && action === "acquire") || command === "start"
1589
+ : (command === "fleet" && action === "discover")
1590
+ || (command === "core-successor" && action === "plan")
1591
+ ? ["fleet-root", "workspace-root"]
1592
+ : (command === "lease" && action === "acquire")
1593
+ || command === "start"
1594
+ || (command === "formal" && action === "start")
1189
1595
  ? ["owned-path"]
1190
1596
  : [],
1191
1597
  boolean: command === "inspect"
1192
1598
  ? ["json", "verbose", "history"]
1193
1599
  : command === "init"
1194
1600
  ? ["json"]
1195
- : command === "doctor" || command === "adopt"
1601
+ : command === "doctor" || command === "adopt" || command === "status"
1196
1602
  ? ["json"]
1197
- : command === "profiles"
1198
- ? ["json", "dry-run"]
1603
+ : command === "profiles"
1604
+ ? ["json", "dry-run", "apply"]
1605
+ : command === "candidate"
1606
+ ? ["delivery-v1"]
1607
+ : command === "core-successor" && action === "resume"
1608
+ ? ["adopt-orphan-success-receipts"]
1609
+ : command === "deployment" && action === "execute"
1610
+ ? ["resume"]
1199
1611
  : new Set(["quick-verify", "quick-attest", "quick-metrics"]).has(command)
1200
1612
  ? ["json", "local"]
1201
1613
  : [],
@@ -1228,17 +1640,671 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1228
1640
  ? result
1229
1641
  : withHumanOutput(result, formatAdoptionHuman(result));
1230
1642
  }
1643
+ if (command === "assurance") {
1644
+ if (action !== "route") {
1645
+ throw new Error("Usage: owlrunkit assurance route --request <risk-facts.json>");
1646
+ }
1647
+ const requestPath = realpathSync(requireOption(options, "request"));
1648
+ return routeRunKitAssuranceV1(readJson(requestPath));
1649
+ }
1650
+ if (command === "fleet") {
1651
+ if (action === "register-root") {
1652
+ const registered = await registerFleetCoverageRoot({
1653
+ fleetRoot: requireOption(options, "fleet-root"),
1654
+ });
1655
+ return {
1656
+ ...registered,
1657
+ exitCode: 0,
1658
+ };
1659
+ }
1660
+ if (action !== "discover") {
1661
+ throw new Error("Usage: owlrunkit fleet <register-root|discover> [--fleet-root <directory> | --fleet-manifest <file> | --workspace-root <directory>]");
1662
+ }
1663
+ const discovered = await discoverFleet({
1664
+ fleetRoots: options["fleet-root"] ?? null,
1665
+ fleetManifestPath: options["fleet-manifest"] ?? null,
1666
+ workspaceRoots: options["workspace-root"] ?? null,
1667
+ });
1668
+ return {
1669
+ ...discovered,
1670
+ status: discovered.complete ? "discovered" : "incomplete",
1671
+ exitCode: discovered.complete ? 0 : 2,
1672
+ };
1673
+ }
1674
+ if (command === "core-successor") {
1675
+ const root = workspace(options);
1676
+ if (action === "plan") {
1677
+ const fleetDiscoveryRequest = options["fleet-manifest"]
1678
+ ? { fleetManifestPath: options["fleet-manifest"] }
1679
+ : options["fleet-root"]
1680
+ ? { fleetRoots: options["fleet-root"] }
1681
+ : options["workspace-root"]
1682
+ ? { workspaceRoots: options["workspace-root"] }
1683
+ : {};
1684
+ return await createCoreSuccessorPlanFromFleetV1({
1685
+ controllerWorkspaceRoot: root,
1686
+ planId: requireOption(options, "plan-id"),
1687
+ ...(options.output ? { planPath: options.output } : {}),
1688
+ sourceCandidateRequest: {
1689
+ runId: safeRunId(requireOption(options, "run-id")),
1690
+ workItemId: requireOption(options, "from-lease"),
1691
+ candidateId: requireOption(options, "candidate-id"),
1692
+ },
1693
+ fleetDiscoveryRequest,
1694
+ });
1695
+ }
1696
+ if (action === "apply") {
1697
+ return await applyCoreSuccessorPlanV1({
1698
+ controllerWorkspaceRoot: root,
1699
+ planPath: requireOption(options, "plan"),
1700
+ receiptId: requireOption(options, "receipt-id"),
1701
+ ...(options.output ? { receiptPath: options.output } : {}),
1702
+ ownerAuthority: readJson(
1703
+ realpathSync(requireOption(options, "owner-authority")),
1704
+ ),
1705
+ });
1706
+ }
1707
+ if (action === "resume") {
1708
+ const planPath = requireOption(options, "plan");
1709
+ const adoptOrphans = options["adopt-orphan-success-receipts"] === true;
1710
+ const receiptId = adoptOrphans
1711
+ ? requireOption(options, "receipt-id")
1712
+ : null;
1713
+ return await resumeCoreSuccessorPlanV1({
1714
+ controllerWorkspaceRoot: root,
1715
+ planPath,
1716
+ ...(options["from-receipt"]
1717
+ ? { fromReceiptPath: options["from-receipt"] }
1718
+ : {}),
1719
+ ...(adoptOrphans
1720
+ ? {
1721
+ receiptId,
1722
+ adoptOrphanSuccessReceipts: true,
1723
+ ...(options.output ? { receiptPath: options.output } : {}),
1724
+ }
1725
+ : {}),
1726
+ ownerAuthority: readJson(
1727
+ realpathSync(requireOption(options, "owner-authority")),
1728
+ ),
1729
+ });
1730
+ }
1731
+ throw new Error(
1732
+ "Usage: owlrunkit core-successor <plan|apply|resume> [options]",
1733
+ );
1734
+ }
1735
+ if (command === "deployment") {
1736
+ const root = workspace(options);
1737
+ if (action === "prepare") {
1738
+ let ownerDecision = null;
1739
+ if (!options.request) {
1740
+ const decisionRelativePath = safeRelativePath(
1741
+ requireOption(options, "owner-decision"),
1742
+ "deployment Owner decision",
1743
+ );
1744
+ const decisionPath = realpathSync(
1745
+ path.join(root, decisionRelativePath),
1746
+ );
1747
+ if (decisionPath !== path.resolve(root, decisionRelativePath)) {
1748
+ throw new Error(
1749
+ "Deployment Owner decision must be a regular workspace file without symlink traversal.",
1750
+ );
1751
+ }
1752
+ ownerDecision = readJson(decisionPath);
1753
+ }
1754
+ const receipt = options.request
1755
+ ? createDeploymentPrepareReceipt(
1756
+ readJson(realpathSync(requireOption(options, "request"))),
1757
+ )
1758
+ : createDeploymentPrepareReceiptFromClosedRun({
1759
+ workspaceRoot: root,
1760
+ prepareRunId: safeRunId(requireOption(options, "run-id")),
1761
+ artifactPath: requireOption(options, "artifact"),
1762
+ mediaType: requireOption(options, "media-type"),
1763
+ ownerDecision,
1764
+ });
1765
+ const receiptPath = writeWorkspaceArtifact(
1766
+ root,
1767
+ requireOption(options, "output"),
1768
+ "deployment prepare output",
1769
+ receipt,
1770
+ );
1771
+ return {
1772
+ status: "deployment_prepared",
1773
+ exitCode: 0,
1774
+ receiptPath,
1775
+ receiptSha256: receipt.receiptSha256,
1776
+ authorizationGranted: false,
1777
+ };
1778
+ }
1779
+ if (action === "execute") {
1780
+ const prepareReceipt = readWorkspaceArtifact(
1781
+ root,
1782
+ requireOption(options, "prepare"),
1783
+ "deployment prepare receipt",
1784
+ );
1785
+ const manifestPath = realpathSync(requireOption(options, "manifest"));
1786
+ const manifestBytes = readFileSync(manifestPath);
1787
+ const manifest = JSON.parse(manifestBytes.toString("utf8"));
1788
+ const automatic = options.request === undefined;
1789
+ const resuming = options.resume === true;
1790
+ if (resuming && !automatic) {
1791
+ throw new Error(
1792
+ "deployment execute --resume cannot be combined with --request.",
1793
+ );
1794
+ }
1795
+ let generated = null;
1796
+ let executeRequest;
1797
+ if (automatic) {
1798
+ const authorityRelativePath = safeRelativePath(
1799
+ requireOption(options, "owner-authority"),
1800
+ "deployment Owner authority",
1801
+ );
1802
+ const authorityPath = realpathSync(
1803
+ path.join(root, authorityRelativePath),
1804
+ );
1805
+ if (authorityPath !== path.resolve(root, authorityRelativePath)) {
1806
+ throw new Error(
1807
+ "Deployment Owner authority must be a regular workspace file without symlink traversal.",
1808
+ );
1809
+ }
1810
+ const ownerAuthority = {
1811
+ path: authorityRelativePath,
1812
+ sha256: sha256(readFileSync(authorityPath)),
1813
+ };
1814
+ const decisionRelativePath = safeRelativePath(
1815
+ requireOption(options, "owner-decision"),
1816
+ "deployment Owner decision",
1817
+ );
1818
+ const decisionPath = realpathSync(
1819
+ path.join(root, decisionRelativePath),
1820
+ );
1821
+ if (decisionPath !== path.resolve(root, decisionRelativePath)) {
1822
+ throw new Error(
1823
+ "Deployment Owner decision must be a regular workspace file without symlink traversal.",
1824
+ );
1825
+ }
1826
+ const ownerDecision = {
1827
+ path: decisionRelativePath,
1828
+ sha256: sha256(readFileSync(decisionPath)),
1829
+ };
1830
+ if (resuming) {
1831
+ const runId = safeRunId(manifest.deploymentId);
1832
+ const deploymentRoot = `${RUNTIME_ROOT}/deployments/${runId}`;
1833
+ const executeRequestPath = `${deploymentRoot}/execute-request.json`;
1834
+ const existingRequest = readWorkspaceArtifact(
1835
+ root,
1836
+ executeRequestPath,
1837
+ "deployment execute request",
1838
+ );
1839
+ if (
1840
+ existingRequest.executeRunId !== runId
1841
+ || JSON.stringify(existingRequest.ownerAuthority)
1842
+ !== JSON.stringify(ownerAuthority)
1843
+ || existingRequest.ownerDecision?.path
1844
+ !== ownerDecision.path
1845
+ ) {
1846
+ throw new Error(
1847
+ "Deployment resume Owner authority or child request drifted.",
1848
+ );
1849
+ }
1850
+ generated = {
1851
+ runId,
1852
+ executeRequest: existingRequest,
1853
+ executeRequestPath,
1854
+ lineagePath: `${deploymentRoot}/lineage.json`,
1855
+ resultPath: `${deploymentRoot}/result.json`,
1856
+ };
1857
+ } else {
1858
+ generated = createDeploymentExecuteChildRun({
1859
+ workspaceRoot: root,
1860
+ prepareReceipt,
1861
+ remoteManifest: manifest,
1862
+ ownerAuthority,
1863
+ ownerDecision,
1864
+ });
1865
+ }
1866
+ executeRequest = generated.executeRequest;
1867
+ } else {
1868
+ executeRequest = readJson(
1869
+ realpathSync(requireOption(options, "request")),
1870
+ );
1871
+ }
1872
+ const lineageOutput = automatic
1873
+ ? generated.lineagePath
1874
+ : requireOption(options, "lineage-output");
1875
+ const resultOutput = automatic
1876
+ ? generated.resultPath
1877
+ : requireOption(options, "output");
1878
+ let detectedSupersession = null;
1879
+ try {
1880
+ if (automatic) {
1881
+ const initialDecisionState = inspectDeploymentOwnerDecisionStateV1({
1882
+ workspaceRoot: root,
1883
+ descriptor: executeRequest.ownerDecision,
1884
+ expectedDecisionSha256:
1885
+ prepareReceipt.ownerDecision?.decisionSha256,
1886
+ });
1887
+ if (initialDecisionState.status === "superseded") {
1888
+ detectedSupersession = {
1889
+ priorDecision: {
1890
+ decisionSha256:
1891
+ prepareReceipt.ownerDecision.decisionSha256,
1892
+ },
1893
+ replacementDecision: initialDecisionState.decision,
1894
+ };
1895
+ const error = new Error("owner_decision_superseded");
1896
+ error.code = "owner_decision_superseded";
1897
+ error.replacementDecision = initialDecisionState.decision;
1898
+ throw error;
1899
+ }
1900
+ }
1901
+ const lineage = createDeploymentExecuteLineageFromActiveRun({
1902
+ workspaceRoot: root,
1903
+ prepareReceipt,
1904
+ executeRequest,
1905
+ remoteManifest: manifest,
1906
+ remoteManifestBytes: manifestBytes,
1907
+ verifyOwnerAuthority:
1908
+ runtimeHooks.verifyOwnerDeploymentAuthority,
1909
+ });
1910
+ const lineagePath = resuming
1911
+ ? writeOrVerifyWorkspaceArtifact(
1912
+ root,
1913
+ lineageOutput,
1914
+ "deployment lineage output",
1915
+ lineage,
1916
+ )
1917
+ : writeWorkspaceArtifact(
1918
+ root,
1919
+ lineageOutput,
1920
+ "deployment lineage output",
1921
+ lineage,
1922
+ );
1923
+ if (
1924
+ manifest.deploymentLineageSha256 !== null
1925
+ && manifest.deploymentLineageSha256 !== lineage.lineageSha256
1926
+ ) {
1927
+ throw new Error("Remote deployment manifest lineage does not match the execute lineage.");
1928
+ }
1929
+ const boundManifest = {
1930
+ ...manifest,
1931
+ deploymentLineageSha256: lineage.lineageSha256,
1932
+ };
1933
+ if (
1934
+ JSON.stringify(boundManifest.target) !== JSON.stringify(lineage.target.value)
1935
+ || JSON.stringify(boundManifest.artifact) !== JSON.stringify(lineage.parent.artifact)
1936
+ ) {
1937
+ throw new Error("Remote deployment manifest target or artifact does not match the execute lineage.");
1938
+ }
1939
+ const remoteAdapter = createDeclaredRemoteAdapter(
1940
+ boundManifest,
1941
+ root,
1942
+ );
1943
+ const executeRequestPath = automatic
1944
+ ? path.join(root, generated.executeRequestPath)
1945
+ : realpathSync(requireOption(options, "request"));
1946
+ const stageJournal = createRemoteDeploymentStageJournalV1({
1947
+ journalRoot: path.join(
1948
+ root,
1949
+ RUNTIME_ROOT,
1950
+ "deployments",
1951
+ executeRequest.executeRunId,
1952
+ "stage-journal",
1953
+ ),
1954
+ binding: {
1955
+ deploymentId: executeRequest.executeRunId,
1956
+ deploymentLineageSha256: lineage.lineageSha256,
1957
+ remoteManifestSha256: sha256(manifestBytes),
1958
+ executeRequestSha256: sha256(readFileSync(executeRequestPath)),
1959
+ adapterIdentity: remoteAdapter.identity,
1960
+ },
1961
+ });
1962
+ const result = await executeRemoteDeployment({
1963
+ manifest: boundManifest,
1964
+ adapter: remoteAdapter,
1965
+ permissions: lineage.permissions,
1966
+ stageJournal,
1967
+ beforeStageGuard() {
1968
+ const decisionState = inspectDeploymentOwnerDecisionStateV1({
1969
+ workspaceRoot: root,
1970
+ descriptor: executeRequest.ownerDecision,
1971
+ expectedDecisionSha256:
1972
+ lineage.ownerDecision.decisionSha256,
1973
+ });
1974
+ if (decisionState.status === "superseded") {
1975
+ detectedSupersession = {
1976
+ priorDecision: {
1977
+ decisionSha256:
1978
+ lineage.ownerDecision.decisionSha256,
1979
+ },
1980
+ replacementDecision: decisionState.decision,
1981
+ };
1982
+ const error = new Error("owner_decision_superseded");
1983
+ error.code = "owner_decision_superseded";
1984
+ throw error;
1985
+ }
1986
+ const currentLineage = createDeploymentExecuteLineageFromActiveRun({
1987
+ workspaceRoot: root,
1988
+ prepareReceipt,
1989
+ executeRequest,
1990
+ remoteManifest: manifest,
1991
+ remoteManifestBytes: manifestBytes,
1992
+ verifyOwnerAuthority:
1993
+ runtimeHooks.verifyOwnerDeploymentAuthority,
1994
+ });
1995
+ if (currentLineage.lineageSha256 !== lineage.lineageSha256) {
1996
+ throw new Error(
1997
+ "Deployment child control state drifted during remote execution.",
1998
+ );
1999
+ }
2000
+ },
2001
+ });
2002
+ if (result.status === "reconciliation_required") {
2003
+ return {
2004
+ ...result,
2005
+ exitCode: 2,
2006
+ runId: executeRequest.executeRunId,
2007
+ executeRequestPath: generated?.executeRequestPath ?? null,
2008
+ lineagePath,
2009
+ resultPath: null,
2010
+ stageJournalPath: relativePath(root, stageJournal.journalRoot),
2011
+ closeoutDecision: null,
2012
+ childRemainsActive: true,
2013
+ };
2014
+ }
2015
+ if (automatic && result.status === "deployed") {
2016
+ const finalLineage = createDeploymentExecuteLineageFromActiveRun({
2017
+ workspaceRoot: root,
2018
+ prepareReceipt,
2019
+ executeRequest,
2020
+ remoteManifest: manifest,
2021
+ remoteManifestBytes: manifestBytes,
2022
+ verifyOwnerAuthority:
2023
+ runtimeHooks.verifyOwnerDeploymentAuthority,
2024
+ });
2025
+ if (finalLineage.lineageSha256 !== lineage.lineageSha256) {
2026
+ throw new Error(
2027
+ "Deployment child control state drifted before closeout.",
2028
+ );
2029
+ }
2030
+ }
2031
+ const resultPath = resuming
2032
+ ? writeOrVerifyWorkspaceArtifact(
2033
+ root,
2034
+ resultOutput,
2035
+ "remote deployment result output",
2036
+ result,
2037
+ )
2038
+ : writeWorkspaceArtifact(
2039
+ root,
2040
+ resultOutput,
2041
+ "remote deployment result output",
2042
+ result,
2043
+ );
2044
+ const closeout = automatic
2045
+ ? closeDeploymentExecuteChildRun({
2046
+ workspaceRoot: root,
2047
+ prepareReceipt,
2048
+ executeRequest,
2049
+ result,
2050
+ resultPath,
2051
+ ...(detectedSupersession === null
2052
+ ? {}
2053
+ : { supersession: detectedSupersession }),
2054
+ })
2055
+ : null;
2056
+ return {
2057
+ ...result,
2058
+ exitCode: result.status === "deployed" ? 0 : 2,
2059
+ runId: executeRequest.executeRunId,
2060
+ executeRequestPath: generated?.executeRequestPath ?? null,
2061
+ lineagePath,
2062
+ resultPath,
2063
+ ...(closeout ?? {}),
2064
+ };
2065
+ } catch (error) {
2066
+ if (!automatic) throw error;
2067
+ if (
2068
+ error?.code === "owner_decision_superseded"
2069
+ && detectedSupersession !== null
2070
+ ) {
2071
+ const result = {
2072
+ schemaVersion: "OwlCodaRunKitDeploymentWorkflowFailureV1",
2073
+ deploymentId: executeRequest.executeRunId,
2074
+ status: "failed",
2075
+ completedStages: [],
2076
+ stoppedAtStage: "control_preflight",
2077
+ failureCode: "owner_decision_superseded",
2078
+ authorizationGranted: false,
2079
+ };
2080
+ const resultPath = resuming
2081
+ ? writeOrVerifyWorkspaceArtifact(
2082
+ root,
2083
+ resultOutput,
2084
+ "remote deployment result output",
2085
+ result,
2086
+ )
2087
+ : writeWorkspaceArtifact(
2088
+ root,
2089
+ resultOutput,
2090
+ "remote deployment result output",
2091
+ result,
2092
+ );
2093
+ const closeout = closeDeploymentExecuteChildRun({
2094
+ workspaceRoot: root,
2095
+ prepareReceipt,
2096
+ executeRequest,
2097
+ result,
2098
+ resultPath,
2099
+ supersession: detectedSupersession,
2100
+ });
2101
+ return {
2102
+ ...result,
2103
+ exitCode: 2,
2104
+ runId: executeRequest.executeRunId,
2105
+ executeRequestPath: generated.executeRequestPath,
2106
+ lineagePath: generated.lineagePath,
2107
+ resultPath,
2108
+ issues: [error.message],
2109
+ ...closeout,
2110
+ };
2111
+ }
2112
+ if (resuming) {
2113
+ return {
2114
+ schemaVersion: "OwlCodaRunKitDeploymentWorkflowFailureV1",
2115
+ deploymentId: executeRequest.executeRunId,
2116
+ status: "failed",
2117
+ completedStages: [],
2118
+ stoppedAtStage: "control_preflight",
2119
+ failureCode: "deployment_resume_control_preflight_failed",
2120
+ exitCode: 2,
2121
+ runId: executeRequest.executeRunId,
2122
+ executeRequestPath: generated.executeRequestPath,
2123
+ lineagePath: generated.lineagePath,
2124
+ resultPath: null,
2125
+ closeoutDecision: null,
2126
+ childRemainsActive: true,
2127
+ issues: [error instanceof Error ? error.message : String(error)],
2128
+ authorizationGranted: false,
2129
+ };
2130
+ }
2131
+ const result = {
2132
+ schemaVersion: "OwlCodaRunKitDeploymentWorkflowFailureV1",
2133
+ deploymentId: executeRequest.executeRunId,
2134
+ status: "failed",
2135
+ completedStages: [],
2136
+ stoppedAtStage: "control_preflight",
2137
+ failureCode: "deployment_control_preflight_failed",
2138
+ authorizationGranted: false,
2139
+ };
2140
+ const resultPath = writeWorkspaceArtifact(
2141
+ root,
2142
+ resultOutput,
2143
+ "remote deployment result output",
2144
+ result,
2145
+ );
2146
+ const closeout = closeDeploymentExecuteChildRun({
2147
+ workspaceRoot: root,
2148
+ prepareReceipt,
2149
+ executeRequest,
2150
+ result,
2151
+ resultPath,
2152
+ });
2153
+ return {
2154
+ ...result,
2155
+ exitCode: 2,
2156
+ runId: executeRequest.executeRunId,
2157
+ executeRequestPath: generated.executeRequestPath,
2158
+ lineagePath: generated.lineagePath,
2159
+ resultPath,
2160
+ issues: [error instanceof Error ? error.message : String(error)],
2161
+ ...closeout,
2162
+ };
2163
+ }
2164
+ }
2165
+ throw new Error("Usage: owlrunkit deployment <prepare|execute> [options]");
2166
+ }
2167
+ if (command === "formal") {
2168
+ if (action === "start") {
2169
+ return startExecution({
2170
+ ...options,
2171
+ "work-item": options["work-item"] ?? "delivery",
2172
+ });
2173
+ }
2174
+ if (action === "check") {
2175
+ return recordFormalCheckV1({
2176
+ workspaceRoot: workspace(options),
2177
+ runId: safeRunId(requireOption(options, "run-id")),
2178
+ workItemId: options["from-lease"] ?? "delivery",
2179
+ checkId: requireOption(options, "check-id"),
2180
+ cwd: options.cwd ?? ".",
2181
+ commandArgv,
2182
+ });
2183
+ }
2184
+ if (action === "finish") {
2185
+ const decision = options.decision ?? "accepted";
2186
+ if (!new Set(["accepted", "rejected", "blocked"]).has(decision)) {
2187
+ throw new Error("decision must be accepted, rejected, or blocked.");
2188
+ }
2189
+ const root = workspace(options);
2190
+ const runId = safeRunId(requireOption(options, "run-id"));
2191
+ const finalizeId = options["finalize-id"] ?? "formal-final";
2192
+ const resumed = resumeCompletedFormalFinishV1({
2193
+ workspaceRoot: root,
2194
+ runId,
2195
+ finalizeId,
2196
+ decision,
2197
+ });
2198
+ if (resumed !== null) return resumed;
2199
+ let finalized = null;
2200
+ if (decision === "accepted") {
2201
+ finalized = finalizeFormalChecksV1({
2202
+ workspaceRoot: root,
2203
+ runId,
2204
+ finalizeId,
2205
+ });
2206
+ if (finalized.status !== "accepted_passed") return finalized;
2207
+ }
2208
+ const finished = finishExecution({
2209
+ ...options,
2210
+ decision,
2211
+ });
2212
+ if (finished.status !== "finished") return finished;
2213
+ return {
2214
+ status: "formal_finished",
2215
+ exitCode: 0,
2216
+ runId: finished.runId,
2217
+ decision,
2218
+ snapshotCount: finalized?.snapshotCount ?? 0,
2219
+ staleCheckCount: finalized?.staleCheckCount ?? 0,
2220
+ sourceFingerprint: finalized?.sourceFingerprint ?? null,
2221
+ activeReceiptSha256: finished.activeReceiptSha256,
2222
+ closeoutArtifactSha256: finished.closeoutArtifactSha256,
2223
+ releasedLeaseIds: finished.releasedLeaseIds,
2224
+ authorizationGranted: false,
2225
+ };
2226
+ }
2227
+ throw new Error("Usage: owlrunkit formal <start|check|finish> [options]");
2228
+ }
2229
+ if (command === "candidate") {
2230
+ if (action === "freeze") {
2231
+ const freezeCandidate = options["delivery-v1"] === true
2232
+ ? freezeSourceCandidateV1
2233
+ : freezeSourceCandidateV2;
2234
+ return freezeCandidate({
2235
+ workspaceRoot: workspace(options),
2236
+ runId: safeRunId(requireOption(options, "run-id")),
2237
+ workItemId: requireOption(options, "from-lease"),
2238
+ candidateId: requireOption(options, "candidate-id"),
2239
+ });
2240
+ }
2241
+ if (action === "verify") {
2242
+ const root = workspace(options);
2243
+ const candidatePath = requireOption(options, "candidate");
2244
+ const candidate = readWorkspaceArtifact(
2245
+ root,
2246
+ candidatePath,
2247
+ "source candidate",
2248
+ );
2249
+ if (candidate.schemaVersion === "OwlCodaRunKitSourceCandidateV2") {
2250
+ return verifySourceCandidateV2({
2251
+ workspaceRoot: root,
2252
+ candidatePath,
2253
+ });
2254
+ }
2255
+ return verifySourceCandidateV1({
2256
+ workspaceRoot: root,
2257
+ candidatePath,
2258
+ });
2259
+ }
2260
+ if (action === "materialize") {
2261
+ return materializeSourceCandidateV2({
2262
+ workspaceRoot: workspace(options),
2263
+ candidatePath: requireOption(options, "candidate"),
2264
+ targetWorkspaceRoot: requireOption(options, "target-workspace"),
2265
+ });
2266
+ }
2267
+ throw new Error("Usage: owlrunkit candidate <freeze|verify|materialize> [options]");
2268
+ }
1231
2269
  if (command === "start") return startExecution(options);
1232
2270
  if (command === "plan") {
1233
2271
  const root = workspace(options);
1234
2272
  return withControlTransaction(root, () => plan(options));
1235
2273
  }
1236
2274
  if (command === "inspect") return inspect(options);
2275
+ if (command === "status") {
2276
+ const inspected = inspect({ ...options, json: true });
2277
+ const status = deriveHumanStatusFromInspectV1(inspected);
2278
+ return options.json === true
2279
+ ? { ...status, exitCode: status.overall === "blocked" ? 2 : 0 }
2280
+ : withHumanOutput(
2281
+ { ...status, exitCode: status.overall === "blocked" ? 2 : 0 },
2282
+ [
2283
+ status.headline,
2284
+ `Stage: ${status.stage}`,
2285
+ `Completed: ${status.completedSteps.length > 0 ? status.completedSteps.join(", ") : "none"}`,
2286
+ `Source/data readiness: ${status.milestones.sourceData.status}`,
2287
+ `Release package: ${status.milestones.releasePackage.status}`,
2288
+ `Remote/VM write: ${status.milestones.remoteVmWrite.status}`,
2289
+ `Remaining gates: ${status.remainingGateCount}`,
2290
+ `Next: ${status.nextAllowedAction}`,
2291
+ "",
2292
+ ].join("\n"),
2293
+ );
2294
+ }
1237
2295
  if (command === "profiles") {
1238
2296
  const root = requireOption(options, "workspace");
1239
2297
  if (action === "detect") {
1240
- if (options["dry-run"] !== true) throw new Error("profiles detect requires --dry-run.");
1241
- return detectProfiles({ workspaceRoot: root });
2298
+ if (options.apply === true && options["dry-run"] === true) {
2299
+ throw new Error("profiles detect accepts either --apply or --dry-run, not both.");
2300
+ }
2301
+ if (options["dry-run"] === true) {
2302
+ return detectProfiles({ workspaceRoot: root });
2303
+ }
2304
+ const detection = detectProfilesV2({ workspaceRoot: root });
2305
+ return options.apply === true
2306
+ ? applyDetectedProfilesV2({ workspaceRoot: root, detection })
2307
+ : detection;
1242
2308
  }
1243
2309
  if (action === "validate") return validateProfiles({ workspaceRoot: root });
1244
2310
  if (action === "impact") {
@@ -1280,12 +2346,17 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1280
2346
  });
1281
2347
  }
1282
2348
  if (command === "verify") {
2349
+ const root = workspace(options);
2350
+ const cwd = options.cwd ?? ".";
2351
+ validateLegacyVerificationSideEffectsV1({
2352
+ commandArgv,
2353
+ });
1283
2354
  return runHighLevelVerify({
1284
- workspaceRoot: workspace(options),
2355
+ workspaceRoot: root,
1285
2356
  runId: safeRunId(requireOption(options, "run-id")),
1286
2357
  workItemId: requireOption(options, "from-lease"),
1287
2358
  verificationId: requireOption(options, "verification-id"),
1288
- cwd: options.cwd ?? ".",
2359
+ cwd,
1289
2360
  commandArgv,
1290
2361
  });
1291
2362
  }
@@ -1384,7 +2455,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1384
2455
  return withControlTransaction(root, () => closeout(options));
1385
2456
  }
1386
2457
  if (command === "finish") return finishExecution(options);
1387
- throw new Error("Usage: owlrunkit <init|doctor|adopt|profiles|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
2458
+ throw new Error("Usage: owlrunkit <init|doctor|adopt|assurance|fleet|profiles|status|formal|candidate|deployment|core-successor|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
1388
2459
  } catch (error) {
1389
2460
  return {
1390
2461
  status: "invalid_input",
@@ -1414,7 +2485,12 @@ if (isDirectExecution(import.meta.url)) {
1414
2485
  && !argv.includes("--json")
1415
2486
  && process.stdout.isTTY === true;
1416
2487
  process.stdout.write(
1417
- typeof result.humanOutput === "string" && (!onboardingHumanCommand || interactiveOnboarding)
2488
+ typeof result.humanOutput === "string"
2489
+ && (
2490
+ result.status === "help"
2491
+ || !onboardingHumanCommand
2492
+ || interactiveOnboarding
2493
+ )
1418
2494
  ? result.humanOutput
1419
2495
  : humanInspect
1420
2496
  ? formatInspectHuman(result)