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
|
@@ -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.0";
|
|
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 {
|
|
@@ -234,9 +368,15 @@ export function validateLeaseOwnedPaths(paths) {
|
|
|
234
368
|
|
|
235
369
|
export function validateExecutionPin({ expected, actual } = {}) {
|
|
236
370
|
const fields = ["contractVersion", "coreVersion", "coreManifestSha256", "coreSourceRef"];
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
371
|
+
try {
|
|
372
|
+
validateCoreIdentity(expected, { requireSourceBinding: false });
|
|
373
|
+
validateCoreIdentity(actual, { requireSourceBinding: false });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return {
|
|
376
|
+
status: "invalid_input",
|
|
377
|
+
exitCode: 3,
|
|
378
|
+
issues: [error instanceof Error ? `invalid engine pin: ${error.message}` : String(error)],
|
|
379
|
+
};
|
|
240
380
|
}
|
|
241
381
|
const issues = fields.filter((field) => expected[field] !== actual[field]).map((field) => `${field} changed during execution`);
|
|
242
382
|
return issues.length === 0
|
|
@@ -244,11 +384,27 @@ export function validateExecutionPin({ expected, actual } = {}) {
|
|
|
244
384
|
: { status: "engine_changed_during_execution", exitCode: 2, issues };
|
|
245
385
|
}
|
|
246
386
|
|
|
247
|
-
function validateCoreIdentity(core) {
|
|
387
|
+
function validateCoreIdentity(core, { requireSourceBinding = true } = {}) {
|
|
248
388
|
if (!isRecord(core)) throw new Error("Core identity must be an object.");
|
|
389
|
+
const coreKeys = ["contractVersion", "coreManifestSha256", "coreSourceRef", "coreVersion"];
|
|
390
|
+
const unsupportedCoreKeys = Object.keys(core).filter(key => !coreKeys.includes(key));
|
|
391
|
+
if (unsupportedCoreKeys.length > 0) {
|
|
392
|
+
throw new Error(`Core identity contains unsupported field: ${unsupportedCoreKeys[0]}`);
|
|
393
|
+
}
|
|
249
394
|
if (typeof core.contractVersion !== "string" || typeof core.coreVersion !== "string") throw new Error("Core identity requires contractVersion and coreVersion.");
|
|
395
|
+
if (!new Set(["0.1", CONTRACT_VERSION]).has(core.contractVersion)) {
|
|
396
|
+
throw new Error("Core identity contractVersion must be a supported RunKit contract version.");
|
|
397
|
+
}
|
|
398
|
+
if (!/^\d+\.\d+\.\d+$/u.test(core.coreVersion)) {
|
|
399
|
+
throw new Error("Core identity coreVersion must be a three-part semantic version.");
|
|
400
|
+
}
|
|
250
401
|
if (!isSha256Ref(core.coreManifestSha256)) throw new Error("Core identity requires coreManifestSha256.");
|
|
251
|
-
if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0)
|
|
402
|
+
if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0) {
|
|
403
|
+
throw new Error("Core identity coreSourceRef must be a non-empty string.");
|
|
404
|
+
}
|
|
405
|
+
if (requireSourceBinding && core.coreSourceRef !== `artifact:${core.coreManifestSha256}`) {
|
|
406
|
+
throw new Error("Core identity coreSourceRef must bind coreManifestSha256.");
|
|
407
|
+
}
|
|
252
408
|
}
|
|
253
409
|
|
|
254
410
|
export function validateProjectConfigV2(config) {
|
|
@@ -281,102 +437,36 @@ export function validateProjectConfigV2(config) {
|
|
|
281
437
|
}
|
|
282
438
|
}
|
|
283
439
|
|
|
284
|
-
|
|
285
|
-
if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
|
|
286
|
-
throw new Error("workspaceRoot is required.");
|
|
287
|
-
}
|
|
288
|
-
const root = realpathSync(workspaceRoot);
|
|
289
|
-
const executionsRoot = resolve(root, RUNTIME_ROOT, "executions");
|
|
290
|
-
if (!existsSync(executionsRoot)) {
|
|
291
|
-
return {
|
|
292
|
-
status: "safe",
|
|
293
|
-
activeRunIds: [],
|
|
294
|
-
activeLeaseIds: [],
|
|
295
|
-
issues: [],
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const executionsStat = lstatSync(executionsRoot);
|
|
299
|
-
if (executionsStat.isSymbolicLink() || !executionsStat.isDirectory()) {
|
|
300
|
-
return {
|
|
301
|
-
status: "unsafe",
|
|
302
|
-
activeRunIds: [],
|
|
303
|
-
activeLeaseIds: [],
|
|
304
|
-
issues: ["RunKit executions root must be a regular directory."],
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
const activeRunIds = [];
|
|
308
|
-
const activeLeaseIds = [];
|
|
309
|
-
const issues = [];
|
|
310
|
-
const entries = readdirSync(executionsRoot, { withFileTypes: true })
|
|
311
|
-
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
312
|
-
for (const entry of entries) {
|
|
313
|
-
if (entry.isSymbolicLink()) {
|
|
314
|
-
issues.push(`Execution directory must not be a symlink: ${entry.name}`);
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
if (!entry.isDirectory()) continue;
|
|
318
|
-
const executionRoot = resolve(executionsRoot, entry.name);
|
|
319
|
-
const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
|
|
320
|
-
if (!existsSync(closeoutPath)) {
|
|
321
|
-
activeRunIds.push(entry.name);
|
|
322
|
-
} else {
|
|
323
|
-
const closeoutStat = lstatSync(closeoutPath);
|
|
324
|
-
if (closeoutStat.isSymbolicLink() || !closeoutStat.isFile()) {
|
|
325
|
-
issues.push(`Closeout receipt must be a regular file: ${entry.name}`);
|
|
326
|
-
} else {
|
|
327
|
-
try {
|
|
328
|
-
const closeout = JSON.parse(readFileSync(closeoutPath, "utf8"));
|
|
329
|
-
if (closeout?.artifact?.payload?.runId !== entry.name) {
|
|
330
|
-
issues.push(`Closeout receipt runId mismatch: ${entry.name}`);
|
|
331
|
-
}
|
|
332
|
-
} catch {
|
|
333
|
-
issues.push(`Closeout receipt is invalid JSON: ${entry.name}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const leasesRoot = resolve(executionRoot, "leases");
|
|
338
|
-
if (!existsSync(leasesRoot)) continue;
|
|
339
|
-
const leasesStat = lstatSync(leasesRoot);
|
|
340
|
-
if (leasesStat.isSymbolicLink() || !leasesStat.isDirectory()) {
|
|
341
|
-
issues.push(`Lease directory must be a regular directory: ${entry.name}`);
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
const leaseEntries = readdirSync(leasesRoot, { withFileTypes: true })
|
|
345
|
-
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
346
|
-
for (const leaseEntry of leaseEntries) {
|
|
347
|
-
if (!leaseEntry.name.endsWith(".json")) continue;
|
|
348
|
-
if (leaseEntry.isSymbolicLink() || !leaseEntry.isFile()) {
|
|
349
|
-
issues.push(`Lease artifact must be a regular file: ${entry.name}/${leaseEntry.name}`);
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
try {
|
|
353
|
-
const lease = JSON.parse(readFileSync(resolve(leasesRoot, leaseEntry.name), "utf8"));
|
|
354
|
-
if (lease.state === "active") {
|
|
355
|
-
activeLeaseIds.push(`${entry.name}:${lease.workItemId ?? leaseEntry.name}`);
|
|
356
|
-
} else if (lease.state !== "released") {
|
|
357
|
-
issues.push(`Lease state is invalid: ${entry.name}/${leaseEntry.name}`);
|
|
358
|
-
}
|
|
359
|
-
} catch {
|
|
360
|
-
issues.push(`Lease artifact is invalid JSON: ${entry.name}/${leaseEntry.name}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
440
|
+
function projectControlParserContract() {
|
|
364
441
|
return {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
activeRunIds,
|
|
371
|
-
activeLeaseIds,
|
|
372
|
-
issues,
|
|
442
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
443
|
+
acceptedCloseoutVerificationIssues,
|
|
444
|
+
validateCoreArtifact,
|
|
445
|
+
validateExecutionPin,
|
|
446
|
+
validateLeaseOwnedPaths,
|
|
373
447
|
};
|
|
374
448
|
}
|
|
375
449
|
|
|
450
|
+
export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
|
|
451
|
+
return parseProjectControlState({
|
|
452
|
+
workspaceRoot,
|
|
453
|
+
currentCore: currentCoreIdentity(),
|
|
454
|
+
contract: projectControlParserContract(),
|
|
455
|
+
}).upgradeSafety;
|
|
456
|
+
}
|
|
457
|
+
|
|
376
458
|
function validateProducer(producer) {
|
|
377
459
|
if (!isRecord(producer) || typeof producer.adapterKind !== "string" || typeof producer.adapterVersion !== "string") {
|
|
378
460
|
throw new Error("Producer requires adapterKind and adapterVersion.");
|
|
379
461
|
}
|
|
462
|
+
if (producer.adapterKind.length === 0 || producer.adapterVersion.length === 0) {
|
|
463
|
+
throw new Error("Producer adapterKind and adapterVersion must be non-empty strings.");
|
|
464
|
+
}
|
|
465
|
+
const producerKeys = ["adapterKind", "adapterVersion"];
|
|
466
|
+
const unsupportedProducerKeys = Object.keys(producer).filter(key => !producerKeys.includes(key));
|
|
467
|
+
if (unsupportedProducerKeys.length > 0) {
|
|
468
|
+
throw new Error(`Producer contains unsupported field: ${unsupportedProducerKeys[0]}`);
|
|
469
|
+
}
|
|
380
470
|
}
|
|
381
471
|
|
|
382
472
|
function validateExtensions(extensions) {
|
|
@@ -416,7 +506,18 @@ export function createCoreArtifact({ core = currentCoreIdentity(), producer, pay
|
|
|
416
506
|
export function validateCoreArtifact(artifact) {
|
|
417
507
|
try {
|
|
418
508
|
if (!isRecord(artifact) || !new Set(["RunKitCoreArtifactV1", "RunKitCoreArtifactV2"]).has(artifact.schemaVersion)) throw new Error("Unsupported core artifact schemaVersion.");
|
|
509
|
+
const artifactKeys = ["core", "extensions", "payload", "producer", "schemaVersion"];
|
|
510
|
+
const unsupportedArtifactKeys = Object.keys(artifact).filter(key => !artifactKeys.includes(key));
|
|
511
|
+
if (unsupportedArtifactKeys.length > 0) {
|
|
512
|
+
throw new Error(`Core artifact contains unsupported field: ${unsupportedArtifactKeys[0]}`);
|
|
513
|
+
}
|
|
419
514
|
validateCoreIdentity(artifact.core);
|
|
515
|
+
const expectedContractVersion = artifact.schemaVersion === "RunKitCoreArtifactV1"
|
|
516
|
+
? "0.1"
|
|
517
|
+
: CONTRACT_VERSION;
|
|
518
|
+
if (artifact.core.contractVersion !== expectedContractVersion) {
|
|
519
|
+
throw new Error(`Core artifact ${artifact.schemaVersion} requires contractVersion ${expectedContractVersion}.`);
|
|
520
|
+
}
|
|
420
521
|
validateProducer(artifact.producer);
|
|
421
522
|
if (!isRecord(artifact.payload)) throw new Error("Core artifact payload must be an object.");
|
|
422
523
|
validateExtensions(artifact.extensions);
|
|
@@ -429,12 +530,26 @@ export function validateCoreArtifact(artifact) {
|
|
|
429
530
|
export function acceptedCloseoutVerificationIssues(artifact) {
|
|
430
531
|
if (artifact?.core?.contractVersion !== "0.2" || artifact?.payload?.decision !== "accepted") return [];
|
|
431
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
|
+
);
|
|
432
546
|
const validFields = [
|
|
433
547
|
verification?.contractVersion === "0.2",
|
|
434
548
|
verification?.gateDecision === "accepted_passed",
|
|
435
549
|
typeof verification?.gateInputSha256 === "string" && /^[a-f0-9]{64}$/.test(verification.gateInputSha256),
|
|
436
550
|
typeof verification?.activeReceiptSha256 === "string" && /^[a-f0-9]{64}$/.test(verification.activeReceiptSha256),
|
|
437
551
|
typeof verification?.sourceFingerprint === "string" && /^[a-f0-9]{64}$/.test(verification.sourceFingerprint),
|
|
552
|
+
sourceArtifactValid,
|
|
438
553
|
typeof verification?.verificationContextFingerprint === "string" && /^[a-f0-9]{64}$/.test(verification.verificationContextFingerprint),
|
|
439
554
|
verification?.leaseState === "released",
|
|
440
555
|
Array.isArray(verification?.selectedProfileIds)
|
|
@@ -450,15 +565,27 @@ export function acceptedCloseoutVerificationIssues(artifact) {
|
|
|
450
565
|
|
|
451
566
|
export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
452
567
|
if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) throw new Error("workspaceRoot is required.");
|
|
453
|
-
const root =
|
|
454
|
-
if (
|
|
568
|
+
const root = canonicalWorkspaceRoot(workspaceRoot);
|
|
569
|
+
if (pathEntryExists(resolve(root, ".owlrunkit"))) throw new Error("Legacy .owlrunkit exists; explicit migration is required before initialization.");
|
|
455
570
|
const runtimeRoot = resolve(root, RUNTIME_ROOT);
|
|
456
|
-
|
|
457
|
-
|
|
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
|
+
);
|
|
458
578
|
const configPath = resolve(runtimeRoot, "config.json");
|
|
459
579
|
const profilesPath = resolve(runtimeRoot, "profiles.json");
|
|
460
|
-
|
|
461
|
-
|
|
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) {
|
|
462
589
|
atomicWriteJson(configPath, projectConfig());
|
|
463
590
|
} else {
|
|
464
591
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
@@ -486,8 +613,11 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
486
613
|
config,
|
|
487
614
|
migration: "config-v01-to-v02",
|
|
488
615
|
});
|
|
489
|
-
if (!
|
|
490
|
-
|
|
616
|
+
if (!profilesExist) {
|
|
617
|
+
atomicWriteJson(profilesPath, {
|
|
618
|
+
schemaVersion: "OwlCodaRunKitProfilesV1",
|
|
619
|
+
profiles: [],
|
|
620
|
+
});
|
|
491
621
|
}
|
|
492
622
|
return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
|
|
493
623
|
} else if (config.schemaVersion === "OwlCodaRunKitConfigV2" && config.core?.contractVersion === CONTRACT_VERSION) {
|
|
@@ -507,17 +637,22 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
507
637
|
config,
|
|
508
638
|
migration: "config-v02-core-refresh",
|
|
509
639
|
});
|
|
510
|
-
if (!
|
|
511
|
-
|
|
640
|
+
if (!profilesExist) {
|
|
641
|
+
atomicWriteJson(profilesPath, {
|
|
642
|
+
schemaVersion: "OwlCodaRunKitProfilesV1",
|
|
643
|
+
profiles: [],
|
|
644
|
+
});
|
|
512
645
|
}
|
|
513
646
|
return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
|
|
514
647
|
} else {
|
|
515
648
|
throw new Error(`Unsupported project config migration: ${config.schemaVersion ?? "missing schemaVersion"}.`);
|
|
516
649
|
}
|
|
517
650
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
651
|
+
if (!profilesExist) {
|
|
652
|
+
atomicWriteJson(profilesPath, {
|
|
653
|
+
schemaVersion: "OwlCodaRunKitProfilesV1",
|
|
654
|
+
profiles: [],
|
|
655
|
+
});
|
|
521
656
|
}
|
|
522
657
|
return { status: "initialized", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: currentCoreIdentity() };
|
|
523
658
|
}
|