owlrunkit 0.19.0 → 0.19.2
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 +48 -0
- package/README.md +81 -3
- package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +12 -9
- package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +36 -0
- package/docs/architecture/OWLCODA_RUNKIT_0192_FORMAL_USABILITY_PRODUCT_DECISION_20260813.md +141 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +52 -9
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +42 -4
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/core-contract.mjs +35 -2
- package/scripts/runkit-contract/efficiency-metrics.mjs +9 -7
- package/scripts/runkit-contract/formal-preflight.mjs +352 -0
- package/scripts/runkit-contract/formal-supplemental-evidence.mjs +713 -0
- package/scripts/runkit-contract/formal-workflow.mjs +529 -59
- package/scripts/runkit-contract/onboarding-doctor.mjs +32 -13
- package/scripts/runkit-contract/profile-onboarding.mjs +52 -33
- package/scripts/runkit-contract/project-cli-resolver.mjs +33 -3
- package/scripts/runkit-contract/project-external-delivery.mjs +16 -9
- package/scripts/runkit-contract/quick-attest.mjs +24 -0
- package/scripts/runkit-contract/runkit-cli.mjs +365 -49
- package/scripts/runkit-contract/schemas/formal-check-v2.schema.json +231 -0
- package/scripts/runkit-contract/schemas/formal-envelope-preflight-v1.schema.json +212 -0
- package/scripts/runkit-contract/schemas/formal-evidence-summary-v1.schema.json +114 -0
- package/scripts/runkit-contract/schemas/formal-supplemental-evidence-v1.schema.json +103 -0
- package/scripts/runkit-contract/schemas/team-project-event-v5.schema.json +89 -0
- package/scripts/runkit-contract/schemas/team-project-status-v3.schema.json +173 -0
- package/scripts/runkit-contract/snapshot.mjs +125 -5
- package/scripts/runkit-contract/source-candidate.mjs +271 -69
- package/scripts/runkit-contract/sync-release-identity.mjs +39 -4
- package/scripts/runkit-contract/team-project-successor.mjs +7 -4
- package/scripts/runkit-contract/team-project.mjs +440 -28
- package/scripts/runkit-contract/usage-mode.mjs +9 -3
- package/scripts/runkit-contract/verification-envelope.mjs +425 -37
|
@@ -28,10 +28,15 @@ import {
|
|
|
28
28
|
relativeToWorkspace,
|
|
29
29
|
resolveExistingArtifact,
|
|
30
30
|
safeIdentifier,
|
|
31
|
+
safeRelativePath,
|
|
31
32
|
sha256,
|
|
32
33
|
writeJsonAtomically,
|
|
33
34
|
writeJsonExclusiveAtomically,
|
|
34
35
|
} from "./provenance-common.mjs";
|
|
36
|
+
|
|
37
|
+
export const SOURCE_CANDIDATE_V2_MAX_BYTES = 64 * 1024 * 1024;
|
|
38
|
+
export const SOURCE_CANDIDATE_V2_MAX_NODES = 2_000_000;
|
|
39
|
+
import { readWorkspaceJsonBounded } from "./onboarding-doctor.mjs";
|
|
35
40
|
import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
|
|
36
41
|
|
|
37
42
|
function canonicalJson(value) {
|
|
@@ -51,6 +56,18 @@ function candidateHash(value) {
|
|
|
51
56
|
return `sha256:${sha256(canonicalJson(value))}`;
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
function hasExactKeys(value, keys) {
|
|
60
|
+
return Boolean(value)
|
|
61
|
+
&& typeof value === "object"
|
|
62
|
+
&& !Array.isArray(value)
|
|
63
|
+
&& canonicalJson(Object.keys(value).sort()) === canonicalJson([...keys].sort());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validIdentifier(value) {
|
|
67
|
+
return typeof value === "string"
|
|
68
|
+
&& /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
function repositoryActionsFalse() {
|
|
55
72
|
return {
|
|
56
73
|
worktreeCreated: false,
|
|
@@ -281,15 +298,56 @@ function sourceManifestV2({ workspaceRoot, ownedPaths }) {
|
|
|
281
298
|
};
|
|
282
299
|
}
|
|
283
300
|
|
|
284
|
-
function
|
|
301
|
+
function sourceCandidateNodeCount(value) {
|
|
302
|
+
let count = 0;
|
|
303
|
+
const pending = [value];
|
|
304
|
+
while (pending.length > 0) {
|
|
305
|
+
const current = pending.pop();
|
|
306
|
+
count += 1;
|
|
307
|
+
if (Array.isArray(current)) {
|
|
308
|
+
pending.push(...current);
|
|
309
|
+
} else if (current !== null && typeof current === "object") {
|
|
310
|
+
pending.push(...Object.values(current));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return count;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function validateSourceCandidateDocumentBoundsV2(value, {
|
|
317
|
+
maxBytes = SOURCE_CANDIDATE_V2_MAX_BYTES,
|
|
318
|
+
maxNodes = SOURCE_CANDIDATE_V2_MAX_NODES,
|
|
319
|
+
} = {}) {
|
|
320
|
+
const bytes = Buffer.byteLength(`${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
321
|
+
const nodes = sourceCandidateNodeCount(value);
|
|
322
|
+
return {
|
|
323
|
+
valid: bytes <= maxBytes && nodes <= maxNodes,
|
|
324
|
+
bytes,
|
|
325
|
+
nodes,
|
|
326
|
+
maxBytes,
|
|
327
|
+
maxNodes,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function assertSourceCandidateDocumentBoundsV2(value) {
|
|
332
|
+
const result = validateSourceCandidateDocumentBoundsV2(value);
|
|
333
|
+
if (!result.valid) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
"SourceCandidateV2 exceeds its supported serialized size or JSON node limit.",
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function planPayloadV1({
|
|
285
342
|
workspaceRoot,
|
|
286
343
|
candidateRoot,
|
|
287
344
|
candidateId,
|
|
288
345
|
manifest,
|
|
289
346
|
}) {
|
|
290
347
|
const payloadRoot = path.join(candidateRoot, `${candidateId}.payload`);
|
|
291
|
-
|
|
292
|
-
|
|
348
|
+
if (existsSync(payloadRoot)) {
|
|
349
|
+
throw new Error(`source candidate payload already exists: ${candidateId}`);
|
|
350
|
+
}
|
|
293
351
|
const payloadRootRelative = relativeToWorkspace(workspaceRoot, payloadRoot);
|
|
294
352
|
const entries = [];
|
|
295
353
|
const blobs = new Map();
|
|
@@ -308,13 +366,10 @@ function freezePayloadV1({
|
|
|
308
366
|
}
|
|
309
367
|
const payloadPath = path.join(payloadRoot, "sha256", digest);
|
|
310
368
|
if (!blobs.has(digest)) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|| sha256(readFileSync(payloadPath)) !== digest
|
|
316
|
-
) {
|
|
317
|
-
throw new Error(`Source payload blob drifted during freeze: ${digest}`);
|
|
369
|
+
blobs.set(digest, {
|
|
370
|
+
sourcePath,
|
|
371
|
+
sizeBytes: bytes.length,
|
|
372
|
+
});
|
|
318
373
|
}
|
|
319
374
|
entries.push({
|
|
320
375
|
path: manifestEntry.path,
|
|
@@ -329,15 +384,46 @@ function freezePayloadV1({
|
|
|
329
384
|
storage: "create_only_content_addressed_sha256",
|
|
330
385
|
root: payloadRootRelative,
|
|
331
386
|
blobCount: blobs.size,
|
|
332
|
-
totalBytes: [...blobs.values()]
|
|
387
|
+
totalBytes: [...blobs.values()]
|
|
388
|
+
.reduce((total, row) => total + row.sizeBytes, 0),
|
|
333
389
|
entries,
|
|
334
390
|
};
|
|
335
391
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
392
|
+
payload: {
|
|
393
|
+
...body,
|
|
394
|
+
sha256: candidateHash(body),
|
|
395
|
+
},
|
|
396
|
+
blobs,
|
|
397
|
+
payloadRoot,
|
|
338
398
|
};
|
|
339
399
|
}
|
|
340
400
|
|
|
401
|
+
function persistPayloadPlanV1({ candidateRoot, plan }) {
|
|
402
|
+
if (!existsSync(candidateRoot)) mkdirSync(candidateRoot);
|
|
403
|
+
const candidateRootStat = lstatSync(candidateRoot);
|
|
404
|
+
if (
|
|
405
|
+
candidateRootStat.isSymbolicLink()
|
|
406
|
+
|| !candidateRootStat.isDirectory()
|
|
407
|
+
|| realpathSync(candidateRoot) !== path.resolve(candidateRoot)
|
|
408
|
+
) {
|
|
409
|
+
throw new Error("Source candidate directory must be a regular directory.");
|
|
410
|
+
}
|
|
411
|
+
mkdirSync(plan.payloadRoot);
|
|
412
|
+
const blobRoot = path.join(plan.payloadRoot, "sha256");
|
|
413
|
+
mkdirSync(blobRoot);
|
|
414
|
+
for (const [digest, row] of plan.blobs) {
|
|
415
|
+
const bytes = readFileSync(row.sourcePath);
|
|
416
|
+
if (bytes.length !== row.sizeBytes || sha256(bytes) !== digest) {
|
|
417
|
+
throw new Error(`Source changed while persisting payload: ${digest}`);
|
|
418
|
+
}
|
|
419
|
+
writeFileSync(path.join(blobRoot, digest), bytes, {
|
|
420
|
+
flag: "wx",
|
|
421
|
+
mode: 0o600,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return plan.payload;
|
|
425
|
+
}
|
|
426
|
+
|
|
341
427
|
function payloadContractIssues({
|
|
342
428
|
workspaceRoot,
|
|
343
429
|
candidatePath,
|
|
@@ -354,7 +440,16 @@ function payloadContractIssues({
|
|
|
354
440
|
(entry) => entry.operation !== "deleted",
|
|
355
441
|
);
|
|
356
442
|
if (
|
|
357
|
-
payload
|
|
443
|
+
!hasExactKeys(payload, [
|
|
444
|
+
"schemaVersion",
|
|
445
|
+
"storage",
|
|
446
|
+
"root",
|
|
447
|
+
"blobCount",
|
|
448
|
+
"totalBytes",
|
|
449
|
+
"entries",
|
|
450
|
+
"sha256",
|
|
451
|
+
])
|
|
452
|
+
|| payload?.schemaVersion !== "OwlCodaRunKitSourcePayloadV1"
|
|
358
453
|
|| payload.storage !== "create_only_content_addressed_sha256"
|
|
359
454
|
|| payload.root !== expectedRoot
|
|
360
455
|
|| !Number.isInteger(payload.blobCount)
|
|
@@ -376,7 +471,14 @@ function payloadContractIssues({
|
|
|
376
471
|
for (const entry of payload.entries) {
|
|
377
472
|
const manifestEntry = expectedByPath.get(entry?.path);
|
|
378
473
|
if (
|
|
379
|
-
!
|
|
474
|
+
!hasExactKeys(entry, [
|
|
475
|
+
"path",
|
|
476
|
+
"payloadPath",
|
|
477
|
+
"sha256",
|
|
478
|
+
"sizeBytes",
|
|
479
|
+
"mode",
|
|
480
|
+
])
|
|
481
|
+
|| !manifestEntry
|
|
380
482
|
|| seenPaths.has(entry.path)
|
|
381
483
|
|| entry.sha256 !== manifestEntry.sha256
|
|
382
484
|
|| !Number.isInteger(entry.sizeBytes)
|
|
@@ -438,7 +540,14 @@ function activeLease({ workspaceRoot, executionRoot, workItemId }) {
|
|
|
438
540
|
|
|
439
541
|
function manifestContractValid(manifest) {
|
|
440
542
|
if (
|
|
441
|
-
manifest
|
|
543
|
+
!hasExactKeys(manifest, [
|
|
544
|
+
"schemaVersion",
|
|
545
|
+
"statusMode",
|
|
546
|
+
"ownedPaths",
|
|
547
|
+
"entries",
|
|
548
|
+
"sha256",
|
|
549
|
+
])
|
|
550
|
+
|| manifest?.schemaVersion !== "OwlCodaRunKitDirtySourceManifestV2"
|
|
442
551
|
|| manifest.statusMode !== "porcelain-v1-z-untracked-all-runkit-excluded"
|
|
443
552
|
|| !Array.isArray(manifest.ownedPaths)
|
|
444
553
|
|| manifest.ownedPaths.length === 0
|
|
@@ -450,8 +559,14 @@ function manifestContractValid(manifest) {
|
|
|
450
559
|
return false;
|
|
451
560
|
}
|
|
452
561
|
for (const entry of manifest.entries) {
|
|
562
|
+
const expectedEntryKeys = entry?.operation === "renamed"
|
|
563
|
+
? ["operation", "status", "path", "previousPath", "sha256"]
|
|
564
|
+
: entry?.operation === "deleted"
|
|
565
|
+
? ["operation", "status", "path"]
|
|
566
|
+
: ["operation", "status", "path", "sha256"];
|
|
453
567
|
if (
|
|
454
568
|
!entry
|
|
569
|
+
|| !hasExactKeys(entry, expectedEntryKeys)
|
|
455
570
|
|| !["added", "deleted", "modified", "renamed"].includes(entry.operation)
|
|
456
571
|
|| typeof entry.status !== "string"
|
|
457
572
|
|| entry.status.length !== 2
|
|
@@ -513,18 +628,39 @@ function repositoryActionsAreFalse(actions) {
|
|
|
513
628
|
function candidateV2ContractIssues(candidate) {
|
|
514
629
|
const { candidateSha256, ...body } = candidate ?? {};
|
|
515
630
|
if (
|
|
516
|
-
candidate
|
|
631
|
+
!hasExactKeys(candidate, [
|
|
632
|
+
"schemaVersion",
|
|
633
|
+
"runId",
|
|
634
|
+
"candidateId",
|
|
635
|
+
"status",
|
|
636
|
+
"sourceMode",
|
|
637
|
+
"baseline",
|
|
638
|
+
"sourceManifest",
|
|
639
|
+
"sourceFingerprint",
|
|
640
|
+
"payload",
|
|
641
|
+
"discovery",
|
|
642
|
+
"repositoryActions",
|
|
643
|
+
"authorizationGranted",
|
|
644
|
+
"candidateSha256",
|
|
645
|
+
])
|
|
646
|
+
|| candidate?.schemaVersion !== "OwlCodaRunKitSourceCandidateV2"
|
|
517
647
|
|| candidate.status !== "frozen"
|
|
518
648
|
|| candidate.sourceMode !== "dirty_worktree_exact_manifest_v2"
|
|
519
649
|
|| candidate.authorizationGranted !== false
|
|
520
|
-
||
|
|
650
|
+
|| !/^sha256:[a-f0-9]{64}$/u.test(candidateSha256)
|
|
521
651
|
|| candidateSha256 !== candidateHash(body)
|
|
522
|
-
||
|
|
523
|
-
||
|
|
524
|
-
||
|
|
525
|
-
||
|
|
526
|
-
||
|
|
527
|
-
||
|
|
652
|
+
|| !validIdentifier(candidate.runId)
|
|
653
|
+
|| !validIdentifier(candidate.candidateId)
|
|
654
|
+
|| !hasExactKeys(candidate.baseline, ["branch", "head"])
|
|
655
|
+
|| typeof candidate.baseline.branch !== "string"
|
|
656
|
+
|| candidate.baseline.branch.length === 0
|
|
657
|
+
|| !/^[a-f0-9]{40}$/u.test(candidate.baseline.head)
|
|
658
|
+
|| !hasExactKeys(candidate.sourceFingerprint, ["sha256"])
|
|
659
|
+
|| !/^[a-f0-9]{64}$/u.test(candidate.sourceFingerprint.sha256)
|
|
660
|
+
|| !hasExactKeys(candidate.discovery, ["fromLease", "leasePath"])
|
|
661
|
+
|| !validIdentifier(candidate.discovery.fromLease)
|
|
662
|
+
|| typeof candidate.discovery.leasePath !== "string"
|
|
663
|
+
|| candidate.discovery.leasePath.length === 0
|
|
528
664
|
|| !repositoryActionsAreFalse(candidate.repositoryActions)
|
|
529
665
|
|| !manifestContractValid(candidate.sourceManifest)
|
|
530
666
|
|| candidate.sourceFingerprint?.sha256
|
|
@@ -643,13 +779,14 @@ export function freezeSourceCandidateWithinControlTransactionV2({
|
|
|
643
779
|
const { executionRoot, pinGate } = loadActiveExecution(root, runId);
|
|
644
780
|
if (pinGate.status !== "valid") return { ...pinGate, authorizationGranted: false };
|
|
645
781
|
const candidateRoot = path.join(executionRoot, "source-candidates");
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
782
|
+
if (existsSync(candidateRoot)) {
|
|
783
|
+
const candidateRootStat = lstatSync(candidateRoot);
|
|
784
|
+
if (candidateRootStat.isSymbolicLink() || !candidateRootStat.isDirectory()) {
|
|
785
|
+
throw new Error("Source candidate directory must be a regular directory.");
|
|
786
|
+
}
|
|
787
|
+
if (!within(realpathSync(executionRoot), realpathSync(candidateRoot))) {
|
|
788
|
+
throw new Error("Source candidate directory escapes its execution root.");
|
|
789
|
+
}
|
|
653
790
|
}
|
|
654
791
|
const candidatePath = path.join(candidateRoot, `${candidateId}.json`);
|
|
655
792
|
if (existsSync(candidatePath)) {
|
|
@@ -665,12 +802,43 @@ export function freezeSourceCandidateWithinControlTransactionV2({
|
|
|
665
802
|
workspaceRoot: root,
|
|
666
803
|
ownedPaths: lease.ownedPaths,
|
|
667
804
|
});
|
|
668
|
-
const
|
|
805
|
+
const payloadPlan = planPayloadV1({
|
|
669
806
|
workspaceRoot: root,
|
|
670
807
|
candidateRoot,
|
|
671
808
|
candidateId,
|
|
672
809
|
manifest: statusBefore,
|
|
673
810
|
});
|
|
811
|
+
const buildCandidate = ({ baseline, sourceManifest, payload }) => {
|
|
812
|
+
const body = {
|
|
813
|
+
schemaVersion: "OwlCodaRunKitSourceCandidateV2",
|
|
814
|
+
runId,
|
|
815
|
+
candidateId,
|
|
816
|
+
status: "frozen",
|
|
817
|
+
sourceMode: "dirty_worktree_exact_manifest_v2",
|
|
818
|
+
baseline,
|
|
819
|
+
sourceManifest,
|
|
820
|
+
sourceFingerprint: {
|
|
821
|
+
sha256: sourceManifest.sha256.slice("sha256:".length),
|
|
822
|
+
},
|
|
823
|
+
payload,
|
|
824
|
+
discovery: {
|
|
825
|
+
fromLease: workItemId,
|
|
826
|
+
leasePath: lease.leaseRelativePath,
|
|
827
|
+
},
|
|
828
|
+
repositoryActions: repositoryActionsFalse(),
|
|
829
|
+
authorizationGranted: false,
|
|
830
|
+
};
|
|
831
|
+
return {
|
|
832
|
+
...body,
|
|
833
|
+
candidateSha256: candidateHash(body),
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
assertSourceCandidateDocumentBoundsV2(buildCandidate({
|
|
837
|
+
baseline: baselineBefore,
|
|
838
|
+
sourceManifest: statusBefore,
|
|
839
|
+
payload: payloadPlan.payload,
|
|
840
|
+
}));
|
|
841
|
+
const payload = persistPayloadPlanV1({ candidateRoot, plan: payloadPlan });
|
|
674
842
|
const statusAfter = sourceManifestV2({
|
|
675
843
|
workspaceRoot: root,
|
|
676
844
|
ownedPaths: lease.ownedPaths,
|
|
@@ -682,29 +850,12 @@ export function freezeSourceCandidateWithinControlTransactionV2({
|
|
|
682
850
|
) {
|
|
683
851
|
throw new Error("Owned workspace source changed during candidate freeze.");
|
|
684
852
|
}
|
|
685
|
-
const
|
|
686
|
-
schemaVersion: "OwlCodaRunKitSourceCandidateV2",
|
|
687
|
-
runId,
|
|
688
|
-
candidateId,
|
|
689
|
-
status: "frozen",
|
|
690
|
-
sourceMode: "dirty_worktree_exact_manifest_v2",
|
|
853
|
+
const candidate = buildCandidate({
|
|
691
854
|
baseline: baselineAfter,
|
|
692
855
|
sourceManifest: statusAfter,
|
|
693
|
-
sourceFingerprint: {
|
|
694
|
-
sha256: statusAfter.sha256.slice("sha256:".length),
|
|
695
|
-
},
|
|
696
856
|
payload,
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
leasePath: lease.leaseRelativePath,
|
|
700
|
-
},
|
|
701
|
-
repositoryActions: repositoryActionsFalse(),
|
|
702
|
-
authorizationGranted: false,
|
|
703
|
-
};
|
|
704
|
-
const candidate = {
|
|
705
|
-
...body,
|
|
706
|
-
candidateSha256: candidateHash(body),
|
|
707
|
-
};
|
|
857
|
+
});
|
|
858
|
+
assertSourceCandidateDocumentBoundsV2(candidate);
|
|
708
859
|
writeJsonExclusiveAtomically(candidatePath, candidate);
|
|
709
860
|
return {
|
|
710
861
|
status: "source_candidate_frozen",
|
|
@@ -721,6 +872,49 @@ export function freezeSourceCandidateWithinControlTransactionV2({
|
|
|
721
872
|
};
|
|
722
873
|
}
|
|
723
874
|
|
|
875
|
+
export function previewSourceCandidateWithinControlTransactionV2({
|
|
876
|
+
workspaceRoot,
|
|
877
|
+
runId,
|
|
878
|
+
workItemId,
|
|
879
|
+
}) {
|
|
880
|
+
const root = realpathSync(workspaceRoot);
|
|
881
|
+
const { executionRoot, pinGate } = loadActiveExecution(root, runId);
|
|
882
|
+
if (pinGate.status !== "valid") {
|
|
883
|
+
return { ...pinGate, authorizationGranted: false };
|
|
884
|
+
}
|
|
885
|
+
const lease = activeLease({
|
|
886
|
+
workspaceRoot: root,
|
|
887
|
+
executionRoot,
|
|
888
|
+
workItemId,
|
|
889
|
+
});
|
|
890
|
+
const baselineBefore = currentBaseline(root);
|
|
891
|
+
const manifestBefore = sourceManifestV2({
|
|
892
|
+
workspaceRoot: root,
|
|
893
|
+
ownedPaths: lease.ownedPaths,
|
|
894
|
+
});
|
|
895
|
+
const manifestAfter = sourceManifestV2({
|
|
896
|
+
workspaceRoot: root,
|
|
897
|
+
ownedPaths: lease.ownedPaths,
|
|
898
|
+
});
|
|
899
|
+
const baselineAfter = currentBaseline(root);
|
|
900
|
+
if (
|
|
901
|
+
manifestBefore.sha256 !== manifestAfter.sha256
|
|
902
|
+
|| canonicalJson(baselineBefore) !== canonicalJson(baselineAfter)
|
|
903
|
+
) {
|
|
904
|
+
throw new Error("Owned workspace source changed during candidate preview.");
|
|
905
|
+
}
|
|
906
|
+
return {
|
|
907
|
+
status: "source_candidate_previewed",
|
|
908
|
+
exitCode: 0,
|
|
909
|
+
runId,
|
|
910
|
+
workItemId,
|
|
911
|
+
baseline: baselineAfter,
|
|
912
|
+
sourceManifest: manifestAfter,
|
|
913
|
+
sourceFingerprint: manifestAfter.sha256.slice("sha256:".length),
|
|
914
|
+
authorizationGranted: false,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
724
918
|
export function freezeSourceCandidateV2(options) {
|
|
725
919
|
const workspaceRoot = realpathSync(options.workspaceRoot);
|
|
726
920
|
return withControlTransaction(
|
|
@@ -732,31 +926,31 @@ export function freezeSourceCandidateV2(options) {
|
|
|
732
926
|
);
|
|
733
927
|
}
|
|
734
928
|
|
|
735
|
-
export function
|
|
929
|
+
export function verifySourceCandidateDetailsV2({
|
|
736
930
|
workspaceRoot,
|
|
737
931
|
candidatePath,
|
|
932
|
+
requireCurrentWorkspace = true,
|
|
738
933
|
}) {
|
|
739
934
|
const root = realpathSync(workspaceRoot);
|
|
740
935
|
const issueCodes = [];
|
|
741
936
|
let absoluteCandidatePath;
|
|
742
937
|
let candidate;
|
|
743
938
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
751
|
-
throw new Error("candidate is not a regular file");
|
|
752
|
-
}
|
|
753
|
-
candidate = readJson(absoluteCandidatePath);
|
|
939
|
+
const relativeCandidatePath = safeRelativePath(candidatePath, "candidatePath");
|
|
940
|
+
candidate = readWorkspaceJsonBounded(root, relativeCandidatePath, {
|
|
941
|
+
maxBytes: SOURCE_CANDIDATE_V2_MAX_BYTES,
|
|
942
|
+
maxNodes: SOURCE_CANDIDATE_V2_MAX_NODES,
|
|
943
|
+
});
|
|
944
|
+
absoluteCandidatePath = path.join(root, relativeCandidatePath);
|
|
754
945
|
} catch {
|
|
755
946
|
return {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
947
|
+
gate: {
|
|
948
|
+
status: "invalid",
|
|
949
|
+
exitCode: 2,
|
|
950
|
+
issueCodes: ["source_candidate_unreadable"],
|
|
951
|
+
authorizationGranted: false,
|
|
952
|
+
},
|
|
953
|
+
candidateDocument: null,
|
|
760
954
|
};
|
|
761
955
|
}
|
|
762
956
|
const candidateSha256 = candidate.candidateSha256;
|
|
@@ -792,7 +986,7 @@ export function verifySourceCandidateV2({
|
|
|
792
986
|
issueCodes.push("source_candidate_lease_unreadable");
|
|
793
987
|
}
|
|
794
988
|
}
|
|
795
|
-
if (issueCodes.length === 0) {
|
|
989
|
+
if (issueCodes.length === 0 && requireCurrentWorkspace) {
|
|
796
990
|
try {
|
|
797
991
|
const currentManifest = sourceManifestV2({
|
|
798
992
|
workspaceRoot: root,
|
|
@@ -812,7 +1006,7 @@ export function verifySourceCandidateV2({
|
|
|
812
1006
|
issueCodes.push("source_candidate_baseline_drift");
|
|
813
1007
|
}
|
|
814
1008
|
}
|
|
815
|
-
|
|
1009
|
+
const gate = {
|
|
816
1010
|
status: issueCodes.length === 0 ? "valid" : "invalid",
|
|
817
1011
|
exitCode: issueCodes.length === 0 ? 0 : 2,
|
|
818
1012
|
candidatePath: relativeToWorkspace(root, absoluteCandidatePath),
|
|
@@ -821,6 +1015,14 @@ export function verifySourceCandidateV2({
|
|
|
821
1015
|
issueCodes: [...new Set(issueCodes)].sort(),
|
|
822
1016
|
authorizationGranted: false,
|
|
823
1017
|
};
|
|
1018
|
+
return {
|
|
1019
|
+
gate,
|
|
1020
|
+
candidateDocument: gate.status === "valid" ? candidate : null,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export function verifySourceCandidateV2(options) {
|
|
1025
|
+
return verifySourceCandidateDetailsV2(options).gate;
|
|
824
1026
|
}
|
|
825
1027
|
|
|
826
1028
|
class SimulatedMaterializationInterruption extends Error {
|
|
@@ -153,14 +153,49 @@ export function verifyReleaseIdentitySurfacesV1(input) {
|
|
|
153
153
|
const matches = [...source.matchAll(pattern)];
|
|
154
154
|
return matches.length === 1 && matches[0][1] === core.coreManifestSha256;
|
|
155
155
|
};
|
|
156
|
+
const releasePolicy = input.releasePolicy ?? null;
|
|
157
|
+
const candidateUnpublished = releasePolicy?.status === "release_candidate_unpublished";
|
|
158
|
+
if (candidateUnpublished) {
|
|
159
|
+
requireSurface(
|
|
160
|
+
releasePolicy?.packageName === "owlrunkit"
|
|
161
|
+
&& releasePolicy?.candidateVersion === core.coreVersion
|
|
162
|
+
&& releasePolicy?.registryRelease === null
|
|
163
|
+
&& releasePolicy?.currentPublishedRelease?.packageName === "owlrunkit"
|
|
164
|
+
&& /^\d+\.\d+\.\d+$/u.test(
|
|
165
|
+
releasePolicy?.currentPublishedRelease?.version ?? "",
|
|
166
|
+
)
|
|
167
|
+
&& releasePolicy.currentPublishedRelease.version !== core.coreVersion,
|
|
168
|
+
"release_policy",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
156
171
|
const skillExact = (markdown) => {
|
|
157
172
|
if (typeof markdown !== "string") return false;
|
|
158
173
|
const hashes = markdown.match(/sha256:[a-f0-9]{64}/gu) ?? [];
|
|
159
|
-
|
|
174
|
+
const identityExact = hashes.length === 1
|
|
160
175
|
&& hashes[0] === core.coreManifestSha256
|
|
161
|
-
&& markdown.includes(`
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
&& markdown.includes(`Core \`${core.coreVersion}\``);
|
|
177
|
+
if (!identityExact) return false;
|
|
178
|
+
if (!candidateUnpublished) {
|
|
179
|
+
return markdown.includes(
|
|
180
|
+
`Bundled release: standalone \`owlrunkit@${core.coreVersion}\``,
|
|
181
|
+
) && !/(?:not published|current published release remains)/iu.test(markdown);
|
|
182
|
+
}
|
|
183
|
+
const prior = releasePolicy.currentPublishedRelease.version;
|
|
184
|
+
return markdown.includes(
|
|
185
|
+
`Bundled candidate: standalone \`owlrunkit@${core.coreVersion}\``,
|
|
186
|
+
)
|
|
187
|
+
&& /candidate is not published/iu.test(markdown)
|
|
188
|
+
&& new RegExp(
|
|
189
|
+
"current published release remains\\s+`owlrunkit@"
|
|
190
|
+
+ escaped(prior)
|
|
191
|
+
+ "`",
|
|
192
|
+
"iu",
|
|
193
|
+
).test(markdown)
|
|
194
|
+
&& !markdown.includes(
|
|
195
|
+
`Bundled release: standalone \`owlrunkit@${core.coreVersion}\``,
|
|
196
|
+
)
|
|
197
|
+
&& !markdown.includes(`--exact owlrunkit@${core.coreVersion}`)
|
|
198
|
+
&& !markdown.includes(`npm install --save-exact owlrunkit@${core.coreVersion}`);
|
|
164
199
|
};
|
|
165
200
|
|
|
166
201
|
requireSurface(packageExact(input.packageManifest), "package_manifest");
|
|
@@ -95,11 +95,14 @@ function nonEmptyString(value, label) {
|
|
|
95
95
|
|
|
96
96
|
function validateTimestamp(value) {
|
|
97
97
|
if (typeof value !== "string"
|
|
98
|
-
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
|
|
98
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u.test(value)
|
|
99
99
|
|| Number.isNaN(Date.parse(value))) {
|
|
100
|
-
throw new Error(
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Team project successor --at must be an ISO-8601 UTC timestamp "
|
|
102
|
+
+ "(YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.sssZ).",
|
|
103
|
+
);
|
|
101
104
|
}
|
|
102
|
-
return value;
|
|
105
|
+
return new Date(value).toISOString();
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
function assertHash(value, label) {
|
|
@@ -981,7 +984,7 @@ export function successorTeamProjectV1({
|
|
|
981
984
|
}) {
|
|
982
985
|
const root = assertWorkspaceRoot(workspaceRoot);
|
|
983
986
|
safeIdentifier(transitionId, "transitionId");
|
|
984
|
-
validateTimestamp(occurredAt);
|
|
987
|
+
occurredAt = validateTimestamp(occurredAt);
|
|
985
988
|
nonEmptyString(reason, "Team project successor reason");
|
|
986
989
|
const runkitPath = path.join(root, RUNKIT_ROOT);
|
|
987
990
|
assertRealDirectory(path.join(root, ".owlcoda"), "OwlCoda control directory");
|