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.
- package/CHANGELOG.md +100 -0
- package/README.md +274 -66
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
- package/package.json +2 -2
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/registry-release-policy-v1.json +8 -2
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +119 -0
- package/scripts/runkit-contract/core-contract.mjs +250 -115
- package/scripts/runkit-contract/core-successor.mjs +2043 -0
- package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
- package/scripts/runkit-contract/finalize.mjs +242 -21
- package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
- package/scripts/runkit-contract/formal-workflow.mjs +850 -0
- package/scripts/runkit-contract/human-status.mjs +553 -0
- package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
- package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
- package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
- package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
- package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
- package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
- package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
- package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
- package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
- package/scripts/runkit-contract/project-control-parser.mjs +830 -0
- package/scripts/runkit-contract/project-control-state.mjs +28 -0
- package/scripts/runkit-contract/provenance-common.mjs +19 -2
- package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
- package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
- package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
- package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
- package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
- package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
- package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
- package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
- package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
- package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
- package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
- package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
- package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
- package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
- package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
- package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
- package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
- package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
- package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
- package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
- package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
- package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
- package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
- package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
- package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
- package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
- package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
- package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
- package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
- package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
- package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
- package/scripts/runkit-contract/snapshot.mjs +1 -1
- package/scripts/runkit-contract/source-candidate.mjs +1382 -0
- package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
- package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
- package/scripts/runkit-contract/templates/assurance-request.json +13 -0
- package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
- package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
- package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
- package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
- package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
- package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
- package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
- package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
- package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
- 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 {
|
|
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>
|
|
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
|
|
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 = [
|
|
914
|
-
|
|
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 (
|
|
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
|
-
|
|
1179
|
-
|
|
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([
|
|
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 === "
|
|
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
|
-
|
|
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"]
|
|
1241
|
-
|
|
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:
|
|
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
|
|
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"
|
|
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)
|