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