owlrunkit 0.14.0 → 0.15.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 +36 -0
- package/README.md +46 -2
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +76 -14
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +1 -1
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/registry-release-policy-v1.json +1 -1
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/core-contract.mjs +283 -9
- package/scripts/runkit-contract/install-codex-skill.mjs +505 -11
- package/scripts/runkit-contract/lifecycle-orchestration.mjs +119 -4
- package/scripts/runkit-contract/onboarding-doctor.mjs +205 -7
- package/scripts/runkit-contract/profile-onboarding.mjs +188 -10
- package/scripts/runkit-contract/registry-adoption-gate.mjs +111 -0
- package/scripts/runkit-contract/registry-adoption.mjs +2 -1
- package/scripts/runkit-contract/runkit-cli.mjs +65 -23
- package/scripts/runkit-contract/schemas/verification-finalize-attempt-v1.schema.json +52 -0
- package/scripts/runkit-contract/schemas/verification-preflight-v1.schema.json +52 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
existsSync,
|
|
4
|
+
lstatSync,
|
|
4
5
|
mkdirSync,
|
|
5
6
|
readFileSync,
|
|
7
|
+
readdirSync,
|
|
6
8
|
realpathSync,
|
|
7
9
|
renameSync,
|
|
8
10
|
rmSync,
|
|
@@ -12,7 +14,7 @@ import { isAbsolute, relative, resolve, win32 } from "node:path";
|
|
|
12
14
|
import { fileURLToPath } from "node:url";
|
|
13
15
|
|
|
14
16
|
export const CONTRACT_VERSION = "0.2";
|
|
15
|
-
export const CORE_VERSION = "0.
|
|
17
|
+
export const CORE_VERSION = "0.15.1";
|
|
16
18
|
export const RUNTIME_ROOT = ".owlcoda/runkit";
|
|
17
19
|
|
|
18
20
|
const CORE_FILES = [
|
|
@@ -72,6 +74,8 @@ const CORE_FILES = [
|
|
|
72
74
|
"schemas/visual-smoke-request-v1.schema.json",
|
|
73
75
|
"schemas/visual-smoke-result-v1.schema.json",
|
|
74
76
|
"schemas/verification-context-v1.schema.json",
|
|
77
|
+
"schemas/verification-finalize-attempt-v1.schema.json",
|
|
78
|
+
"schemas/verification-preflight-v1.schema.json",
|
|
75
79
|
"schemas/verification-plan-v1.schema.json",
|
|
76
80
|
"schemas/verification-receipt-v2.schema.json",
|
|
77
81
|
"schemas/verify-plan-request-v1.schema.json",
|
|
@@ -136,6 +140,22 @@ function migrationReceiptName(migration, fromCore, toCore) {
|
|
|
136
140
|
return `config-v02-core-refresh-${from}-to-${to}.json`;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
function writeMigrationReceiptAppendOnly(receiptsRoot, baseName, receipt) {
|
|
144
|
+
const bytes = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
145
|
+
const stem = baseName.slice(0, -".json".length);
|
|
146
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
147
|
+
const receiptName = attempt === 1
|
|
148
|
+
? baseName
|
|
149
|
+
: `${stem}-attempt-${String(attempt).padStart(3, "0")}.json`;
|
|
150
|
+
try {
|
|
151
|
+
writeFileSync(resolve(receiptsRoot, receiptName), bytes, { flag: "wx" });
|
|
152
|
+
return receiptName;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error?.code !== "EEXIST") throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
139
159
|
function migrateProjectConfig({ runtimeRoot, configPath, config, migration }) {
|
|
140
160
|
validateCoreIdentity(config.core);
|
|
141
161
|
const next = projectConfig();
|
|
@@ -150,8 +170,11 @@ function migrateProjectConfig({ runtimeRoot, configPath, config, migration }) {
|
|
|
150
170
|
};
|
|
151
171
|
const receiptsRoot = resolve(runtimeRoot, "config-migration-receipts");
|
|
152
172
|
mkdirSync(receiptsRoot, { recursive: true });
|
|
153
|
-
const receiptName =
|
|
154
|
-
|
|
173
|
+
const receiptName = writeMigrationReceiptAppendOnly(
|
|
174
|
+
receiptsRoot,
|
|
175
|
+
migrationReceiptName(migration, config.core, next.core),
|
|
176
|
+
receipt,
|
|
177
|
+
);
|
|
155
178
|
atomicWriteJson(configPath, next);
|
|
156
179
|
return `${RUNTIME_ROOT}/config-migration-receipts/${receiptName}`;
|
|
157
180
|
}
|
|
@@ -211,9 +234,15 @@ export function validateLeaseOwnedPaths(paths) {
|
|
|
211
234
|
|
|
212
235
|
export function validateExecutionPin({ expected, actual } = {}) {
|
|
213
236
|
const fields = ["contractVersion", "coreVersion", "coreManifestSha256", "coreSourceRef"];
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
237
|
+
try {
|
|
238
|
+
validateCoreIdentity(expected, { requireSourceBinding: false });
|
|
239
|
+
validateCoreIdentity(actual, { requireSourceBinding: false });
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
status: "invalid_input",
|
|
243
|
+
exitCode: 3,
|
|
244
|
+
issues: [error instanceof Error ? `invalid engine pin: ${error.message}` : String(error)],
|
|
245
|
+
};
|
|
217
246
|
}
|
|
218
247
|
const issues = fields.filter((field) => expected[field] !== actual[field]).map((field) => `${field} changed during execution`);
|
|
219
248
|
return issues.length === 0
|
|
@@ -221,11 +250,27 @@ export function validateExecutionPin({ expected, actual } = {}) {
|
|
|
221
250
|
: { status: "engine_changed_during_execution", exitCode: 2, issues };
|
|
222
251
|
}
|
|
223
252
|
|
|
224
|
-
function validateCoreIdentity(core) {
|
|
253
|
+
function validateCoreIdentity(core, { requireSourceBinding = true } = {}) {
|
|
225
254
|
if (!isRecord(core)) throw new Error("Core identity must be an object.");
|
|
255
|
+
const coreKeys = ["contractVersion", "coreManifestSha256", "coreSourceRef", "coreVersion"];
|
|
256
|
+
const unsupportedCoreKeys = Object.keys(core).filter(key => !coreKeys.includes(key));
|
|
257
|
+
if (unsupportedCoreKeys.length > 0) {
|
|
258
|
+
throw new Error(`Core identity contains unsupported field: ${unsupportedCoreKeys[0]}`);
|
|
259
|
+
}
|
|
226
260
|
if (typeof core.contractVersion !== "string" || typeof core.coreVersion !== "string") throw new Error("Core identity requires contractVersion and coreVersion.");
|
|
261
|
+
if (!new Set(["0.1", CONTRACT_VERSION]).has(core.contractVersion)) {
|
|
262
|
+
throw new Error("Core identity contractVersion must be a supported RunKit contract version.");
|
|
263
|
+
}
|
|
264
|
+
if (!/^\d+\.\d+\.\d+$/u.test(core.coreVersion)) {
|
|
265
|
+
throw new Error("Core identity coreVersion must be a three-part semantic version.");
|
|
266
|
+
}
|
|
227
267
|
if (!isSha256Ref(core.coreManifestSha256)) throw new Error("Core identity requires coreManifestSha256.");
|
|
228
|
-
if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0)
|
|
268
|
+
if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0) {
|
|
269
|
+
throw new Error("Core identity coreSourceRef must be a non-empty string.");
|
|
270
|
+
}
|
|
271
|
+
if (requireSourceBinding && core.coreSourceRef !== `artifact:${core.coreManifestSha256}`) {
|
|
272
|
+
throw new Error("Core identity coreSourceRef must bind coreManifestSha256.");
|
|
273
|
+
}
|
|
229
274
|
}
|
|
230
275
|
|
|
231
276
|
export function validateProjectConfigV2(config) {
|
|
@@ -258,10 +303,207 @@ export function validateProjectConfigV2(config) {
|
|
|
258
303
|
}
|
|
259
304
|
}
|
|
260
305
|
|
|
306
|
+
function inspectTrustedCloseout(executionRoot, runId) {
|
|
307
|
+
const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
|
|
308
|
+
try {
|
|
309
|
+
const closeoutStat = lstatSync(closeoutPath);
|
|
310
|
+
if (closeoutStat.isSymbolicLink() || !closeoutStat.isFile()) {
|
|
311
|
+
return {
|
|
312
|
+
trusted: false,
|
|
313
|
+
decision: null,
|
|
314
|
+
issues: [`Closeout receipt must be a regular file: ${runId}`],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const closeout = JSON.parse(readFileSync(closeoutPath, "utf8"));
|
|
318
|
+
if (closeout?.artifact?.payload?.runId !== runId) {
|
|
319
|
+
return {
|
|
320
|
+
trusted: false,
|
|
321
|
+
decision: null,
|
|
322
|
+
issues: [`Closeout receipt runId mismatch: ${runId}`],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const artifactGate = validateCoreArtifact(closeout.artifact);
|
|
326
|
+
if (!artifactGate.valid) {
|
|
327
|
+
return {
|
|
328
|
+
trusted: false,
|
|
329
|
+
decision: null,
|
|
330
|
+
issues: artifactGate.issues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (closeout.acceptanceSha256 !== artifactGate.acceptanceSha256
|
|
334
|
+
|| closeout.artifactSha256 !== artifactGate.artifactSha256) {
|
|
335
|
+
return {
|
|
336
|
+
trusted: false,
|
|
337
|
+
decision: null,
|
|
338
|
+
issues: [`Closeout receipt hash mismatch: ${runId}`],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const payload = closeout.artifact.payload;
|
|
342
|
+
if (!new Set(["accepted", "rejected", "blocked"]).has(payload.decision)
|
|
343
|
+
|| payload.authorizationGranted !== false) {
|
|
344
|
+
return {
|
|
345
|
+
trusted: false,
|
|
346
|
+
decision: null,
|
|
347
|
+
issues: [`Closeout receipt decision or authorization is invalid: ${runId}`],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const expectedPayloadKeys = payload.decision === "accepted"
|
|
351
|
+
? ["authorizationGranted", "decision", "runId", "verification"]
|
|
352
|
+
: ["authorizationGranted", "decision", "runId"];
|
|
353
|
+
const unsupportedPayloadKeys = Object.keys(payload)
|
|
354
|
+
.filter(key => !expectedPayloadKeys.includes(key));
|
|
355
|
+
if (unsupportedPayloadKeys.length > 0) {
|
|
356
|
+
return {
|
|
357
|
+
trusted: false,
|
|
358
|
+
decision: null,
|
|
359
|
+
issues: [
|
|
360
|
+
`Closeout receipt payload contains unsupported field: ${runId}: ${unsupportedPayloadKeys[0]}`,
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const verificationIssues = acceptedCloseoutVerificationIssues(closeout.artifact);
|
|
365
|
+
if (verificationIssues.length > 0) {
|
|
366
|
+
return {
|
|
367
|
+
trusted: false,
|
|
368
|
+
decision: null,
|
|
369
|
+
issues: verificationIssues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const pinPath = resolve(executionRoot, "engine-pin.json");
|
|
373
|
+
const pinStat = lstatSync(pinPath);
|
|
374
|
+
if (pinStat.isSymbolicLink() || !pinStat.isFile()) {
|
|
375
|
+
return {
|
|
376
|
+
trusted: false,
|
|
377
|
+
decision: null,
|
|
378
|
+
issues: [`Execution engine pin must be a regular file: ${runId}`],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const pinGate = validateExecutionPin({
|
|
382
|
+
expected: JSON.parse(readFileSync(pinPath, "utf8")),
|
|
383
|
+
actual: closeout.artifact.core,
|
|
384
|
+
});
|
|
385
|
+
if (pinGate.status !== "valid") {
|
|
386
|
+
return {
|
|
387
|
+
trusted: false,
|
|
388
|
+
decision: null,
|
|
389
|
+
issues: pinGate.issues.map(issue => `Closeout engine pin mismatch: ${runId}: ${issue}`),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
trusted: true,
|
|
394
|
+
decision: payload.decision,
|
|
395
|
+
issues: [],
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return {
|
|
399
|
+
trusted: false,
|
|
400
|
+
decision: null,
|
|
401
|
+
issues: [
|
|
402
|
+
`Closeout receipt or engine pin is invalid: ${runId}: ${
|
|
403
|
+
error instanceof Error ? error.message : String(error)
|
|
404
|
+
}`,
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
|
|
411
|
+
if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
|
|
412
|
+
throw new Error("workspaceRoot is required.");
|
|
413
|
+
}
|
|
414
|
+
const root = realpathSync(workspaceRoot);
|
|
415
|
+
const executionsRoot = resolve(root, RUNTIME_ROOT, "executions");
|
|
416
|
+
if (!existsSync(executionsRoot)) {
|
|
417
|
+
return {
|
|
418
|
+
status: "safe",
|
|
419
|
+
activeRunIds: [],
|
|
420
|
+
activeLeaseIds: [],
|
|
421
|
+
issues: [],
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const executionsStat = lstatSync(executionsRoot);
|
|
425
|
+
if (executionsStat.isSymbolicLink() || !executionsStat.isDirectory()) {
|
|
426
|
+
return {
|
|
427
|
+
status: "unsafe",
|
|
428
|
+
activeRunIds: [],
|
|
429
|
+
activeLeaseIds: [],
|
|
430
|
+
issues: ["RunKit executions root must be a regular directory."],
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const activeRunIds = [];
|
|
434
|
+
const activeLeaseIds = [];
|
|
435
|
+
const issues = [];
|
|
436
|
+
const entries = readdirSync(executionsRoot, { withFileTypes: true })
|
|
437
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
438
|
+
for (const entry of entries) {
|
|
439
|
+
if (entry.isSymbolicLink()) {
|
|
440
|
+
issues.push(`Execution directory must not be a symlink: ${entry.name}`);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!entry.isDirectory()) continue;
|
|
444
|
+
const executionRoot = resolve(executionsRoot, entry.name);
|
|
445
|
+
const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
|
|
446
|
+
let skipHistoricalLeases = false;
|
|
447
|
+
if (!existsSync(closeoutPath)) {
|
|
448
|
+
activeRunIds.push(entry.name);
|
|
449
|
+
} else {
|
|
450
|
+
const closeout = inspectTrustedCloseout(executionRoot, entry.name);
|
|
451
|
+
issues.push(...closeout.issues);
|
|
452
|
+
skipHistoricalLeases = closeout.trusted
|
|
453
|
+
&& new Set(["rejected", "blocked"]).has(closeout.decision);
|
|
454
|
+
}
|
|
455
|
+
if (skipHistoricalLeases) continue;
|
|
456
|
+
const leasesRoot = resolve(executionRoot, "leases");
|
|
457
|
+
if (!existsSync(leasesRoot)) continue;
|
|
458
|
+
const leasesStat = lstatSync(leasesRoot);
|
|
459
|
+
if (leasesStat.isSymbolicLink() || !leasesStat.isDirectory()) {
|
|
460
|
+
issues.push(`Lease directory must be a regular directory: ${entry.name}`);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const leaseEntries = readdirSync(leasesRoot, { withFileTypes: true })
|
|
464
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
465
|
+
for (const leaseEntry of leaseEntries) {
|
|
466
|
+
if (!leaseEntry.name.endsWith(".json")) continue;
|
|
467
|
+
if (leaseEntry.isSymbolicLink() || !leaseEntry.isFile()) {
|
|
468
|
+
issues.push(`Lease artifact must be a regular file: ${entry.name}/${leaseEntry.name}`);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const lease = JSON.parse(readFileSync(resolve(leasesRoot, leaseEntry.name), "utf8"));
|
|
473
|
+
if (lease.state === "active") {
|
|
474
|
+
activeLeaseIds.push(`${entry.name}:${lease.workItemId ?? leaseEntry.name}`);
|
|
475
|
+
} else if (lease.state !== "released") {
|
|
476
|
+
issues.push(`Lease state is invalid: ${entry.name}/${leaseEntry.name}`);
|
|
477
|
+
}
|
|
478
|
+
} catch {
|
|
479
|
+
issues.push(`Lease artifact is invalid JSON: ${entry.name}/${leaseEntry.name}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
status: activeRunIds.length === 0
|
|
485
|
+
&& activeLeaseIds.length === 0
|
|
486
|
+
&& issues.length === 0
|
|
487
|
+
? "safe"
|
|
488
|
+
: "unsafe",
|
|
489
|
+
activeRunIds,
|
|
490
|
+
activeLeaseIds,
|
|
491
|
+
issues,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
261
495
|
function validateProducer(producer) {
|
|
262
496
|
if (!isRecord(producer) || typeof producer.adapterKind !== "string" || typeof producer.adapterVersion !== "string") {
|
|
263
497
|
throw new Error("Producer requires adapterKind and adapterVersion.");
|
|
264
498
|
}
|
|
499
|
+
if (producer.adapterKind.length === 0 || producer.adapterVersion.length === 0) {
|
|
500
|
+
throw new Error("Producer adapterKind and adapterVersion must be non-empty strings.");
|
|
501
|
+
}
|
|
502
|
+
const producerKeys = ["adapterKind", "adapterVersion"];
|
|
503
|
+
const unsupportedProducerKeys = Object.keys(producer).filter(key => !producerKeys.includes(key));
|
|
504
|
+
if (unsupportedProducerKeys.length > 0) {
|
|
505
|
+
throw new Error(`Producer contains unsupported field: ${unsupportedProducerKeys[0]}`);
|
|
506
|
+
}
|
|
265
507
|
}
|
|
266
508
|
|
|
267
509
|
function validateExtensions(extensions) {
|
|
@@ -301,7 +543,18 @@ export function createCoreArtifact({ core = currentCoreIdentity(), producer, pay
|
|
|
301
543
|
export function validateCoreArtifact(artifact) {
|
|
302
544
|
try {
|
|
303
545
|
if (!isRecord(artifact) || !new Set(["RunKitCoreArtifactV1", "RunKitCoreArtifactV2"]).has(artifact.schemaVersion)) throw new Error("Unsupported core artifact schemaVersion.");
|
|
546
|
+
const artifactKeys = ["core", "extensions", "payload", "producer", "schemaVersion"];
|
|
547
|
+
const unsupportedArtifactKeys = Object.keys(artifact).filter(key => !artifactKeys.includes(key));
|
|
548
|
+
if (unsupportedArtifactKeys.length > 0) {
|
|
549
|
+
throw new Error(`Core artifact contains unsupported field: ${unsupportedArtifactKeys[0]}`);
|
|
550
|
+
}
|
|
304
551
|
validateCoreIdentity(artifact.core);
|
|
552
|
+
const expectedContractVersion = artifact.schemaVersion === "RunKitCoreArtifactV1"
|
|
553
|
+
? "0.1"
|
|
554
|
+
: CONTRACT_VERSION;
|
|
555
|
+
if (artifact.core.contractVersion !== expectedContractVersion) {
|
|
556
|
+
throw new Error(`Core artifact ${artifact.schemaVersion} requires contractVersion ${expectedContractVersion}.`);
|
|
557
|
+
}
|
|
305
558
|
validateProducer(artifact.producer);
|
|
306
559
|
if (!isRecord(artifact.payload)) throw new Error("Core artifact payload must be an object.");
|
|
307
560
|
validateExtensions(artifact.extensions);
|
|
@@ -340,10 +593,10 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
340
593
|
const runtimeRoot = resolve(root, RUNTIME_ROOT);
|
|
341
594
|
const remainder = relative(root, runtimeRoot);
|
|
342
595
|
if (remainder.startsWith("..") || isAbsolute(remainder)) throw new Error("Runtime root escapes the workspace.");
|
|
343
|
-
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
344
596
|
const configPath = resolve(runtimeRoot, "config.json");
|
|
345
597
|
const profilesPath = resolve(runtimeRoot, "profiles.json");
|
|
346
598
|
if (!existsSync(configPath)) {
|
|
599
|
+
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
347
600
|
atomicWriteJson(configPath, projectConfig());
|
|
348
601
|
} else {
|
|
349
602
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
@@ -355,6 +608,16 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
355
608
|
if (config.schemaVersion === "OwlCodaRunKitConfigV2" && currentGate.status === "valid") {
|
|
356
609
|
// The project already points at this exact Core identity.
|
|
357
610
|
} else if (config.schemaVersion === "OwlCodaRunKitConfigV1" && config.core?.contractVersion === "0.1") {
|
|
611
|
+
const safety = inspectProjectUpgradeSafety({ workspaceRoot: root });
|
|
612
|
+
if (safety.status !== "safe") {
|
|
613
|
+
return {
|
|
614
|
+
...safety,
|
|
615
|
+
status: "upgrade_blocked_active_execution",
|
|
616
|
+
exitCode: 2,
|
|
617
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
618
|
+
core: current,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
358
621
|
const migrationReceipt = migrateProjectConfig({
|
|
359
622
|
runtimeRoot,
|
|
360
623
|
configPath,
|
|
@@ -366,6 +629,16 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
366
629
|
}
|
|
367
630
|
return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
|
|
368
631
|
} else if (config.schemaVersion === "OwlCodaRunKitConfigV2" && config.core?.contractVersion === CONTRACT_VERSION) {
|
|
632
|
+
const safety = inspectProjectUpgradeSafety({ workspaceRoot: root });
|
|
633
|
+
if (safety.status !== "safe") {
|
|
634
|
+
return {
|
|
635
|
+
...safety,
|
|
636
|
+
status: "upgrade_blocked_active_execution",
|
|
637
|
+
exitCode: 2,
|
|
638
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
639
|
+
core: current,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
369
642
|
const migrationReceipt = migrateProjectConfig({
|
|
370
643
|
runtimeRoot,
|
|
371
644
|
configPath,
|
|
@@ -380,6 +653,7 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
380
653
|
throw new Error(`Unsupported project config migration: ${config.schemaVersion ?? "missing schemaVersion"}.`);
|
|
381
654
|
}
|
|
382
655
|
}
|
|
656
|
+
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
383
657
|
if (!existsSync(profilesPath)) {
|
|
384
658
|
writeFileSync(profilesPath, `${JSON.stringify({ schemaVersion: "OwlCodaRunKitProfilesV1", profiles: [] }, null, 2)}\n`);
|
|
385
659
|
}
|