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,223 @@
1
+ import { isAbsolute, win32 } from "node:path"
2
+ import { isDirectExecution, isReservedRuntimePath } from "./core-contract.mjs"
3
+
4
+ const FULL_PROFILE_REQUIRED = "full_profile_required"
5
+ const TARGETED_PROFILES = "targeted_profiles"
6
+
7
+ export function resolveProfileImpact(input) {
8
+ if (!isRecord(input) || !Array.isArray(input.changedPaths) || !Array.isArray(input.profiles)) {
9
+ throw new Error("Profile impact input requires changedPaths and profiles arrays.")
10
+ }
11
+ const changedPaths = sortedUniqueStrings(input.changedPaths, "Changed paths must be non-empty strings.")
12
+ for (const changedPath of changedPaths) validateRepoRelativePath(changedPath, "Changed path")
13
+ const profiles = input.profiles.map(profile => normalizeProfile(profile))
14
+ const matchedProfileIds = new Set()
15
+ const uncoveredPaths = []
16
+
17
+ for (const changedPath of changedPaths) {
18
+ let covered = false
19
+ for (const profile of profiles) {
20
+ if (!profile.paths.some(rule => matchesRule(changedPath, rule))) continue
21
+ covered = true
22
+ matchedProfileIds.add(profile.id)
23
+ }
24
+ if (!covered) uncoveredPaths.push(changedPath)
25
+ }
26
+
27
+ if (uncoveredPaths.length > 0) {
28
+ return {
29
+ decision: FULL_PROFILE_REQUIRED,
30
+ profileIds: [],
31
+ uncoveredPaths,
32
+ }
33
+ }
34
+ return {
35
+ decision: TARGETED_PROFILES,
36
+ profileIds: [...matchedProfileIds].sort(),
37
+ uncoveredPaths: [],
38
+ }
39
+ }
40
+
41
+ export function resolveProfileImpactDetailed(input) {
42
+ if (!isRecord(input) || !Array.isArray(input.changedPaths) || !Array.isArray(input.profiles)) {
43
+ throw new Error("Detailed profile impact input requires changedPaths and profiles arrays.")
44
+ }
45
+ const changedPaths = sortedUniqueStrings(input.changedPaths, "Changed paths must be non-empty strings.")
46
+ for (const changedPath of changedPaths) validateRepoRelativePath(changedPath, "Changed path")
47
+ const profiles = input.profiles.map(profile => normalizeDetailedProfile(profile))
48
+ const profileById = new Map()
49
+ for (const profile of profiles) {
50
+ if (profileById.has(profile.id)) throw new Error(`Detailed profile ids must be unique: ${profile.id}`)
51
+ profileById.set(profile.id, profile)
52
+ }
53
+
54
+ const directProfileIds = new Set()
55
+ const supportingProfileIds = new Set()
56
+ const uncoveredPaths = []
57
+ const warnings = []
58
+ const specificityByProfileId = new Map()
59
+ for (const changedPath of changedPaths) {
60
+ const directMatches = []
61
+ const supportingMatches = []
62
+ for (const profile of profiles) {
63
+ const specificity = Math.max(
64
+ -1,
65
+ ...profile.paths
66
+ .filter(rule => matchesRule(changedPath, rule))
67
+ .map(rule => rule.endsWith("/**") ? rule.length - 3 : rule.length + 1_000_000),
68
+ )
69
+ if (specificity < 0) continue
70
+ if (profile.role === "supporting") supportingMatches.push(profile)
71
+ else directMatches.push(profile)
72
+ const prior = specificityByProfileId.get(profile.id) ?? -1
73
+ if (specificity > prior) specificityByProfileId.set(profile.id, specificity)
74
+ }
75
+ for (const profile of supportingMatches) supportingProfileIds.add(profile.id)
76
+ if (directMatches.length === 0) {
77
+ uncoveredPaths.push(changedPath)
78
+ if (supportingMatches.length > 0) warnings.push(`supporting_only_match:${changedPath}`)
79
+ continue
80
+ }
81
+ for (const profile of directMatches) directProfileIds.add(profile.id)
82
+ }
83
+
84
+ const transitiveProfileIds = new Set()
85
+ const queue = [...directProfileIds].sort()
86
+ while (queue.length > 0) {
87
+ const profileId = queue.shift()
88
+ const profile = profileById.get(profileId)
89
+ for (const requiredId of profile.requiresProfileIds) {
90
+ if (!profileById.has(requiredId)) throw new Error(`Required profile does not exist: ${requiredId}`)
91
+ if (directProfileIds.has(requiredId) || transitiveProfileIds.has(requiredId)) continue
92
+ transitiveProfileIds.add(requiredId)
93
+ queue.push(requiredId)
94
+ queue.sort()
95
+ }
96
+ }
97
+
98
+ const direct = [...directProfileIds].sort()
99
+ if (direct.length > 10) warnings.push(`broad_profile_match:${direct.length}`)
100
+ const flaggedPrimaryIds = direct.filter(profileId => profileById.get(profileId).primary)
101
+ let primaryProfileId = null
102
+ if (flaggedPrimaryIds.length === 1) primaryProfileId = flaggedPrimaryIds[0]
103
+ else if (flaggedPrimaryIds.length > 1) warnings.push(`ambiguous_primary_profile:${flaggedPrimaryIds.join(",")}`)
104
+ else if (direct.length === 1) primaryProfileId = direct[0]
105
+ else if (direct.length > 1) {
106
+ const highestSpecificity = Math.max(...direct.map(profileId => specificityByProfileId.get(profileId) ?? -1))
107
+ const mostSpecific = direct.filter(profileId => specificityByProfileId.get(profileId) === highestSpecificity)
108
+ if (mostSpecific.length === 1) primaryProfileId = mostSpecific[0]
109
+ else warnings.push(`ambiguous_primary_profile:${mostSpecific.join(",")}`)
110
+ }
111
+
112
+ const selectedProfileIds = [...new Set([...directProfileIds, ...transitiveProfileIds])].sort()
113
+ return {
114
+ decision: uncoveredPaths.length > 0 ? FULL_PROFILE_REQUIRED : TARGETED_PROFILES,
115
+ primaryProfileId,
116
+ directProfileIds: direct,
117
+ transitiveProfileIds: [...transitiveProfileIds].sort(),
118
+ supportingProfileIds: [...supportingProfileIds].sort(),
119
+ selectedProfileIds,
120
+ uncoveredPaths: uncoveredPaths.sort(),
121
+ warnings: [...new Set(warnings)].sort(),
122
+ }
123
+ }
124
+
125
+ export function resolveProfileImpactProjection({ changedPaths, profiles, detailed = false }) {
126
+ if (!detailed) return resolveProfileImpact({ changedPaths, profiles })
127
+ const impact = resolveProfileImpactDetailed({ changedPaths, profiles })
128
+ return {
129
+ decision: impact.decision,
130
+ profileIds: impact.selectedProfileIds,
131
+ uncoveredPaths: impact.uncoveredPaths,
132
+ directProfileIds: impact.directProfileIds,
133
+ transitiveProfileIds: impact.transitiveProfileIds,
134
+ }
135
+ }
136
+
137
+ function normalizeProfile(value) {
138
+ if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0 || !Array.isArray(value.paths)) {
139
+ throw new Error("Each profile requires a non-empty id and paths array.")
140
+ }
141
+ const paths = sortedUniqueStrings(value.paths, "Profile paths must be non-empty strings.")
142
+ for (const rule of paths) validateRule(rule)
143
+ return { id: value.id, paths }
144
+ }
145
+
146
+ function normalizeDetailedProfile(value) {
147
+ const normalized = normalizeProfile(value)
148
+ const role = value.role ?? "primary"
149
+ if (!new Set(["primary", "supporting"]).has(role)) {
150
+ throw new Error("Detailed profile role must be primary or supporting.")
151
+ }
152
+ if (value.primary !== undefined && typeof value.primary !== "boolean") {
153
+ throw new Error("Detailed profile primary must be a boolean.")
154
+ }
155
+ const requiresProfileIds = value.requiresProfileIds === undefined
156
+ ? []
157
+ : sortedUniqueStrings(value.requiresProfileIds, "requiresProfileIds must contain non-empty strings.")
158
+ return {
159
+ ...normalized,
160
+ role,
161
+ primary: value.primary ?? false,
162
+ requiresProfileIds,
163
+ }
164
+ }
165
+
166
+ function validateRule(rule) {
167
+ const wildcardIndex = rule.indexOf("*")
168
+ if (wildcardIndex === -1) {
169
+ validateRepoRelativePath(rule, "Profile exact path rule")
170
+ return
171
+ }
172
+ if (!rule.endsWith("/**") || rule.slice(0, -3).includes("*")) {
173
+ throw new Error("Profile path rules support only exact paths and directory/**.")
174
+ }
175
+ validateRepoRelativePath(rule.slice(0, -3), "Profile directory rule prefix")
176
+ }
177
+
178
+ function validateRepoRelativePath(value, label) {
179
+ if (isAbsolute(value) || win32.isAbsolute(value) || value.includes("\\")) {
180
+ throw new Error(`${label} must be a safe repository-relative path.`)
181
+ }
182
+ const segments = value.split("/")
183
+ if (segments.some(segment => segment.length === 0 || segment === "." || segment === "..")) {
184
+ throw new Error(`${label} must not contain empty, dot, or parent segments.`)
185
+ }
186
+ if (value.includes("*")) {
187
+ throw new Error(`${label} must not contain wildcard segments.`)
188
+ }
189
+ if (isReservedRuntimePath(value)) {
190
+ throw new Error(`${label} uses the reserved runtime path .owlcoda/runkit.`)
191
+ }
192
+ }
193
+
194
+ function matchesRule(changedPath, rule) {
195
+ if (!rule.endsWith("/**")) return changedPath === rule
196
+ return changedPath.startsWith(`${rule.slice(0, -3)}/`)
197
+ }
198
+
199
+ function sortedUniqueStrings(values, message) {
200
+ if (values.some(value => typeof value !== "string" || value.length === 0)) throw new Error(message)
201
+ return [...new Set(values)].sort()
202
+ }
203
+
204
+ function isRecord(value) {
205
+ return typeof value === "object" && value !== null && !Array.isArray(value)
206
+ }
207
+
208
+ async function runCli() {
209
+ try {
210
+ let raw = ""
211
+ for await (const chunk of process.stdin) raw += chunk
212
+ const result = resolveProfileImpact(JSON.parse(raw))
213
+ process.stdout.write(`${JSON.stringify(result)}\n`)
214
+ } catch (error) {
215
+ process.stdout.write(`${JSON.stringify({
216
+ decision: "invalid_input",
217
+ error: error instanceof Error ? error.message : String(error),
218
+ })}\n`)
219
+ process.exitCode = 3
220
+ }
221
+ }
222
+
223
+ if (isDirectExecution(import.meta.url)) await runCli()
@@ -0,0 +1,153 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ lstatSync,
4
+ linkSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ realpathSync,
8
+ renameSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import path from "node:path";
13
+
14
+ import {
15
+ RUNTIME_ROOT,
16
+ currentCoreIdentity,
17
+ validateExecutionPin,
18
+ } from "./core-contract.mjs";
19
+
20
+ export function isRecord(value) {
21
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22
+ }
23
+
24
+ export function sha256(value) {
25
+ return createHash("sha256").update(value).digest("hex");
26
+ }
27
+
28
+ export function readJson(filePath) {
29
+ return JSON.parse(readFileSync(filePath, "utf8"));
30
+ }
31
+
32
+ export function writeJsonExclusive(filePath, value) {
33
+ mkdirSync(path.dirname(filePath), { recursive: true });
34
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
35
+ }
36
+
37
+ export function writeJsonExclusiveAtomically(filePath, value) {
38
+ mkdirSync(path.dirname(filePath), { recursive: true });
39
+ const temporaryPath = `${filePath}.tmp-${randomUUID()}`;
40
+ try {
41
+ writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
42
+ linkSync(temporaryPath, filePath);
43
+ } finally {
44
+ rmSync(temporaryPath, { force: true });
45
+ }
46
+ }
47
+
48
+ export function writeJsonAtomically(filePath, value) {
49
+ mkdirSync(path.dirname(filePath), { recursive: true });
50
+ const temporaryPath = `${filePath}.tmp-${process.pid}`;
51
+ rmSync(temporaryPath, { force: true });
52
+ writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
53
+ renameSync(temporaryPath, filePath);
54
+ }
55
+
56
+ export function assertAllowedKeys(value, label, allowedKeys) {
57
+ if (!isRecord(value)) throw new Error(`${label} must be an object.`);
58
+ const allowed = new Set(allowedKeys);
59
+ const unsupported = Object.keys(value).filter((key) => !allowed.has(key));
60
+ if (unsupported.length > 0) {
61
+ throw new Error(`${label} contains unsupported field: ${unsupported[0]}`);
62
+ }
63
+ }
64
+
65
+ export function safeIdentifier(value, label) {
66
+ if (typeof value !== "string"
67
+ || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)
68
+ || value === "."
69
+ || value === "..") {
70
+ throw new Error(`${label} must use letters, digits, dot, underscore, or hyphen without path separators.`);
71
+ }
72
+ return value;
73
+ }
74
+
75
+ function withinRoot(root, candidate) {
76
+ const remainder = path.relative(root, candidate);
77
+ return remainder === ""
78
+ || (!remainder.startsWith(`..${path.sep}`) && remainder !== ".." && !path.isAbsolute(remainder));
79
+ }
80
+
81
+ export function safeRelativePath(value, label, { allowDot = false } = {}) {
82
+ if (allowDot && value === ".") return value;
83
+ if (typeof value !== "string"
84
+ || value.length === 0
85
+ || path.isAbsolute(value)
86
+ || path.win32.isAbsolute(value)
87
+ || value.includes("\\")
88
+ || value.includes("\0")) {
89
+ throw new Error(`${label} must be a safe relative path.`);
90
+ }
91
+ const segments = value.split("/");
92
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
93
+ throw new Error(`${label} must not contain empty, dot, or parent segments.`);
94
+ }
95
+ return value;
96
+ }
97
+
98
+ export function resolveExistingArtifact(workspaceRoot, value, label) {
99
+ const relativePath = safeRelativePath(value, label);
100
+ const candidate = path.resolve(workspaceRoot, relativePath);
101
+ if (!withinRoot(workspaceRoot, candidate)) throw new Error(`${label} escapes the workspace.`);
102
+ return realpathSync(candidate);
103
+ }
104
+
105
+ export function resolveWithinRoot(root, relativePath, label, options = {}) {
106
+ const safePath = safeRelativePath(relativePath, label, options);
107
+ const candidate = path.resolve(root, safePath);
108
+ if (!withinRoot(root, candidate)) throw new Error(`${label} escapes its root.`);
109
+ return candidate;
110
+ }
111
+
112
+ export function relativeToWorkspace(workspaceRoot, absolutePath) {
113
+ const relativePath = path.relative(workspaceRoot, absolutePath).split(path.sep).join("/");
114
+ if (!relativePath || relativePath.startsWith("../") || path.isAbsolute(relativePath)) {
115
+ throw new Error("Artifact path is outside the workspace.");
116
+ }
117
+ return relativePath;
118
+ }
119
+
120
+ export function loadActiveExecution(workspaceRoot, runId) {
121
+ const root = realpathSync(workspaceRoot);
122
+ const runtimeRoot = path.resolve(root, RUNTIME_ROOT);
123
+ const executionsRoot = path.join(runtimeRoot, "executions");
124
+ const executionRoot = path.join(executionsRoot, runId);
125
+ for (const [directoryPath, label] of [
126
+ [runtimeRoot, "RunKit runtime directory"],
127
+ [executionsRoot, "Executions directory"],
128
+ [executionRoot, "Execution directory"],
129
+ ]) {
130
+ const stat = lstatSync(directoryPath);
131
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
132
+ throw new Error(`${label} must be a real directory, not a symlink.`);
133
+ }
134
+ const realDirectory = realpathSync(directoryPath);
135
+ if (realDirectory !== path.resolve(directoryPath) || !withinRoot(root, realDirectory)) {
136
+ throw new Error(`${label} must remain inside the RunKit workspace without symlink ancestors.`);
137
+ }
138
+ }
139
+ const pin = readJson(path.join(executionRoot, "engine-pin.json"));
140
+ const pinGate = validateExecutionPin({ expected: pin, actual: currentCoreIdentity() });
141
+ return { executionRoot, pin, pinGate };
142
+ }
143
+
144
+ export function repositoryActionsFalse() {
145
+ return {
146
+ staged: false,
147
+ committed: false,
148
+ pushed: false,
149
+ tagged: false,
150
+ published: false,
151
+ deployed: false,
152
+ };
153
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ attestQuickReceipt,
3
+ attestQuickReceiptDetails,
4
+ } from "../../packages/attest/src/quick.mjs";
@@ -0,0 +1,41 @@
1
+ import {
2
+ HASH_PATTERN,
3
+ PUBLIC_VERIFIER_LIMITS,
4
+ canonicalJson,
5
+ hasExactKeys,
6
+ isPlainJsonObject,
7
+ isPlainObject,
8
+ isSha256Ref,
9
+ parseJsonStrict as parsePublicJsonStrict,
10
+ sha256Bytes,
11
+ sha256Canonical,
12
+ } from "../../packages/attest/src/formal.mjs";
13
+
14
+ export const QUICK_INPUT_LIMITS = Object.freeze({
15
+ maxInputBytes: PUBLIC_VERIFIER_LIMITS.maxInputBytes,
16
+ maxNestingDepth: PUBLIC_VERIFIER_LIMITS.maxNestingDepth,
17
+ maxJsonNodes: PUBLIC_VERIFIER_LIMITS.maxJsonNodes,
18
+ });
19
+
20
+ export const SHA256_REF_PATTERN = HASH_PATTERN;
21
+
22
+ export function parseJsonStrict(text) {
23
+ try {
24
+ return parsePublicJsonStrict(text);
25
+ } catch (error) {
26
+ if (error?.code === "DUPLICATE_OBJECT_KEY") {
27
+ error.code = "receipt_duplicate_key";
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+
33
+ export {
34
+ canonicalJson,
35
+ hasExactKeys,
36
+ isPlainJsonObject,
37
+ isPlainObject,
38
+ isSha256Ref,
39
+ sha256Bytes,
40
+ sha256Canonical,
41
+ };
@@ -0,0 +1,86 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ readdirSync,
5
+ realpathSync,
6
+ } from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import { attestQuickReceipt } from "./quick-attest.mjs";
10
+ import { quickReceiptRoot } from "./quick-receipt.mjs";
11
+
12
+ function trustedReceiptRoot(workspaceRoot) {
13
+ const workspace = realpathSync(workspaceRoot);
14
+ const root = quickReceiptRoot(workspace);
15
+ let current = workspace;
16
+ for (const segment of path.relative(workspace, root).split(path.sep)) {
17
+ current = path.join(current, segment);
18
+ if (!existsSync(current)) return { status: "missing", root };
19
+ const stat = lstatSync(current);
20
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return { status: "invalid", root };
21
+ const resolved = realpathSync(current);
22
+ const remainder = path.relative(workspace, resolved);
23
+ if (remainder === "" || remainder === ".."
24
+ || remainder.startsWith(`..${path.sep}`) || path.isAbsolute(remainder)) {
25
+ return { status: "invalid", root };
26
+ }
27
+ }
28
+ return { status: "valid", root };
29
+ }
30
+
31
+ export function readLocalQuickMetrics(workspaceRoot) {
32
+ const selected = trustedReceiptRoot(workspaceRoot);
33
+ if (selected.status === "invalid") {
34
+ return {
35
+ status: "quick_receipt_store_invalid",
36
+ exitCode: 3,
37
+ schemaVersion: "OwlCodaRunKitLocalMetricsV1",
38
+ telemetry: false,
39
+ networkRequests: 0,
40
+ inputs: [],
41
+ quick: {
42
+ total: 0,
43
+ passed: 0,
44
+ failed: 0,
45
+ sourceMutated: 0,
46
+ invalid: 0,
47
+ },
48
+ issueCodes: ["quick_receipt_store_invalid"],
49
+ authorizationGranted: false,
50
+ };
51
+ }
52
+ const receiptPaths = selected.status === "valid"
53
+ ? readdirSync(selected.root, { withFileTypes: true })
54
+ .filter((entry) => entry.isDirectory() && !entry.isSymbolicLink())
55
+ .map((entry) => path.join(selected.root, entry.name, "receipt.json"))
56
+ .filter(existsSync)
57
+ .sort()
58
+ : [];
59
+ const totals = {
60
+ total: receiptPaths.length,
61
+ passed: 0,
62
+ failed: 0,
63
+ sourceMutated: 0,
64
+ invalid: 0,
65
+ };
66
+ for (const receiptPath of receiptPaths) {
67
+ try {
68
+ const result = attestQuickReceipt({ receiptPath, workspaceRoot });
69
+ if (result.decision === "GO") totals.passed += 1;
70
+ else if (result.issueCodes.includes("receipt_source_mismatch")) totals.sourceMutated += 1;
71
+ else totals.failed += 1;
72
+ } catch {
73
+ totals.invalid += 1;
74
+ }
75
+ }
76
+ return {
77
+ status: "local_metrics",
78
+ exitCode: 0,
79
+ schemaVersion: "OwlCodaRunKitLocalMetricsV1",
80
+ telemetry: false,
81
+ networkRequests: 0,
82
+ inputs: receiptPaths,
83
+ quick: totals,
84
+ authorizationGranted: false,
85
+ };
86
+ }
@@ -0,0 +1,139 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ existsSync,
5
+ fstatSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readFileSync,
10
+ realpathSync,
11
+ renameSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import path from "node:path";
15
+
16
+ import { sha256Bytes } from "./quick-canonical.mjs";
17
+
18
+ const QUICK_ROOT = ".owlcoda/runkit/quick";
19
+
20
+ function withinRoot(root, candidate) {
21
+ const remainder = path.relative(root, candidate);
22
+ return remainder !== ""
23
+ && !remainder.startsWith(`..${path.sep}`)
24
+ && remainder !== ".."
25
+ && !path.isAbsolute(remainder);
26
+ }
27
+
28
+ function ensureDirectoryChain(workspaceRoot, relativeDirectory) {
29
+ const root = realpathSync(workspaceRoot);
30
+ let current = root;
31
+ for (const segment of relativeDirectory.split("/")) {
32
+ current = path.join(current, segment);
33
+ if (!existsSync(current)) mkdirSync(current);
34
+ const stat = lstatSync(current);
35
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
36
+ throw new Error(`Quick receipt store component must be a real directory: ${current}`);
37
+ }
38
+ const resolved = realpathSync(current);
39
+ if (!withinRoot(root, resolved)) {
40
+ throw new Error(`Quick receipt store escapes the workspace: ${current}`);
41
+ }
42
+ }
43
+ return current;
44
+ }
45
+
46
+ function atomicWriteJson(filePath, value) {
47
+ const temporary = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
48
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
49
+ renameSync(temporary, filePath);
50
+ }
51
+
52
+ export function createQuickReceiptStore(workspaceRoot, receiptId = `quick-${Date.now()}-${randomUUID()}`) {
53
+ if (!/^[a-zA-Z0-9._-]+$/.test(receiptId)) throw new Error("receiptId is unsafe");
54
+ const receiptRoot = ensureDirectoryChain(workspaceRoot, `${QUICK_ROOT}/receipts/${receiptId}`);
55
+ const stdoutPath = path.join(receiptRoot, "stdout.log");
56
+ const stderrPath = path.join(receiptRoot, "stderr.log");
57
+ const stdoutFd = openSync(stdoutPath, "wx", 0o600);
58
+ const stderrFd = openSync(stderrPath, "wx", 0o600);
59
+ const rootStat = lstatSync(receiptRoot);
60
+ const stdoutStat = fstatSync(stdoutFd);
61
+ const stderrStat = fstatSync(stderrFd);
62
+ return {
63
+ receiptId,
64
+ receiptRoot,
65
+ receiptPath: path.join(receiptRoot, "receipt.json"),
66
+ stdoutPath,
67
+ stderrPath,
68
+ stdoutFd,
69
+ stderrFd,
70
+ receiptRootIdentity: {
71
+ dev: rootStat.dev,
72
+ ino: rootStat.ino,
73
+ },
74
+ stdoutIdentity: {
75
+ dev: stdoutStat.dev,
76
+ ino: stdoutStat.ino,
77
+ },
78
+ stderrIdentity: {
79
+ dev: stderrStat.dev,
80
+ ino: stderrStat.ino,
81
+ },
82
+ };
83
+ }
84
+
85
+ export function closeQuickOutputFiles(store) {
86
+ closeSync(store.stdoutFd);
87
+ closeSync(store.stderrFd);
88
+ }
89
+
90
+ function outputArtifact(workspaceRoot, filePath) {
91
+ const bytes = readFileSync(filePath);
92
+ return {
93
+ path: path.relative(realpathSync(workspaceRoot), filePath).split(path.sep).join("/"),
94
+ sha256: sha256Bytes(bytes),
95
+ sizeBytes: bytes.byteLength,
96
+ };
97
+ }
98
+
99
+ function assertStorePath(root, filePath, expectedIdentity, expectedType) {
100
+ const stat = lstatSync(filePath);
101
+ if (stat.isSymbolicLink() || (expectedType === "file" ? !stat.isFile() : !stat.isDirectory())) {
102
+ throw new Error(`Quick receipt store ${expectedType} is not trusted: ${filePath}`);
103
+ }
104
+ if (stat.dev !== expectedIdentity.dev || stat.ino !== expectedIdentity.ino) {
105
+ throw new Error(`Quick receipt store ${expectedType} identity changed: ${filePath}`);
106
+ }
107
+ const resolved = realpathSync(filePath);
108
+ if (!withinRoot(root, resolved)) {
109
+ throw new Error(`Quick receipt store ${expectedType} escapes the workspace: ${filePath}`);
110
+ }
111
+ }
112
+
113
+ function validateQuickReceiptStore(workspaceRoot, store) {
114
+ const root = realpathSync(workspaceRoot);
115
+ assertStorePath(root, store.receiptRoot, store.receiptRootIdentity, "directory");
116
+ assertStorePath(root, store.stdoutPath, store.stdoutIdentity, "file");
117
+ assertStorePath(root, store.stderrPath, store.stderrIdentity, "file");
118
+ }
119
+
120
+ export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
121
+ validateQuickReceiptStore(workspaceRoot, store);
122
+ const complete = {
123
+ ...receipt,
124
+ outputArtifacts: {
125
+ stdout: outputArtifact(workspaceRoot, store.stdoutPath),
126
+ stderr: outputArtifact(workspaceRoot, store.stderrPath),
127
+ },
128
+ };
129
+ atomicWriteJson(store.receiptPath, complete);
130
+ return {
131
+ receipt: complete,
132
+ receiptPath: store.receiptPath,
133
+ receiptSha256: sha256Bytes(readFileSync(store.receiptPath)),
134
+ };
135
+ }
136
+
137
+ export function quickReceiptRoot(workspaceRoot) {
138
+ return path.join(realpathSync(workspaceRoot), ...QUICK_ROOT.split("/"), "receipts");
139
+ }