owlrunkit 0.13.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +674 -0
  3. package/NOTICE.md +137 -0
  4. package/README.md +44 -0
  5. package/SOURCE.md +10 -0
  6. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_1.md +160 -0
  7. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +459 -0
  8. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +408 -0
  9. package/integrations/codex/skills/owlcoda-runkit/agents/openai.yaml +4 -0
  10. package/integrations/codex/skills/owlcoda-runkit/assets/templates/agents-snippet.md +7 -0
  11. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +10 -0
  12. package/integrations/codex/skills/owlcoda-runkit/assets/templates/coverage-adopt-request.json +26 -0
  13. package/integrations/codex/skills/owlcoda-runkit/assets/templates/execution-delivery-packet.json +13 -0
  14. package/integrations/codex/skills/owlcoda-runkit/assets/templates/finalize-request.json +18 -0
  15. package/integrations/codex/skills/owlcoda-runkit/assets/templates/goal-contract.json +11 -0
  16. package/integrations/codex/skills/owlcoda-runkit/assets/templates/profiles.json +9 -0
  17. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +11 -0
  18. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resource-preflight-request.json +56 -0
  19. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resume-request.json +10 -0
  20. package/integrations/codex/skills/owlcoda-runkit/assets/templates/snapshot-request.json +23 -0
  21. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-context.json +15 -0
  22. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-receipt.json +11 -0
  23. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verify-plan-request.json +31 -0
  24. package/integrations/codex/skills/owlcoda-runkit/assets/templates/visual-smoke-request.json +32 -0
  25. package/integrations/codex/skills/owlcoda-runkit/assets/templates/worker-lease.json +7 -0
  26. package/package.json +24 -0
  27. package/packages/attest/LICENSE +674 -0
  28. package/packages/attest/README.md +63 -0
  29. package/packages/attest/cli/owlcoda-attest.mjs +155 -0
  30. package/packages/attest/consumer-surface-v1.json +255 -0
  31. package/packages/attest/distribution-manifest-v1.json +104 -0
  32. package/packages/attest/fixtures/bad/accepted-authority-leak.json +7 -0
  33. package/packages/attest/fixtures/bad/accepted-failed-receipt.json +7 -0
  34. package/packages/attest/fixtures/bad/active-context-binding-mismatch.json +7 -0
  35. package/packages/attest/fixtures/bad/active-source-binding-mismatch.json +7 -0
  36. package/packages/attest/fixtures/bad/context-fingerprint-mismatch.json +7 -0
  37. package/packages/attest/fixtures/bad/duplicate-object-key.json +8 -0
  38. package/packages/attest/fixtures/bad/multiple-active-leaves.json +10 -0
  39. package/packages/attest/fixtures/bad/orphan-replacement.json +7 -0
  40. package/packages/attest/fixtures/bad/root-shape-extra-property.json +8 -0
  41. package/packages/attest/fixtures/bad/source-fingerprint-mismatch.json +7 -0
  42. package/packages/attest/fixtures/good/accepted-replacement-chain.json +59 -0
  43. package/packages/attest/fixtures/good/blocked-single-leaf.json +9 -0
  44. package/packages/attest/fixtures/index.json +90 -0
  45. package/packages/attest/sbom.spdx.json +33 -0
  46. package/packages/attest/schemas/attestation-ref-v1.schema.json +44 -0
  47. package/packages/attest/schemas/attestation-result-v1.schema.json +163 -0
  48. package/packages/attest/schemas/offline-attestation-bundle-v1.schema.json +30 -0
  49. package/packages/attest/schemas/public-verification-bundle-v1.schema.json +74 -0
  50. package/packages/attest/schemas/quick-verification-receipt-v1.schema.json +219 -0
  51. package/packages/attest/schemas/workspace-snapshot-v1.schema.json +135 -0
  52. package/packages/attest/src/canonical.mjs +12 -0
  53. package/packages/attest/src/formal.mjs +762 -0
  54. package/packages/attest/src/index.mjs +113 -0
  55. package/packages/attest/src/offline-bundle.mjs +108 -0
  56. package/packages/attest/src/quick-receipt-contract.mjs +156 -0
  57. package/packages/attest/src/quick.mjs +274 -0
  58. package/packages/attest/src/reference-contract.mjs +64 -0
  59. package/packages/attest/src/reference.mjs +131 -0
  60. package/packages/attest/src/workspace-snapshot.mjs +171 -0
  61. package/registry-release-policy-v1.json +34 -0
  62. package/sbom.spdx.json +39 -0
  63. package/scripts/runkit-contract/acceptance-evidence.mjs +103 -0
  64. package/scripts/runkit-contract/core-contract.mjs +392 -0
  65. package/scripts/runkit-contract/coverage-adoption.mjs +414 -0
  66. package/scripts/runkit-contract/delivery-create.mjs +252 -0
  67. package/scripts/runkit-contract/delivery-selection.mjs +31 -0
  68. package/scripts/runkit-contract/finalize.mjs +790 -0
  69. package/scripts/runkit-contract/inspect-presentation.mjs +183 -0
  70. package/scripts/runkit-contract/install-codex-skill.mjs +239 -0
  71. package/scripts/runkit-contract/lease-lifecycle.mjs +324 -0
  72. package/scripts/runkit-contract/lifecycle-orchestration.mjs +415 -0
  73. package/scripts/runkit-contract/offline-store.mjs +248 -0
  74. package/scripts/runkit-contract/profile-impact.mjs +223 -0
  75. package/scripts/runkit-contract/provenance-common.mjs +153 -0
  76. package/scripts/runkit-contract/quick-attest.mjs +4 -0
  77. package/scripts/runkit-contract/quick-canonical.mjs +41 -0
  78. package/scripts/runkit-contract/quick-metrics.mjs +86 -0
  79. package/scripts/runkit-contract/quick-receipt.mjs +139 -0
  80. package/scripts/runkit-contract/quick-verify.mjs +201 -0
  81. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +3 -0
  82. package/scripts/runkit-contract/ready-for-commit.mjs +188 -0
  83. package/scripts/runkit-contract/receipt-lineage.mjs +332 -0
  84. package/scripts/runkit-contract/registry-adoption-gate.mjs +117 -0
  85. package/scripts/runkit-contract/repair-execution.mjs +997 -0
  86. package/scripts/runkit-contract/resource-preflight.mjs +829 -0
  87. package/scripts/runkit-contract/resume-execution.mjs +569 -0
  88. package/scripts/runkit-contract/runkit-cli.mjs +1214 -0
  89. package/scripts/runkit-contract/schemas/closeout-receipt-v2.schema.json +42 -0
  90. package/scripts/runkit-contract/schemas/core-artifact-v2.schema.json +23 -0
  91. package/scripts/runkit-contract/schemas/core-artifact.schema.json +23 -0
  92. package/scripts/runkit-contract/schemas/coverage-adopt-request-v1.schema.json +66 -0
  93. package/scripts/runkit-contract/schemas/engine-pin-v2.schema.json +14 -0
  94. package/scripts/runkit-contract/schemas/engine-pin.schema.json +14 -0
  95. package/scripts/runkit-contract/schemas/evidence-coverage-index-v1.schema.json +131 -0
  96. package/scripts/runkit-contract/schemas/finalize-request-v1.schema.json +131 -0
  97. package/scripts/runkit-contract/schemas/profiles.schema.json +47 -0
  98. package/scripts/runkit-contract/schemas/project-config-v2.schema.json +13 -0
  99. package/scripts/runkit-contract/schemas/project-config.schema.json +13 -0
  100. package/scripts/runkit-contract/schemas/ready-for-commit-v1.schema.json +26 -0
  101. package/scripts/runkit-contract/schemas/replayable-evidence-v1.schema.json +82 -0
  102. package/scripts/runkit-contract/schemas/resource-preflight-request-v1.schema.json +178 -0
  103. package/scripts/runkit-contract/schemas/resource-preflight-v1.schema.json +153 -0
  104. package/scripts/runkit-contract/schemas/resume-attempt-v1.schema.json +80 -0
  105. package/scripts/runkit-contract/schemas/resume-request-v1.schema.json +77 -0
  106. package/scripts/runkit-contract/schemas/snapshot-v1.schema.json +71 -0
  107. package/scripts/runkit-contract/schemas/verification-context-v1.schema.json +92 -0
  108. package/scripts/runkit-contract/schemas/verification-plan-v1.schema.json +150 -0
  109. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +118 -0
  110. package/scripts/runkit-contract/schemas/verify-plan-request-v1.schema.json +56 -0
  111. package/scripts/runkit-contract/schemas/visual-smoke-request-v1.schema.json +64 -0
  112. package/scripts/runkit-contract/schemas/visual-smoke-result-v1.schema.json +68 -0
  113. package/scripts/runkit-contract/snapshot.mjs +340 -0
  114. package/scripts/runkit-contract/source-fingerprint.mjs +404 -0
  115. package/scripts/runkit-contract/templates/coverage-adopt-request.json +26 -0
  116. package/scripts/runkit-contract/templates/finalize-request.json +18 -0
  117. package/scripts/runkit-contract/templates/goal-contract.json +11 -0
  118. package/scripts/runkit-contract/templates/ready-for-commit-request.json +11 -0
  119. package/scripts/runkit-contract/templates/resource-preflight-request.json +56 -0
  120. package/scripts/runkit-contract/templates/resume-request.json +10 -0
  121. package/scripts/runkit-contract/templates/snapshot-request.json +23 -0
  122. package/scripts/runkit-contract/templates/verify-plan-request.json +31 -0
  123. package/scripts/runkit-contract/templates/visual-smoke-request.json +32 -0
  124. package/scripts/runkit-contract/templates/worker-lease.json +7 -0
  125. package/scripts/runkit-contract/verification-context.mjs +141 -0
  126. package/scripts/runkit-contract/verification-plan.mjs +608 -0
  127. package/scripts/runkit-contract/verification-receipt-gate.mjs +566 -0
  128. package/scripts/runkit-contract/visual-smoke.mjs +410 -0
@@ -0,0 +1,113 @@
1
+ import {
2
+ PUBLIC_VERIFIER_LIMITS,
3
+ canonicalJson,
4
+ decodeUtf8Strict,
5
+ hasExactKeys,
6
+ isPlainJsonObject,
7
+ isPlainObject,
8
+ isSha256Ref,
9
+ parseJsonStrict,
10
+ readFileBytesBounded,
11
+ sha256Bytes,
12
+ sha256Canonical,
13
+ verifyBundle,
14
+ } from "./formal.mjs";
15
+ import {
16
+ SUPPORTED_QUICK_CORE_IDENTITIES,
17
+ attestQuickReceipt,
18
+ attestQuickReceiptDetails,
19
+ attestQuickReceiptDetailsFromBytes,
20
+ validQuickReceiptShape,
21
+ } from "./quick.mjs";
22
+ import {
23
+ createAttestationRef,
24
+ parseAttestationRef,
25
+ resolveAttestationRef,
26
+ } from "./reference.mjs";
27
+ import {
28
+ createOfflineAttestationBundle,
29
+ parseOfflineAttestationBundle,
30
+ } from "./offline-bundle.mjs";
31
+ import { captureWorkspaceSnapshot } from "./workspace-snapshot.mjs";
32
+
33
+ function readSubject(subjectPath) {
34
+ const { absolutePath, bytes } = readFileBytesBounded(subjectPath);
35
+ return {
36
+ absolutePath,
37
+ bytes,
38
+ value: parseJsonStrict(decodeUtf8Strict(bytes)),
39
+ };
40
+ }
41
+
42
+ export function attestFile({ subjectPath, workspaceRoot }) {
43
+ const subject = readSubject(subjectPath);
44
+ if (subject.value.schemaVersion === "OwlCodaQuickVerificationReceiptV1") {
45
+ const details = attestQuickReceiptDetailsFromBytes({
46
+ receiptPath: subject.absolutePath,
47
+ receiptBytes: subject.bytes,
48
+ workspaceRoot,
49
+ });
50
+ return {
51
+ schemaVersion: "OwlCodaAttestCommandResultV1",
52
+ status: "quick_attestation",
53
+ decision: details.attestation.decision,
54
+ exitCode: details.exitCode,
55
+ receiptPath: subject.absolutePath,
56
+ receiptSha256: sha256Bytes(subject.bytes),
57
+ sourceFingerprint: details.sourceFingerprint,
58
+ attestation: details.attestation,
59
+ nextAllowedAction: details.attestation.decision === "GO"
60
+ ? "consume_attestation"
61
+ : "repair_or_replace_evidence",
62
+ authorizationGranted: false,
63
+ networkRequests: 0,
64
+ };
65
+ }
66
+ if (subject.value.schemaVersion === "OwlCodaRunKitPublicVerificationBundleV1") {
67
+ const formalVerification = verifyBundle(subject.value);
68
+ const accepted = formalVerification.valid
69
+ && formalVerification.decision === "accepted";
70
+ return {
71
+ schemaVersion: "OwlCodaAttestCommandResultV1",
72
+ status: "formal_attestation",
73
+ decision: accepted ? "GO" : "NO_GO",
74
+ exitCode: accepted ? 0 : 1,
75
+ subjectPath: subject.absolutePath,
76
+ subjectSha256: sha256Bytes(subject.bytes),
77
+ formalVerification,
78
+ nextAllowedAction: accepted
79
+ ? "consume_attestation"
80
+ : formalVerification.valid
81
+ ? "honor_formal_decision"
82
+ : "repair_or_replace_evidence",
83
+ authorizationGranted: false,
84
+ networkRequests: 0,
85
+ };
86
+ }
87
+ const error = new Error("unsupported attestation subject schemaVersion");
88
+ error.code = "receipt_schema_invalid";
89
+ throw error;
90
+ }
91
+
92
+ export {
93
+ PUBLIC_VERIFIER_LIMITS,
94
+ SUPPORTED_QUICK_CORE_IDENTITIES,
95
+ attestQuickReceipt,
96
+ attestQuickReceiptDetails,
97
+ canonicalJson,
98
+ captureWorkspaceSnapshot,
99
+ createAttestationRef,
100
+ createOfflineAttestationBundle,
101
+ hasExactKeys,
102
+ isPlainJsonObject,
103
+ isPlainObject,
104
+ isSha256Ref,
105
+ parseAttestationRef,
106
+ parseOfflineAttestationBundle,
107
+ parseJsonStrict,
108
+ resolveAttestationRef,
109
+ sha256Bytes,
110
+ sha256Canonical,
111
+ validQuickReceiptShape,
112
+ verifyBundle,
113
+ };
@@ -0,0 +1,108 @@
1
+ import {
2
+ decodeUtf8Strict,
3
+ hasExactKeys,
4
+ parseJsonStrict,
5
+ readFileBytesBounded,
6
+ sha256Bytes,
7
+ } from "./formal.mjs";
8
+ import {
9
+ createAttestationRef,
10
+ parseAttestationRef,
11
+ } from "./reference-contract.mjs";
12
+ import { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
13
+
14
+ const REQUIRED_KEYS = [
15
+ "schemaVersion",
16
+ "reference",
17
+ "receiptByteLength",
18
+ "receiptBytesBase64",
19
+ ];
20
+
21
+ function bundleError(code, message) {
22
+ const error = new Error(message);
23
+ error.code = code;
24
+ return error;
25
+ }
26
+
27
+ function decodeBase64Strict(value) {
28
+ if (
29
+ typeof value !== "string"
30
+ || value.length === 0
31
+ || value.length % 4 !== 0
32
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)
33
+ ) {
34
+ throw bundleError("offline_bundle_invalid", "offline receipt bytes must use canonical base64");
35
+ }
36
+ const bytes = Buffer.from(value, "base64");
37
+ if (bytes.toString("base64") !== value) {
38
+ throw bundleError("offline_bundle_invalid", "offline receipt bytes must use canonical base64");
39
+ }
40
+ return bytes;
41
+ }
42
+
43
+ function validateReceiptBinding(reference, receiptBytes) {
44
+ if (sha256Bytes(receiptBytes) !== reference.receiptSha256) {
45
+ throw bundleError("offline_bundle_hash_mismatch", "offline receipt bytes do not match the reference hash");
46
+ }
47
+ let receipt;
48
+ try {
49
+ receipt = parseJsonStrict(decodeUtf8Strict(receiptBytes));
50
+ } catch (error) {
51
+ if (error?.code === "DUPLICATE_OBJECT_KEY") error.code = "receipt_duplicate_key";
52
+ throw error;
53
+ }
54
+ if (
55
+ !validQuickReceiptShape(receipt)
56
+ || receipt.receiptId !== reference.receiptId
57
+ || receipt.coreIdentity.contractVersion !== reference.coreIdentity.contractVersion
58
+ || receipt.coreIdentity.coreManifestSha256 !== reference.coreIdentity.coreManifestSha256
59
+ ) {
60
+ throw bundleError("offline_bundle_reference_mismatch", "offline receipt identity does not match its reference");
61
+ }
62
+ }
63
+
64
+ export function createOfflineAttestationBundle(receiptPath) {
65
+ const reference = createAttestationRef(receiptPath);
66
+ const { bytes } = readFileBytesBounded(receiptPath);
67
+ validateReceiptBinding(reference, bytes);
68
+ return {
69
+ schemaVersion: "OwlCodaOfflineAttestationBundleV1",
70
+ reference,
71
+ receiptByteLength: bytes.byteLength,
72
+ receiptBytesBase64: bytes.toString("base64"),
73
+ };
74
+ }
75
+
76
+ export function parseOfflineAttestationBundle(input) {
77
+ let value;
78
+ try {
79
+ value = typeof input === "string" ? parseJsonStrict(input) : input;
80
+ } catch (error) {
81
+ if (error?.code === "DUPLICATE_OBJECT_KEY") error.code = "receipt_duplicate_key";
82
+ throw error;
83
+ }
84
+ if (
85
+ !hasExactKeys(value, REQUIRED_KEYS)
86
+ || value.schemaVersion !== "OwlCodaOfflineAttestationBundleV1"
87
+ || !Number.isSafeInteger(value.receiptByteLength)
88
+ || value.receiptByteLength < 1
89
+ ) {
90
+ throw bundleError("offline_bundle_invalid", "bundle does not satisfy OwlCodaOfflineAttestationBundleV1");
91
+ }
92
+ let reference;
93
+ try {
94
+ reference = parseAttestationRef(value.reference);
95
+ } catch {
96
+ throw bundleError("offline_bundle_invalid", "bundle contains an invalid AttestationRefV1");
97
+ }
98
+ const receiptBytes = decodeBase64Strict(value.receiptBytesBase64);
99
+ if (receiptBytes.byteLength !== value.receiptByteLength) {
100
+ throw bundleError("offline_bundle_size_mismatch", "offline receipt byte length does not match");
101
+ }
102
+ validateReceiptBinding(reference, receiptBytes);
103
+ return {
104
+ bundle: value,
105
+ reference,
106
+ receiptBytes,
107
+ };
108
+ }
@@ -0,0 +1,156 @@
1
+ import {
2
+ hasExactKeys,
3
+ isSha256Ref,
4
+ sha256Canonical,
5
+ } from "./formal.mjs";
6
+
7
+ const RECEIPT_REQUIRED_KEYS = [
8
+ "schemaVersion",
9
+ "receiptId",
10
+ "assurance",
11
+ "authorizationGranted",
12
+ "coreIdentity",
13
+ "workspaceBefore",
14
+ "exactCommand",
15
+ "verificationContext",
16
+ "startedAt",
17
+ "finishedAt",
18
+ "exitResult",
19
+ "outputArtifacts",
20
+ "workspaceAfter",
21
+ "mutationDecision",
22
+ "issueCodes",
23
+ ];
24
+ const SNAPSHOT_REQUIRED_KEYS = [
25
+ "schemaVersion",
26
+ "repositoryIdentity",
27
+ "headCommit",
28
+ "trackedTreeIdentity",
29
+ "submodules",
30
+ "dirtyOverlay",
31
+ "dependencyLockfiles",
32
+ "excludedRoots",
33
+ "ignoredPathsBound",
34
+ "policyVersion",
35
+ "sourceFingerprint",
36
+ ];
37
+ const KNOWN_RECEIPT_ISSUES = new Set([
38
+ "quick_ignored_artifact_unbound",
39
+ "source_mutated_during_verification",
40
+ ]);
41
+
42
+ function validDateTime(value) {
43
+ return typeof value === "string"
44
+ && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
45
+ && Number.isFinite(Date.parse(value));
46
+ }
47
+
48
+ function validCoreIdentity(value) {
49
+ return hasExactKeys(value, ["contractVersion", "coreVersion", "coreManifestSha256"])
50
+ && typeof value.contractVersion === "string"
51
+ && value.contractVersion.length > 0
52
+ && typeof value.coreVersion === "string"
53
+ && value.coreVersion.length > 0
54
+ && isSha256Ref(value.coreManifestSha256);
55
+ }
56
+
57
+ function validSnapshot(value) {
58
+ if (!hasExactKeys(value, SNAPSHOT_REQUIRED_KEYS)) return false;
59
+ if (
60
+ value.schemaVersion !== "OwlCodaWorkspaceSnapshotV1"
61
+ || typeof value.repositoryIdentity !== "string"
62
+ || value.repositoryIdentity.length === 0
63
+ || !(value.headCommit === null || /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(value.headCommit))
64
+ || !isSha256Ref(value.trackedTreeIdentity)
65
+ || value.ignoredPathsBound !== false
66
+ || value.policyVersion !== "workspace-snapshot-v1"
67
+ || !isSha256Ref(value.sourceFingerprint)
68
+ || !Array.isArray(value.submodules)
69
+ || !Array.isArray(value.dirtyOverlay)
70
+ || !Array.isArray(value.dependencyLockfiles)
71
+ || !Array.isArray(value.excludedRoots)
72
+ || !value.excludedRoots.includes(".owlcoda/runkit")
73
+ ) {
74
+ return false;
75
+ }
76
+ if (!value.submodules.every((entry) =>
77
+ hasExactKeys(entry, ["path", "commit"])
78
+ && typeof entry.path === "string"
79
+ && /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(entry.commit))) {
80
+ return false;
81
+ }
82
+ if (!value.dirtyOverlay.every((entry) =>
83
+ hasExactKeys(entry, ["path", "state", "sha256"])
84
+ && typeof entry.path === "string"
85
+ && ["modified", "untracked", "deleted"].includes(entry.state)
86
+ && (entry.sha256 === null || isSha256Ref(entry.sha256)))) {
87
+ return false;
88
+ }
89
+ if (!value.dependencyLockfiles.every((entry) =>
90
+ hasExactKeys(entry, ["path", "sha256"])
91
+ && typeof entry.path === "string"
92
+ && isSha256Ref(entry.sha256))) {
93
+ return false;
94
+ }
95
+ return true;
96
+ }
97
+
98
+ export function quickSnapshotFingerprintValid(value) {
99
+ const { sourceFingerprint, ...payload } = value;
100
+ return sha256Canonical(payload) === sourceFingerprint;
101
+ }
102
+
103
+ function validOutputArtifact(value) {
104
+ return hasExactKeys(value, ["path", "sha256", "sizeBytes"])
105
+ && typeof value.path === "string"
106
+ && value.path.length > 0
107
+ && isSha256Ref(value.sha256)
108
+ && Number.isInteger(value.sizeBytes)
109
+ && value.sizeBytes >= 0;
110
+ }
111
+
112
+ export function validQuickReceiptShape(receipt) {
113
+ if (!hasExactKeys(receipt, RECEIPT_REQUIRED_KEYS, ["signatureRef"])) return false;
114
+ if (
115
+ receipt.schemaVersion !== "OwlCodaQuickVerificationReceiptV1"
116
+ || typeof receipt.receiptId !== "string"
117
+ || receipt.receiptId.length === 0
118
+ || receipt.assurance !== "captured_verification"
119
+ || receipt.authorizationGranted !== false
120
+ || !validCoreIdentity(receipt.coreIdentity)
121
+ || !validSnapshot(receipt.workspaceBefore)
122
+ || !validSnapshot(receipt.workspaceAfter)
123
+ || !hasExactKeys(receipt.exactCommand, ["executable", "argv", "cwd"])
124
+ || typeof receipt.exactCommand.executable !== "string"
125
+ || receipt.exactCommand.executable.length === 0
126
+ || !Array.isArray(receipt.exactCommand.argv)
127
+ || !receipt.exactCommand.argv.every((entry) => typeof entry === "string")
128
+ || typeof receipt.exactCommand.cwd !== "string"
129
+ || receipt.exactCommand.cwd.length === 0
130
+ || !hasExactKeys(receipt.verificationContext, ["platform", "architecture", "runtime"])
131
+ || !Object.values(receipt.verificationContext).every((entry) => typeof entry === "string" && entry.length > 0)
132
+ || !validDateTime(receipt.startedAt)
133
+ || !validDateTime(receipt.finishedAt)
134
+ || Date.parse(receipt.finishedAt) < Date.parse(receipt.startedAt)
135
+ || !hasExactKeys(receipt.exitResult, ["exitCode", "signal"])
136
+ || !(receipt.exitResult.exitCode === null || Number.isInteger(receipt.exitResult.exitCode))
137
+ || !(receipt.exitResult.signal === null || typeof receipt.exitResult.signal === "string")
138
+ || !hasExactKeys(receipt.outputArtifacts, ["stdout", "stderr"])
139
+ || !validOutputArtifact(receipt.outputArtifacts.stdout)
140
+ || !validOutputArtifact(receipt.outputArtifacts.stderr)
141
+ || !["source_unchanged", "invalidated_by_command_source_mutation"].includes(receipt.mutationDecision)
142
+ || !Array.isArray(receipt.issueCodes)
143
+ || new Set(receipt.issueCodes).size !== receipt.issueCodes.length
144
+ || !receipt.issueCodes.every((entry) => typeof entry === "string" && KNOWN_RECEIPT_ISSUES.has(entry))
145
+ ) {
146
+ return false;
147
+ }
148
+ if (receipt.signatureRef !== undefined) {
149
+ if (!hasExactKeys(receipt.signatureRef, ["path", "sha256"])
150
+ || typeof receipt.signatureRef.path !== "string"
151
+ || !isSha256Ref(receipt.signatureRef.sha256)) {
152
+ return false;
153
+ }
154
+ }
155
+ return true;
156
+ }
@@ -0,0 +1,274 @@
1
+ import { lstatSync, realpathSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ isPlainJsonObject,
6
+ decodeUtf8Strict,
7
+ parseJsonStrict,
8
+ readFileBytesBounded,
9
+ resolveRegularFilePath,
10
+ sha256Bytes,
11
+ } from "./formal.mjs";
12
+ import {
13
+ quickSnapshotFingerprintValid,
14
+ validQuickReceiptShape,
15
+ } from "./quick-receipt-contract.mjs";
16
+ import { captureWorkspaceSnapshot } from "./workspace-snapshot.mjs";
17
+
18
+ export { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
19
+
20
+ export const SUPPORTED_QUICK_CORE_IDENTITIES = Object.freeze([
21
+ {
22
+ contractVersion: "0.2",
23
+ coreVersion: "0.12.0",
24
+ coreManifestSha256: "sha256:c415b10cb00d2a7891744b7257774fa501ddf40f8ec2f290356505a17fefb40f",
25
+ },
26
+ {
27
+ contractVersion: "0.2",
28
+ coreVersion: "0.12.0",
29
+ coreManifestSha256: "sha256:be4b079fb0bc29e71858af03a1e579f864d4414c815ac612c3b514d8d663d07b",
30
+ },
31
+ {
32
+ contractVersion: "0.2",
33
+ coreVersion: "0.13.0",
34
+ coreManifestSha256: "sha256:0e3233a417365afb4e2ce22db5260608a9c697a819cd5c02897276d6454dde1f",
35
+ },
36
+ {
37
+ contractVersion: "0.2",
38
+ coreVersion: "0.13.0",
39
+ coreManifestSha256: "sha256:037c012751b32abbbb48ce8a8d2cd8faa4fc2c38d6797db391205477e667065f",
40
+ },
41
+ {
42
+ contractVersion: "0.2",
43
+ coreVersion: "0.13.0",
44
+ coreManifestSha256: "sha256:2f840d1884b34656902f8f23af3bd8052dde3212aa516dac2cbb0e0b29627ea1",
45
+ },
46
+ {
47
+ contractVersion: "0.2",
48
+ coreVersion: "0.13.0",
49
+ coreManifestSha256: "sha256:febf2551317fce9e4bb412b833a392a20376d702dda09c57d6116e8a3ca4f857",
50
+ },
51
+ {
52
+ contractVersion: "0.2",
53
+ coreVersion: "0.13.0",
54
+ coreManifestSha256: "sha256:4b72f572b6964f149c4a6fe5c2c9da85f73b8060c88023dc96ba5ca50a036972",
55
+ },
56
+ {
57
+ contractVersion: "0.2",
58
+ coreVersion: "0.13.0",
59
+ coreManifestSha256: "sha256:0455c847bf2df77703583c6f19c20cf103206bafc4f122795bf34d3f85f0263d",
60
+ },
61
+ ]);
62
+
63
+ function resolveBoundMaterial(root, relativePath) {
64
+ if (path.isAbsolute(relativePath) || relativePath.includes("\0")) {
65
+ throw new Error("material path must be workspace-relative");
66
+ }
67
+ const segments = relativePath.split(/[\\/]/);
68
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
69
+ throw new Error("material path contains unsafe segments");
70
+ }
71
+ let current = root;
72
+ for (const segment of segments) {
73
+ current = path.join(current, segment);
74
+ const stat = lstatSync(current);
75
+ if (stat.isSymbolicLink()) throw new Error("material path traverses a symlink");
76
+ }
77
+ const resolved = realpathSync(current);
78
+ const remainder = path.relative(root, resolved);
79
+ if (remainder === "" || remainder === ".." || remainder.startsWith(`..${path.sep}`) || path.isAbsolute(remainder)) {
80
+ throw new Error("material path escapes the workspace");
81
+ }
82
+ if (!statSync(resolved).isFile()) throw new Error("material must be a regular file");
83
+ return resolved;
84
+ }
85
+
86
+ function check(status, issueCode) {
87
+ return issueCode === undefined ? { status } : { status, issueCode };
88
+ }
89
+
90
+ function resultTemplate({ receipt, receiptPath, receiptSha256, decision, issues, checks, materials }) {
91
+ return {
92
+ schemaVersion: "OwlCodaAttestationResultV1",
93
+ decision,
94
+ subjectRef: {
95
+ schemaVersion: "OwlCodaAttestationRefV1",
96
+ receiptId: receipt.receiptId,
97
+ receiptSha256,
98
+ coreIdentity: {
99
+ contractVersion: receipt.coreIdentity.contractVersion,
100
+ coreManifestSha256: receipt.coreIdentity.coreManifestSha256,
101
+ },
102
+ },
103
+ verifiedAt: new Date().toISOString(),
104
+ policy: {
105
+ id: "owlcoda-quick-minimum",
106
+ version: "1.0.0",
107
+ },
108
+ checks,
109
+ authorizationBoundary: {
110
+ authorizationGranted: false,
111
+ statement: "attestation_never_grants_repository_or_business_authority",
112
+ },
113
+ issueCodes: [...new Set(issues)].sort(),
114
+ verifiedMaterials: [
115
+ { path: receiptPath, sha256: receiptSha256 },
116
+ ...materials,
117
+ ],
118
+ };
119
+ }
120
+
121
+ export function attestQuickReceiptDetailsFromBytes({
122
+ receiptPath,
123
+ receiptBytes,
124
+ workspaceRoot,
125
+ }) {
126
+ const absoluteReceiptPath = resolveRegularFilePath(receiptPath);
127
+ let receipt;
128
+ try {
129
+ receipt = parseJsonStrict(decodeUtf8Strict(receiptBytes));
130
+ } catch (error) {
131
+ if (error?.code === "DUPLICATE_OBJECT_KEY") {
132
+ error.code = "receipt_duplicate_key";
133
+ }
134
+ throw error;
135
+ }
136
+ if (!isPlainJsonObject(receipt) || !validQuickReceiptShape(receipt)) {
137
+ const error = new Error("Quick receipt does not satisfy the strict Wave 1 shape");
138
+ error.code = "receipt_schema_invalid";
139
+ throw error;
140
+ }
141
+
142
+ const issues = [...receipt.issueCodes, "signature_absent", "anchor_absent"];
143
+ const materials = [];
144
+ let materialMissing = false;
145
+ let materialMismatch = false;
146
+
147
+ const coreValid = SUPPORTED_QUICK_CORE_IDENTITIES.some((core) =>
148
+ receipt.coreIdentity.contractVersion === core.contractVersion
149
+ && receipt.coreIdentity.coreVersion === core.coreVersion
150
+ && receipt.coreIdentity.coreManifestSha256 === core.coreManifestSha256);
151
+ if (!coreValid) issues.push("core_identity_mismatch");
152
+ const contextValid = receipt.verificationContext.platform === process.platform
153
+ && receipt.verificationContext.architecture === process.arch
154
+ && receipt.verificationContext.runtime === process.version;
155
+ if (!contextValid) issues.push("verification_context_mismatch");
156
+
157
+ const snapshotBindingsValid = quickSnapshotFingerprintValid(receipt.workspaceBefore)
158
+ && quickSnapshotFingerprintValid(receipt.workspaceAfter);
159
+ const sourceUnchanged = receipt.workspaceBefore.sourceFingerprint
160
+ === receipt.workspaceAfter.sourceFingerprint;
161
+ const receiptSourceValid = receipt.mutationDecision === "source_unchanged"
162
+ ? snapshotBindingsValid && sourceUnchanged && !receipt.issueCodes.includes("source_mutated_during_verification")
163
+ : snapshotBindingsValid && !sourceUnchanged && receipt.issueCodes.includes("source_mutated_during_verification");
164
+ const selectedWorkspaceRoot = workspaceRoot === undefined
165
+ ? undefined
166
+ : realpathSync(workspaceRoot);
167
+ if (selectedWorkspaceRoot === undefined) {
168
+ issues.push("current_workspace_not_checked");
169
+ }
170
+ let receiptWorkspaceRoot;
171
+ if (selectedWorkspaceRoot !== undefined) {
172
+ try {
173
+ receiptWorkspaceRoot = realpathSync(receipt.exactCommand.cwd);
174
+ } catch {
175
+ receiptWorkspaceRoot = undefined;
176
+ }
177
+ }
178
+ const commandWorkspaceMatches = selectedWorkspaceRoot !== undefined
179
+ && receiptWorkspaceRoot === selectedWorkspaceRoot;
180
+ if (commandWorkspaceMatches) {
181
+ for (const artifact of [receipt.outputArtifacts.stdout, receipt.outputArtifacts.stderr]) {
182
+ try {
183
+ const materialPath = resolveBoundMaterial(receiptWorkspaceRoot, artifact.path);
184
+ const { bytes } = readFileBytesBounded(materialPath);
185
+ const actualSha256 = sha256Bytes(bytes);
186
+ materials.push({ path: materialPath, sha256: actualSha256 });
187
+ if (actualSha256 !== artifact.sha256 || bytes.byteLength !== artifact.sizeBytes) {
188
+ materialMismatch = true;
189
+ }
190
+ } catch (error) {
191
+ if (error && typeof error === "object" && error.code === "ENOENT") {
192
+ materialMissing = true;
193
+ } else {
194
+ materialMismatch = true;
195
+ }
196
+ }
197
+ }
198
+ }
199
+ if (materialMissing) issues.push("attestation_material_missing");
200
+ if (materialMismatch) issues.push("receipt_material_hash_mismatch");
201
+ const currentSourceValid = selectedWorkspaceRoot === undefined
202
+ || commandWorkspaceMatches
203
+ && captureWorkspaceSnapshot(selectedWorkspaceRoot).sourceFingerprint
204
+ === receipt.workspaceAfter.sourceFingerprint;
205
+ const sourceValid = receiptSourceValid && currentSourceValid;
206
+ if (!sourceValid || receipt.mutationDecision !== "source_unchanged") {
207
+ issues.push("receipt_source_mismatch");
208
+ }
209
+ const commandPassed = receipt.exitResult.exitCode === 0 && receipt.exitResult.signal === null;
210
+ if (!commandPassed) issues.push("verification_command_failed");
211
+ const deterministicFailure = !coreValid
212
+ || !contextValid
213
+ || materialMismatch
214
+ || !sourceValid
215
+ || receipt.mutationDecision !== "source_unchanged"
216
+ || !commandPassed;
217
+ const decision = deterministicFailure
218
+ ? "NO_GO"
219
+ : selectedWorkspaceRoot === undefined || materialMissing
220
+ ? "INDETERMINATE"
221
+ : "GO";
222
+ const receiptSha256 = sha256Bytes(receiptBytes);
223
+
224
+ const attestation = resultTemplate({
225
+ receipt,
226
+ receiptPath: absoluteReceiptPath,
227
+ receiptSha256,
228
+ decision,
229
+ issues,
230
+ materials,
231
+ checks: {
232
+ schema: check("passed"),
233
+ canonicalization: check("passed"),
234
+ lineage: check("passed"),
235
+ source: selectedWorkspaceRoot === undefined
236
+ ? check("not_checked", "current_workspace_not_checked")
237
+ : sourceValid && receipt.mutationDecision === "source_unchanged"
238
+ ? check("passed")
239
+ : check("failed", "receipt_source_mismatch"),
240
+ context: !coreValid
241
+ ? check("failed", "core_identity_mismatch")
242
+ : contextValid
243
+ ? check("passed")
244
+ : check("failed", "verification_context_mismatch"),
245
+ signature: check("not_checked", "signature_absent"),
246
+ key: check("not_checked", "signature_absent"),
247
+ anchor: check("not_checked", "anchor_absent"),
248
+ },
249
+ });
250
+ return {
251
+ attestation,
252
+ sourceFingerprint: receipt.workspaceAfter.sourceFingerprint,
253
+ exitCode: decision === "GO"
254
+ ? 0
255
+ : !sourceValid || receipt.mutationDecision !== "source_unchanged"
256
+ ? 2
257
+ : decision === "INDETERMINATE"
258
+ ? 3
259
+ : 1,
260
+ };
261
+ }
262
+
263
+ export function attestQuickReceiptDetails({ receiptPath, workspaceRoot }) {
264
+ const { absolutePath, bytes } = readFileBytesBounded(receiptPath);
265
+ return attestQuickReceiptDetailsFromBytes({
266
+ receiptPath: absolutePath,
267
+ receiptBytes: bytes,
268
+ workspaceRoot,
269
+ });
270
+ }
271
+
272
+ export function attestQuickReceipt(options) {
273
+ return attestQuickReceiptDetails(options).attestation;
274
+ }