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
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
mkdtemp,
|
|
9
9
|
readFile,
|
|
10
10
|
readdir,
|
|
11
|
-
realpath,
|
|
12
11
|
rename,
|
|
13
12
|
rm,
|
|
14
13
|
writeFile,
|
|
@@ -18,10 +17,11 @@ import { pathToFileURL } from "node:url";
|
|
|
18
17
|
|
|
19
18
|
import {
|
|
20
19
|
initializeProjectRunKit,
|
|
21
|
-
inspectProjectUpgradeSafety,
|
|
22
20
|
isDirectExecution,
|
|
23
21
|
validateProjectConfigV2,
|
|
24
22
|
} from "./core-contract.mjs";
|
|
23
|
+
import { discoverFleet } from "./fleet-discovery.mjs";
|
|
24
|
+
import { inspectProjectControlState } from "./project-control-state.mjs";
|
|
25
25
|
|
|
26
26
|
const MANIFEST_FILE = ".owlcoda-install-manifest.json";
|
|
27
27
|
|
|
@@ -193,21 +193,39 @@ export async function inspectInstalledSkill({ targetRoot } = {}) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
async function inspectFleetForSkillChange(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
export async function inspectFleetForSkillChange({
|
|
197
|
+
workspaceRoots = null,
|
|
198
|
+
fleetManifestPath = null,
|
|
199
|
+
fleetRoots = null,
|
|
200
|
+
fleetRegistryPath = null,
|
|
201
|
+
} = {}) {
|
|
202
|
+
let discovery;
|
|
203
|
+
try {
|
|
204
|
+
discovery = await discoverFleet({
|
|
205
|
+
workspaceRoots,
|
|
206
|
+
fleetManifestPath,
|
|
207
|
+
fleetRoots,
|
|
208
|
+
fleetRegistryPath,
|
|
209
|
+
});
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (
|
|
212
|
+
workspaceRoots === null
|
|
213
|
+
&& fleetManifestPath === null
|
|
214
|
+
&& fleetRoots === null
|
|
215
|
+
&& error?.message === "fleet_registry_missing"
|
|
216
|
+
) {
|
|
217
|
+
throw new Error("skill_update_fleet_registry_missing");
|
|
204
218
|
}
|
|
205
|
-
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
if (!discovery.complete) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`skill_update_fleet_discovery_incomplete:${discovery.unreachableRoots.join(",")}`,
|
|
224
|
+
);
|
|
206
225
|
}
|
|
207
|
-
const fleet = [...new Set(roots)].sort();
|
|
208
226
|
const projects = [];
|
|
209
|
-
for (const workspaceRoot of
|
|
210
|
-
const safety =
|
|
227
|
+
for (const workspaceRoot of discovery.workspaceRoots) {
|
|
228
|
+
const safety = inspectProjectControlState({ workspaceRoot }).upgradeSafety;
|
|
211
229
|
if (safety.status !== "safe") {
|
|
212
230
|
const blockers = [
|
|
213
231
|
...safety.activeRunIds,
|
|
@@ -266,6 +284,9 @@ export async function installCodexSkill({
|
|
|
266
284
|
archiveRoot,
|
|
267
285
|
expectedUnmanagedManifestSha256 = null,
|
|
268
286
|
workspaceRoots = null,
|
|
287
|
+
fleetManifestPath = null,
|
|
288
|
+
fleetRoots = null,
|
|
289
|
+
fleetRegistryPath = null,
|
|
269
290
|
} = {}) {
|
|
270
291
|
if (typeof repositoryRoot !== "string" || typeof targetRoot !== "string" || typeof archiveRoot !== "string") {
|
|
271
292
|
throw new Error("repositoryRoot, targetRoot, and archiveRoot are required");
|
|
@@ -300,7 +321,12 @@ export async function installCodexSkill({
|
|
|
300
321
|
}
|
|
301
322
|
return { status: "unchanged", manifestSha256: desired.manifestSha256, previousInstallation: null };
|
|
302
323
|
}
|
|
303
|
-
const projects = await inspectFleetForSkillChange(
|
|
324
|
+
const projects = await inspectFleetForSkillChange({
|
|
325
|
+
workspaceRoots,
|
|
326
|
+
fleetManifestPath,
|
|
327
|
+
fleetRoots,
|
|
328
|
+
fleetRegistryPath,
|
|
329
|
+
});
|
|
304
330
|
await mkdir(archiveRoot, { recursive: true });
|
|
305
331
|
const archivePath = await nextAvailablePath(
|
|
306
332
|
archiveRoot,
|
|
@@ -438,7 +464,7 @@ export async function restoreCodexSkill({
|
|
|
438
464
|
throw new Error("skill_rollback_prior_identity_mismatch");
|
|
439
465
|
}
|
|
440
466
|
const workspaceRoots = receipt.projects.map((project) => project.workspaceRoot);
|
|
441
|
-
const currentProjects = await inspectFleetForSkillChange(workspaceRoots);
|
|
467
|
+
const currentProjects = await inspectFleetForSkillChange({ workspaceRoots });
|
|
442
468
|
const receiptProjectByRoot = new Map(
|
|
443
469
|
receipt.projects.map((project) => [project.workspaceRoot, project]),
|
|
444
470
|
);
|
|
@@ -516,17 +542,22 @@ export async function recoverCodexSkillForActiveFleet({
|
|
|
516
542
|
targetRoot,
|
|
517
543
|
archiveRoot,
|
|
518
544
|
archivedInstallationPath,
|
|
519
|
-
workspaceRoots,
|
|
545
|
+
workspaceRoots = null,
|
|
546
|
+
fleetManifestPath = null,
|
|
547
|
+
fleetRoots = null,
|
|
520
548
|
} = {}) {
|
|
521
549
|
if (
|
|
522
550
|
typeof targetRoot !== "string"
|
|
523
551
|
|| typeof archiveRoot !== "string"
|
|
524
552
|
|| typeof archivedInstallationPath !== "string"
|
|
525
|
-
||
|
|
526
|
-
|
|
553
|
+
|| (
|
|
554
|
+
(!Array.isArray(workspaceRoots) || workspaceRoots.length === 0)
|
|
555
|
+
&& (typeof fleetManifestPath !== "string" || fleetManifestPath.length === 0)
|
|
556
|
+
&& (!Array.isArray(fleetRoots) || fleetRoots.length === 0)
|
|
557
|
+
)
|
|
527
558
|
) {
|
|
528
559
|
throw new Error(
|
|
529
|
-
"targetRoot, archiveRoot, archivedInstallationPath, and
|
|
560
|
+
"targetRoot, archiveRoot, archivedInstallationPath, and a fleet source are required",
|
|
530
561
|
);
|
|
531
562
|
}
|
|
532
563
|
const resolvedTarget = path.resolve(targetRoot);
|
|
@@ -553,14 +584,19 @@ export async function recoverCodexSkillForActiveFleet({
|
|
|
553
584
|
if (!archivedCore || typeof archivedCore.coreManifestSha256 !== "string") {
|
|
554
585
|
throw new Error("skill_recovery_archived_core_identity_missing");
|
|
555
586
|
}
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
587
|
+
const discovery = await discoverFleet({
|
|
588
|
+
workspaceRoots,
|
|
589
|
+
fleetManifestPath,
|
|
590
|
+
fleetRoots,
|
|
591
|
+
});
|
|
592
|
+
if (!discovery.complete) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`skill_recovery_fleet_discovery_incomplete:${discovery.unreachableRoots.join(",")}`,
|
|
595
|
+
);
|
|
559
596
|
}
|
|
560
|
-
const fleet = [...new Set(fleetRoots)].sort();
|
|
561
597
|
const projects = [];
|
|
562
598
|
let activeRunCount = 0;
|
|
563
|
-
for (const workspaceRoot of
|
|
599
|
+
for (const workspaceRoot of discovery.workspaceRoots) {
|
|
564
600
|
const configPath = path.join(
|
|
565
601
|
workspaceRoot,
|
|
566
602
|
".owlcoda/runkit/config.json",
|
|
@@ -572,7 +608,10 @@ export async function recoverCodexSkillForActiveFleet({
|
|
|
572
608
|
`skill_recovery_config_core_mismatch:${workspaceRoot}`,
|
|
573
609
|
);
|
|
574
610
|
}
|
|
575
|
-
const safety =
|
|
611
|
+
const safety = inspectProjectControlState({
|
|
612
|
+
workspaceRoot,
|
|
613
|
+
currentCore: archivedCore,
|
|
614
|
+
}).upgradeSafety;
|
|
576
615
|
if (safety.issues.length > 0) {
|
|
577
616
|
throw new Error(
|
|
578
617
|
`skill_recovery_control_state_invalid:${workspaceRoot}:${safety.issues.join(",")}`,
|
|
@@ -665,9 +704,9 @@ function parseOptions(values) {
|
|
|
665
704
|
const value = values[index + 1];
|
|
666
705
|
if (!flag?.startsWith("--") || value === undefined) throw new Error("Options must be --name value pairs");
|
|
667
706
|
const name = flag.slice(2);
|
|
668
|
-
if (name === "workspace") {
|
|
669
|
-
options
|
|
670
|
-
options.
|
|
707
|
+
if (name === "workspace" || name === "fleet-root") {
|
|
708
|
+
options[name] ??= [];
|
|
709
|
+
options[name].push(value);
|
|
671
710
|
} else {
|
|
672
711
|
options[name] = value;
|
|
673
712
|
}
|
|
@@ -686,6 +725,8 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
686
725
|
archiveRoot: options.archive,
|
|
687
726
|
expectedUnmanagedManifestSha256: options["expected-unmanaged"] ?? null,
|
|
688
727
|
workspaceRoots: options.workspace ?? null,
|
|
728
|
+
fleetManifestPath: options["fleet-manifest"] ?? null,
|
|
729
|
+
fleetRoots: options["fleet-root"] ?? null,
|
|
689
730
|
});
|
|
690
731
|
return { exitCode: 0, ...output };
|
|
691
732
|
}
|
|
@@ -706,7 +747,9 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
706
747
|
targetRoot: options.target,
|
|
707
748
|
archiveRoot: options.archive,
|
|
708
749
|
archivedInstallationPath: options["archived-installation"],
|
|
709
|
-
workspaceRoots: options.workspace,
|
|
750
|
+
workspaceRoots: options.workspace ?? null,
|
|
751
|
+
fleetManifestPath: options["fleet-manifest"] ?? null,
|
|
752
|
+
fleetRoots: options["fleet-root"] ?? null,
|
|
710
753
|
});
|
|
711
754
|
return { exitCode: 0, ...output };
|
|
712
755
|
}
|
|
@@ -196,9 +196,15 @@ function acquireControlLock(workspaceRoot) {
|
|
|
196
196
|
export function withControlTransaction(workspaceRoot, operation) {
|
|
197
197
|
const release = acquireControlLock(workspaceRoot);
|
|
198
198
|
try {
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const result = operation();
|
|
200
|
+
if (result && typeof result.then === "function") {
|
|
201
|
+
return Promise.resolve(result).finally(release);
|
|
202
|
+
}
|
|
203
|
+
release();
|
|
204
|
+
return result;
|
|
205
|
+
} catch (error) {
|
|
201
206
|
release();
|
|
207
|
+
throw error;
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
verifySnapshotSourceBinding,
|
|
31
31
|
} from "./snapshot.mjs";
|
|
32
32
|
import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
|
|
33
|
+
import { verifySourceCandidateV2 } from "./source-candidate.mjs";
|
|
33
34
|
import { validateVerificationReceiptGate } from "./verification-receipt-gate.mjs";
|
|
34
35
|
|
|
35
36
|
const LOCKFILES = [
|
|
@@ -53,6 +54,12 @@ function entryExists(filePath) {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
function sortedFileMap(value) {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
function within(root, candidate) {
|
|
57
64
|
const relative = path.relative(root, candidate);
|
|
58
65
|
return relative === ""
|
|
@@ -479,11 +486,84 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
|
|
|
479
486
|
}
|
|
480
487
|
}
|
|
481
488
|
const distinctMatchingPackets = collapseByteIdenticalDeliveryCandidates(matchingPackets);
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
489
|
+
let matchingCandidate = null;
|
|
490
|
+
if (distinctMatchingPackets.length === 0) {
|
|
491
|
+
const candidateRoot = path.join(executionRoot, "source-candidates");
|
|
492
|
+
projectTruthDirectory({
|
|
493
|
+
workspaceRoot,
|
|
494
|
+
executionRoot,
|
|
495
|
+
directoryPath: candidateRoot,
|
|
496
|
+
label: "Source candidate directory",
|
|
497
|
+
});
|
|
498
|
+
const candidates = [];
|
|
499
|
+
for (const entry of readdirSync(candidateRoot, { withFileTypes: true })) {
|
|
500
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
501
|
+
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
502
|
+
throw new Error(`Source candidate must be a regular file: ${entry.name}`);
|
|
503
|
+
}
|
|
504
|
+
const candidatePath = path.join(candidateRoot, entry.name);
|
|
505
|
+
const candidate = readJson(regularFile(
|
|
506
|
+
candidatePath,
|
|
507
|
+
`Source candidate ${entry.name}`,
|
|
508
|
+
));
|
|
509
|
+
if (
|
|
510
|
+
candidate.runId === runId
|
|
511
|
+
&& candidate.sourceFingerprint?.sha256
|
|
512
|
+
=== validated.active.receipt.sourceFingerprint
|
|
513
|
+
) {
|
|
514
|
+
const sourceGate = verifySourceCandidateV2({
|
|
515
|
+
workspaceRoot,
|
|
516
|
+
candidatePath: relativeToWorkspace(workspaceRoot, candidatePath),
|
|
517
|
+
});
|
|
518
|
+
if (sourceGate.status === "valid") {
|
|
519
|
+
candidates.push({ candidatePath, candidate, sourceGate });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const manifestFingerprints = new Set(
|
|
524
|
+
candidates.map(({ candidate }) => candidate.sourceManifest.sha256),
|
|
525
|
+
);
|
|
526
|
+
if (manifestFingerprints.size === 1) {
|
|
527
|
+
[matchingCandidate] = candidates;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (
|
|
531
|
+
matchingCandidate === null
|
|
532
|
+
&& (
|
|
533
|
+
distinctMatchingPackets.length !== 1
|
|
534
|
+
|| distinctMatchingPackets[0].sourceGate.status !== "valid"
|
|
535
|
+
)
|
|
536
|
+
) {
|
|
537
|
+
throw new Error("Accepted finish requires one fresh source binding matching the active verification gate; source drift remains unresolved.");
|
|
485
538
|
}
|
|
486
539
|
const [matchingPacket] = distinctMatchingPackets;
|
|
540
|
+
const declaredSourceArtifact = validated.active.receipt.sourceArtifact;
|
|
541
|
+
if (declaredSourceArtifact !== undefined) {
|
|
542
|
+
const selectedPath = matchingCandidate === null
|
|
543
|
+
? matchingPacket.packetPath
|
|
544
|
+
: matchingCandidate.candidatePath;
|
|
545
|
+
const expectedSourceArtifact = {
|
|
546
|
+
kind: matchingCandidate === null
|
|
547
|
+
? "delivery_packet_v1"
|
|
548
|
+
: "source_candidate_v2",
|
|
549
|
+
runId,
|
|
550
|
+
path: relativeToWorkspace(workspaceRoot, selectedPath),
|
|
551
|
+
sha256: sha256(readFileSync(selectedPath)),
|
|
552
|
+
sourceFingerprint: validated.active.receipt.sourceFingerprint,
|
|
553
|
+
};
|
|
554
|
+
if (
|
|
555
|
+
declaredSourceArtifact.kind !== expectedSourceArtifact.kind
|
|
556
|
+
|| declaredSourceArtifact.runId !== expectedSourceArtifact.runId
|
|
557
|
+
|| declaredSourceArtifact.path !== expectedSourceArtifact.path
|
|
558
|
+
|| declaredSourceArtifact.sha256 !== expectedSourceArtifact.sha256
|
|
559
|
+
|| declaredSourceArtifact.sourceFingerprint
|
|
560
|
+
!== expectedSourceArtifact.sourceFingerprint
|
|
561
|
+
) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
"Accepted finish requires the active receipt sourceArtifact to match the selected source bytes.",
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
487
567
|
const snapshotsRoot = path.join(executionRoot, "snapshots");
|
|
488
568
|
const evidenceRoot = path.join(executionRoot, "snapshot-evidence");
|
|
489
569
|
projectTruthDirectory({ workspaceRoot, executionRoot, directoryPath: snapshotsRoot, label: "Snapshot directory" });
|
|
@@ -492,14 +572,45 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
|
|
|
492
572
|
const snapshotId = safeIdentifier(commandReceipt.id, "active snapshot id");
|
|
493
573
|
const snapshotPath = path.join(snapshotsRoot, `${snapshotId}.json`);
|
|
494
574
|
const snapshot = readJson(regularFile(snapshotPath, `Active snapshot ${snapshotId}`));
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
575
|
+
if (matchingCandidate !== null) {
|
|
576
|
+
const expectedFiles = Object.fromEntries(
|
|
577
|
+
matchingCandidate.candidate.sourceManifest.entries
|
|
578
|
+
.filter((entry) => entry.operation !== "deleted")
|
|
579
|
+
.map((entry) => [entry.path, entry.sha256])
|
|
580
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
581
|
+
);
|
|
582
|
+
const beforeFiles = sortedFileMap(
|
|
583
|
+
snapshot.repositoryBefore?.selectedFiles ?? {},
|
|
584
|
+
);
|
|
585
|
+
const afterFiles = sortedFileMap(
|
|
586
|
+
snapshot.repositoryAfter?.selectedFiles ?? {},
|
|
587
|
+
);
|
|
588
|
+
const expectedInputs = Object.entries(expectedFiles)
|
|
589
|
+
.map(([id, hash]) => ({ id, sha256: hash }));
|
|
590
|
+
const actualInputs = [
|
|
591
|
+
...(snapshot.command?.evidence?.materialInputs ?? []),
|
|
592
|
+
].sort((left, right) => String(left?.id).localeCompare(String(right?.id)));
|
|
593
|
+
if (
|
|
594
|
+
snapshot.repositoryBefore?.head
|
|
595
|
+
!== matchingCandidate.candidate.baseline.head
|
|
596
|
+
|| snapshot.repositoryAfter?.head
|
|
597
|
+
!== matchingCandidate.candidate.baseline.head
|
|
598
|
+
|| JSON.stringify(beforeFiles) !== JSON.stringify(expectedFiles)
|
|
599
|
+
|| JSON.stringify(afterFiles) !== JSON.stringify(expectedFiles)
|
|
600
|
+
|| JSON.stringify(actualInputs) !== JSON.stringify(expectedInputs)
|
|
601
|
+
) {
|
|
602
|
+
throw new Error(`Accepted finish requires snapshot source bound to SourceCandidate V2: ${snapshotId}`);
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
const sourceBinding = verifySnapshotSourceBinding({
|
|
606
|
+
snapshot,
|
|
607
|
+
expectedFiles: matchingPacket.packet.changedFiles?.files
|
|
608
|
+
?? matchingPacket.packet.changedFiles?.wholeFileSha256,
|
|
609
|
+
expectedFingerprint: validated.active.receipt.sourceFingerprint,
|
|
610
|
+
});
|
|
611
|
+
if (!sourceBinding.valid) {
|
|
612
|
+
throw new Error(`Accepted finish requires snapshot source bound to the DeliveryPacket: ${snapshotId}; ${sourceBinding.issues.join("; ")}`);
|
|
613
|
+
}
|
|
503
614
|
}
|
|
504
615
|
const expectedStdoutPath = relativeToWorkspace(workspaceRoot, path.join(evidenceRoot, `${snapshotId}.stdout`));
|
|
505
616
|
const expectedStderrPath = relativeToWorkspace(workspaceRoot, path.join(evidenceRoot, `${snapshotId}.stderr`));
|
|
@@ -523,7 +634,19 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
|
|
|
523
634
|
status: "valid",
|
|
524
635
|
gateInputPath,
|
|
525
636
|
gateInputRelativePath: relativeToWorkspace(workspaceRoot, gateInputPath),
|
|
526
|
-
|
|
637
|
+
...(matchingCandidate === null
|
|
638
|
+
? {
|
|
639
|
+
deliveryPacketPath: relativeToWorkspace(
|
|
640
|
+
workspaceRoot,
|
|
641
|
+
matchingPacket.packetPath,
|
|
642
|
+
),
|
|
643
|
+
}
|
|
644
|
+
: {
|
|
645
|
+
sourceCandidatePath: relativeToWorkspace(
|
|
646
|
+
workspaceRoot,
|
|
647
|
+
matchingCandidate.candidatePath,
|
|
648
|
+
),
|
|
649
|
+
}),
|
|
527
650
|
activeReceiptSha256: gate.activeReceiptSha256,
|
|
528
651
|
authorizationGranted: false,
|
|
529
652
|
};
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
isCanonicalSha512Integrity,
|
|
19
19
|
validateStoredRegistryAdoptionEvidence,
|
|
20
20
|
} from "./registry-adoption-gate.mjs";
|
|
21
|
+
import { resolveProjectCli } from "./project-cli-resolver.mjs";
|
|
22
|
+
import { inspectProjectControlState } from "./project-control-state.mjs";
|
|
21
23
|
|
|
22
24
|
const PACKAGE_NAME = "owlrunkit";
|
|
23
25
|
const EXPECTED_VERSION = CORE_VERSION;
|
|
@@ -294,95 +296,25 @@ function strictDescendant(root, candidate) {
|
|
|
294
296
|
&& !path.isAbsolute(relative);
|
|
295
297
|
}
|
|
296
298
|
|
|
297
|
-
function assertPathWithoutSymlinks(root, target, { targetType }) {
|
|
298
|
-
const realRoot = realpathSync(root);
|
|
299
|
-
const relative = path.relative(realRoot, target);
|
|
300
|
-
if (!strictDescendant(realRoot, target)) {
|
|
301
|
-
throw new Error("installed_package_path_invalid");
|
|
302
|
-
}
|
|
303
|
-
let current = realRoot;
|
|
304
|
-
for (const segment of relative.split(path.sep)) {
|
|
305
|
-
current = path.join(current, segment);
|
|
306
|
-
const stat = lstatSync(current);
|
|
307
|
-
if (stat.isSymbolicLink()) throw new Error("installed_package_symlink_rejected");
|
|
308
|
-
}
|
|
309
|
-
const finalStat = lstatSync(target);
|
|
310
|
-
if (
|
|
311
|
-
(targetType === "file" && !finalStat.isFile())
|
|
312
|
-
|| (targetType === "directory" && !finalStat.isDirectory())
|
|
313
|
-
) {
|
|
314
|
-
throw new Error("installed_package_path_invalid");
|
|
315
|
-
}
|
|
316
|
-
const realTarget = realpathSync(target);
|
|
317
|
-
if (!strictDescendant(realRoot, realTarget)) {
|
|
318
|
-
throw new Error("installed_package_path_invalid");
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function dependencyDeclarations(manifest) {
|
|
323
|
-
return ["dependencies", "devDependencies", "optionalDependencies"]
|
|
324
|
-
.filter((section) => Object.hasOwn(manifest[section] ?? {}, PACKAGE_NAME))
|
|
325
|
-
.map((section) => ({ section, spec: manifest[section][PACKAGE_NAME] }));
|
|
326
|
-
}
|
|
327
|
-
|
|
328
299
|
export function readLocalInstallBinding({ workspaceRoot } = {}) {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (declarations.length !== 1) {
|
|
348
|
-
return {
|
|
349
|
-
status: "mismatch",
|
|
350
|
-
issueCodes: ["registry_install_binding_ambiguous"],
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
const requestedSpec = declarations[0].spec;
|
|
354
|
-
const lockBinding = lock.packages?.[`node_modules/${PACKAGE_NAME}`];
|
|
355
|
-
if (
|
|
356
|
-
requestedSpec !== EXPECTED_VERSION
|
|
357
|
-
|| lockBinding?.version !== EXPECTED_VERSION
|
|
358
|
-
|| installedManifest.name !== PACKAGE_NAME
|
|
359
|
-
|| installedManifest.version !== EXPECTED_VERSION
|
|
360
|
-
|| typeof lockBinding.resolved !== "string"
|
|
361
|
-
|| typeof lockBinding.integrity !== "string"
|
|
362
|
-
) {
|
|
363
|
-
return {
|
|
364
|
-
status: "mismatch",
|
|
365
|
-
issueCodes: ["registry_install_binding_mismatch"],
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
status: "bound",
|
|
370
|
-
requestedSpec: `${PACKAGE_NAME}@${requestedSpec}`,
|
|
371
|
-
packageName: PACKAGE_NAME,
|
|
372
|
-
version: EXPECTED_VERSION,
|
|
373
|
-
resolved: lockBinding.resolved,
|
|
374
|
-
integrity: lockBinding.integrity,
|
|
375
|
-
};
|
|
376
|
-
} catch (error) {
|
|
377
|
-
return {
|
|
378
|
-
status: "mismatch",
|
|
379
|
-
issueCodes: [
|
|
380
|
-
error instanceof Error && error.message.startsWith("installed_package_")
|
|
381
|
-
? error.message
|
|
382
|
-
: "registry_install_binding_mismatch",
|
|
383
|
-
],
|
|
384
|
-
};
|
|
385
|
-
}
|
|
300
|
+
const binding = resolveProjectCli({
|
|
301
|
+
workspaceRoot,
|
|
302
|
+
expectedVersion: EXPECTED_VERSION,
|
|
303
|
+
requireEntrypoint: false,
|
|
304
|
+
});
|
|
305
|
+
if (binding.status === "bound") return binding;
|
|
306
|
+
const issue = binding.issueCodes?.[0];
|
|
307
|
+
const issueCode = issue === "project_cli_dependency_ambiguous"
|
|
308
|
+
? "registry_install_binding_ambiguous"
|
|
309
|
+
: issue === "project_cli_symlink_rejected"
|
|
310
|
+
? "installed_package_symlink_rejected"
|
|
311
|
+
: issue === "project_cli_path_invalid"
|
|
312
|
+
? "installed_package_path_invalid"
|
|
313
|
+
: "registry_install_binding_mismatch";
|
|
314
|
+
return {
|
|
315
|
+
status: "mismatch",
|
|
316
|
+
issueCodes: [issueCode],
|
|
317
|
+
};
|
|
386
318
|
}
|
|
387
319
|
|
|
388
320
|
export async function readRegistryExact({
|
|
@@ -694,6 +626,10 @@ export async function runDoctor({
|
|
|
694
626
|
} = {}) {
|
|
695
627
|
const root = resolveBoundedWorkspaceRoot(workspaceRoot);
|
|
696
628
|
const core = currentCoreIdentity();
|
|
629
|
+
const lifecycle = inspectProjectControlState({
|
|
630
|
+
workspaceRoot: root,
|
|
631
|
+
currentCore: core,
|
|
632
|
+
});
|
|
697
633
|
const installed = readLocalInstallBinding({ workspaceRoot: root });
|
|
698
634
|
const registry = registryClient
|
|
699
635
|
? await registryClient.readExact({ packageName: PACKAGE_NAME, version: EXPECTED_VERSION })
|
|
@@ -720,7 +656,12 @@ export async function runDoctor({
|
|
|
720
656
|
},
|
|
721
657
|
config,
|
|
722
658
|
installedPackage: installed.status === "bound"
|
|
723
|
-
? {
|
|
659
|
+
? {
|
|
660
|
+
status: "ok",
|
|
661
|
+
version: installed.version,
|
|
662
|
+
cliStatus: installed.cliStatus,
|
|
663
|
+
...(installed.cliPath ? { cliPath: installed.cliPath } : {}),
|
|
664
|
+
}
|
|
724
665
|
: { status: "mismatch" },
|
|
725
666
|
registry: registry.status === "registry_verified"
|
|
726
667
|
? { status: "ok", version: registry.version }
|
|
@@ -732,6 +673,12 @@ export async function runDoctor({
|
|
|
732
673
|
detected: readProjectScripts(root),
|
|
733
674
|
},
|
|
734
675
|
runtimeIsolation,
|
|
676
|
+
lifecycle: {
|
|
677
|
+
status: lifecycle.status,
|
|
678
|
+
activeRunIds: lifecycle.activeRunIds,
|
|
679
|
+
activeLeaseIds: lifecycle.activeLeaseIds,
|
|
680
|
+
recovery: lifecycle.recovery,
|
|
681
|
+
},
|
|
735
682
|
};
|
|
736
683
|
const issueCodes = [...(registry.issueCodes ?? [])];
|
|
737
684
|
if (!nodeOk) issueCodes.push("node_version_unsupported");
|
|
@@ -740,6 +687,7 @@ export async function runDoctor({
|
|
|
740
687
|
if (config.status === "invalid") issueCodes.push("runkit_config_invalid");
|
|
741
688
|
if (profiles.status === "missing") issueCodes.push("profiles_missing");
|
|
742
689
|
if (profiles.status === "invalid") issueCodes.push("profiles_invalid");
|
|
690
|
+
if (lifecycle.status === "invalid") issueCodes.push("runkit_control_state_invalid");
|
|
743
691
|
if (installed.status !== "bound") {
|
|
744
692
|
issueCodes.push(...(installed.issueCodes ?? ["registry_install_binding_mismatch"]));
|
|
745
693
|
}
|
|
@@ -761,7 +709,9 @@ export async function runDoctor({
|
|
|
761
709
|
...(profiles.launcherWarnings ?? []),
|
|
762
710
|
].sort();
|
|
763
711
|
const nextAllowedAction = uniqueIssues.length === 0
|
|
764
|
-
?
|
|
712
|
+
? lifecycle.recovery.nextAllowedAction !== "plan_new_execution"
|
|
713
|
+
? lifecycle.recovery.nextAllowedAction
|
|
714
|
+
: (profiles.launcherWarnings ?? []).length > 0
|
|
765
715
|
? "review_profile_launchers"
|
|
766
716
|
: adoption.status === "ok"
|
|
767
717
|
? "plan_new_execution"
|
|
@@ -774,6 +724,8 @@ export async function runDoctor({
|
|
|
774
724
|
? "run_init_or_repair_config"
|
|
775
725
|
: uniqueIssues.some((code) => code.startsWith("profiles_"))
|
|
776
726
|
? "detect_or_repair_profiles"
|
|
727
|
+
: uniqueIssues.includes("runkit_control_state_invalid")
|
|
728
|
+
? "repair_execution_artifacts"
|
|
777
729
|
: "repair_registry_binding";
|
|
778
730
|
return {
|
|
779
731
|
schemaVersion: "OwlCodaRunKitDoctorReportV1",
|
|
@@ -795,6 +747,7 @@ export function formatDoctorHuman(report) {
|
|
|
795
747
|
`Registry: ${report.checks.registry.status}`,
|
|
796
748
|
`Profiles: ${report.checks.profiles.status}`,
|
|
797
749
|
`Adoption: ${report.checks.adoption.status}`,
|
|
750
|
+
`Lifecycle: ${report.checks.lifecycle.status}`,
|
|
798
751
|
...(report.issueCodes.length > 0 ? [`Issues: ${report.issueCodes.join(", ")}`] : []),
|
|
799
752
|
...(report.warningCodes.length > 0 ? [`Warnings: ${report.warningCodes.join(", ")}`] : []),
|
|
800
753
|
`Next: ${report.nextAllowedAction === "adopt_current_release"
|