owlrunkit 0.18.4 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +109 -4
  3. package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +100 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +30 -0
  5. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
  6. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +28 -4
  7. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  8. package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
  9. package/package.json +1 -1
  10. package/packages/attest/README.md +9 -5
  11. package/packages/attest/consumer-surface-v2.json +263 -0
  12. package/packages/attest/distribution-manifest-v1.json +14 -1
  13. package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
  14. package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
  15. package/packages/attest/src/dependency-environment.mjs +128 -0
  16. package/packages/attest/src/index.mjs +1 -0
  17. package/packages/attest/src/quick-receipt-contract.mjs +202 -9
  18. package/packages/attest/src/quick.mjs +45 -4
  19. package/packages/attest/src/workspace-snapshot.mjs +133 -6
  20. package/registry-release-policy-v1.json +5 -5
  21. package/sbom.spdx.json +5 -5
  22. package/scripts/runkit-contract/core-contract.mjs +21 -1
  23. package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
  24. package/scripts/runkit-contract/evidence-store.mjs +130 -0
  25. package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
  26. package/scripts/runkit-contract/onboarding-doctor.mjs +2 -1
  27. package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
  28. package/scripts/runkit-contract/profile-onboarding.mjs +74 -35
  29. package/scripts/runkit-contract/project-cli-resolver.mjs +26 -5
  30. package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
  31. package/scripts/runkit-contract/quick-isolation.mjs +314 -0
  32. package/scripts/runkit-contract/quick-metrics.mjs +51 -3
  33. package/scripts/runkit-contract/quick-receipt.mjs +31 -1
  34. package/scripts/runkit-contract/quick-verify.mjs +216 -25
  35. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
  36. package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
  37. package/scripts/runkit-contract/runkit-cli.mjs +330 -18
  38. package/scripts/runkit-contract/runtime-transition.mjs +478 -0
  39. package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
  40. package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
  41. package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
  42. package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
  43. package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
  44. package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
  45. package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
  46. package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
  47. package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
  48. package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
  49. package/scripts/runkit-contract/team-project.mjs +704 -16
  50. package/scripts/runkit-contract/verification-envelope.mjs +15 -18
@@ -0,0 +1,478 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ fstatSync,
6
+ openSync,
7
+ readSync,
8
+ realpathSync,
9
+ renameSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import path from "node:path";
13
+
14
+ import {
15
+ decodeUtf8Strict,
16
+ hasExactKeys,
17
+ parseJsonStrict,
18
+ readFileBytesBounded,
19
+ sha256Bytes,
20
+ sha256Canonical,
21
+ } from "../../packages/attest/src/formal.mjs";
22
+ import {
23
+ quickSnapshotFingerprintValid,
24
+ validQuickReceiptShape,
25
+ } from "../../packages/attest/src/quick-receipt-contract.mjs";
26
+
27
+ const REPORT_SCHEMA = "OwlCodaRunKitExternalRuntimeReportV1";
28
+ const RECEIPT_SCHEMA = "OwlCodaRunKitRuntimeTransitionReceiptV1";
29
+ const QUICK_RECEIPT_PREFIX = ".owlcoda/runkit/quick/receipts/";
30
+ const SHA256 = /^sha256:[a-f0-9]{64}$/u;
31
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
32
+ const STAGE_STATUSES = new Set(["passed", "failed", "skipped"]);
33
+ const EXTERNAL_MUTATIONS = new Set(["changed", "unchanged", "unknown"]);
34
+ const FINAL_STATE_STATUSES = new Set(["changed", "unchanged", "restored", "unknown"]);
35
+ const ROLLBACK_OUTCOMES = new Set(["not_required", "succeeded", "failed", "unknown"]);
36
+
37
+ function compareCodeUnits(left, right) {
38
+ if (left < right) return -1;
39
+ if (left > right) return 1;
40
+ return 0;
41
+ }
42
+
43
+ function withinRoot(root, candidate) {
44
+ const relative = path.relative(root, candidate);
45
+ return relative !== ""
46
+ && relative !== ".."
47
+ && !relative.startsWith(`..${path.sep}`)
48
+ && !path.isAbsolute(relative);
49
+ }
50
+
51
+ function safeText(value, maximum = 2048) {
52
+ return typeof value === "string"
53
+ && value.length > 0
54
+ && value.length <= maximum
55
+ && !value.includes("\0");
56
+ }
57
+
58
+ function safeReference(value) {
59
+ return safeText(value, 1024) && !/[\r\n]/u.test(value);
60
+ }
61
+
62
+ function safeArtifactPath(value, leafName = null) {
63
+ if (typeof value !== "string" || !value.startsWith(QUICK_RECEIPT_PREFIX) || path.isAbsolute(value)) {
64
+ return false;
65
+ }
66
+ if (value.includes("\\") || /[\0\r\n\t]/u.test(value)) return false;
67
+ const segments = value.split("/");
68
+ return segments.length === 6
69
+ && SAFE_ID.test(segments[4])
70
+ && segments.every(segment => segment.length > 0 && segment !== "." && segment !== "..")
71
+ && (leafName === null || segments.at(-1) === leafName);
72
+ }
73
+
74
+ function validState(value) {
75
+ return hasExactKeys(value, ["status", "stateRef", "fingerprint", "summary"])
76
+ && FINAL_STATE_STATUSES.has(value.status)
77
+ && safeReference(value.stateRef)
78
+ && (value.fingerprint === null || SHA256.test(value.fingerprint))
79
+ && safeText(value.summary);
80
+ }
81
+
82
+ function validStateIdentity(value, captured = false) {
83
+ return hasExactKeys(value, captured
84
+ ? ["stageId", "stateRef", "fingerprint"]
85
+ : ["stateRef", "fingerprint"])
86
+ && (!captured || SAFE_ID.test(value.stageId))
87
+ && safeReference(value.stateRef)
88
+ && (value.fingerprint === null || SHA256.test(value.fingerprint));
89
+ }
90
+
91
+ function normalizeRuntimeReport(report, { commandExitCode, commandSignal }) {
92
+ if (!hasExactKeys(report, [
93
+ "schemaVersion",
94
+ "externalMutation",
95
+ "rollbackPerformed",
96
+ "finalExternalState",
97
+ "failedStage",
98
+ "lastPassedStage",
99
+ "rollbackOutcome",
100
+ "stages",
101
+ "recoveryEvidence",
102
+ "authorizationGranted",
103
+ ]) || report.schemaVersion !== REPORT_SCHEMA || report.authorizationGranted !== false) {
104
+ throw new Error("External runtime report shape is invalid.");
105
+ }
106
+ if (!EXTERNAL_MUTATIONS.has(report.externalMutation)
107
+ || typeof report.rollbackPerformed !== "boolean"
108
+ || !validState(report.finalExternalState)
109
+ || !ROLLBACK_OUTCOMES.has(report.rollbackOutcome)
110
+ || !(report.failedStage === null || SAFE_ID.test(report.failedStage))
111
+ || !(report.lastPassedStage === null || SAFE_ID.test(report.lastPassedStage))) {
112
+ throw new Error("External runtime report mutation, stage, or state value is invalid.");
113
+ }
114
+ if (!Array.isArray(report.stages) || report.stages.length === 0 || report.stages.length > 128) {
115
+ throw new Error("External runtime report requires a bounded non-empty stage list.");
116
+ }
117
+ const stageIds = new Set();
118
+ for (const stage of report.stages) {
119
+ if (!hasExactKeys(stage, ["stageId", "status", "summary", "evidenceRefs"])
120
+ || !SAFE_ID.test(stage.stageId)
121
+ || !STAGE_STATUSES.has(stage.status)
122
+ || !safeText(stage.summary)
123
+ || !Array.isArray(stage.evidenceRefs)
124
+ || stage.evidenceRefs.length > 128
125
+ || stage.evidenceRefs.some(value => !safeReference(value))) {
126
+ throw new Error("External runtime stage shape is invalid.");
127
+ }
128
+ if (stageIds.has(stage.stageId)) throw new Error("External runtime stage identifiers must be unique.");
129
+ stageIds.add(stage.stageId);
130
+ }
131
+ const failedIndexes = report.stages
132
+ .map((stage, index) => stage.status === "failed" ? index : -1)
133
+ .filter(index => index >= 0);
134
+ if (failedIndexes.length > 1) throw new Error("External runtime report supports one primary failed stage.");
135
+ const failedIndex = failedIndexes[0] ?? -1;
136
+ const expectedFailedStage = failedIndex < 0 ? null : report.stages[failedIndex].stageId;
137
+ const passedSearchEnd = failedIndex < 0 ? report.stages.length : failedIndex;
138
+ const expectedLastPassedStage = report.stages
139
+ .slice(0, passedSearchEnd)
140
+ .filter(stage => stage.status === "passed")
141
+ .at(-1)?.stageId ?? null;
142
+ if (report.failedStage !== expectedFailedStage || report.lastPassedStage !== expectedLastPassedStage) {
143
+ throw new Error("External runtime report failedStage or lastPassedStage contradicts ordered stages.");
144
+ }
145
+ if ((commandExitCode !== 0 || commandSignal !== null) && report.failedStage === null) {
146
+ throw new Error("A failed exact command must name its primary failed runtime stage.");
147
+ }
148
+ if (report.rollbackPerformed) {
149
+ if (report.rollbackOutcome === "not_required") {
150
+ throw new Error("Performed rollback cannot have not_required outcome.");
151
+ }
152
+ } else if (report.rollbackOutcome !== "not_required") {
153
+ throw new Error("Rollback outcome must be not_required when no rollback was performed.");
154
+ }
155
+ if (report.finalExternalState.status === "restored" && !report.rollbackPerformed) {
156
+ throw new Error("A restored final state requires a performed rollback.");
157
+ }
158
+ if (report.rollbackOutcome === "succeeded" && report.finalExternalState.status !== "restored") {
159
+ throw new Error("Successful rollback requires an explicitly restored final state.");
160
+ }
161
+ if (report.externalMutation === "unchanged"
162
+ && (report.rollbackPerformed || report.finalExternalState.status !== "unchanged")) {
163
+ throw new Error("Unchanged external runtime cannot report rollback or a changed final state.");
164
+ }
165
+ if (!Array.isArray(report.recoveryEvidence) || report.recoveryEvidence.length > 128) {
166
+ throw new Error("External runtime recovery evidence must be a bounded array.");
167
+ }
168
+ for (const evidence of report.recoveryEvidence) {
169
+ if (!hasExactKeys(evidence, ["artifactRef", "capturedBefore", "restoresTo"])
170
+ || !safeReference(evidence.artifactRef)
171
+ || !validStateIdentity(evidence.capturedBefore, true)
172
+ || !validStateIdentity(evidence.restoresTo, false)
173
+ || !stageIds.has(evidence.capturedBefore.stageId)) {
174
+ throw new Error("External runtime recovery evidence semantics are invalid.");
175
+ }
176
+ }
177
+ if (report.rollbackOutcome === "succeeded") {
178
+ const finalIdentity = `${report.finalExternalState.stateRef}\0${report.finalExternalState.fingerprint ?? ""}`;
179
+ const restoresFinal = report.recoveryEvidence.some(evidence => (
180
+ `${evidence.restoresTo.stateRef}\0${evidence.restoresTo.fingerprint ?? ""}` === finalIdentity
181
+ ));
182
+ if (!restoresFinal) {
183
+ throw new Error("Successful rollback must name recovery evidence that restores the final state.");
184
+ }
185
+ }
186
+ return JSON.parse(JSON.stringify(report));
187
+ }
188
+
189
+ function workspaceMutation(quickReceipt) {
190
+ if (quickReceipt.mutationClasses !== undefined) {
191
+ return quickReceipt.mutationClasses.workspaceSource === "changed"
192
+ || quickReceipt.mutationClasses.boundIgnoredPaths === "changed"
193
+ ? "changed"
194
+ : "unchanged";
195
+ }
196
+ return quickReceipt.workspaceBefore.sourceFingerprint === quickReceipt.workspaceAfter.sourceFingerprint
197
+ ? "unchanged"
198
+ : "changed";
199
+ }
200
+
201
+ function transitionDecision({ quickReceipt, report, workspaceMutationValue }) {
202
+ if (workspaceMutationValue === "changed") return "external_transition_invalidated";
203
+ const commandFailed = quickReceipt.exitResult.exitCode !== 0 || quickReceipt.exitResult.signal !== null;
204
+ if (commandFailed || report.failedStage !== null) {
205
+ return report.rollbackOutcome === "succeeded" && report.finalExternalState.status === "restored"
206
+ ? "external_transition_failed_recovered"
207
+ : "external_transition_failed_unrecovered";
208
+ }
209
+ if (report.externalMutation === "unknown"
210
+ || report.finalExternalState.status === "unknown"
211
+ || report.rollbackOutcome === "unknown") {
212
+ return "external_transition_indeterminate";
213
+ }
214
+ return "external_transition_passed";
215
+ }
216
+
217
+ function readBoundedJson(root, requestedPath) {
218
+ const absolute = path.resolve(root, requestedPath);
219
+ if (!withinRoot(root, absolute)) throw new Error("Runtime transition material escapes the workspace.");
220
+ const loaded = readFileBytesBounded(absolute);
221
+ if (loaded.absolutePath !== absolute) throw new Error("Runtime transition material must not traverse symlinks.");
222
+ return {
223
+ absolute,
224
+ bytes: loaded.bytes,
225
+ value: parseJsonStrict(decodeUtf8Strict(loaded.bytes)),
226
+ };
227
+ }
228
+
229
+ function verifyArtifactBytes(root, artifact) {
230
+ if (!hasExactKeys(artifact, ["path", "sha256", "sizeBytes"])
231
+ || !safeArtifactPath(artifact.path)
232
+ || !SHA256.test(artifact.sha256)
233
+ || !Number.isSafeInteger(artifact.sizeBytes)
234
+ || artifact.sizeBytes < 0) {
235
+ throw new Error("Quick material reference is invalid.");
236
+ }
237
+ const absolute = path.resolve(root, artifact.path);
238
+ if (!withinRoot(root, absolute)) throw new Error("Quick material escapes the workspace.");
239
+ if (realpathSync(absolute) !== absolute) throw new Error("Quick material must not traverse symlinks.");
240
+ const descriptor = openSync(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
241
+ try {
242
+ const metadata = fstatSync(descriptor);
243
+ if (!metadata.isFile() || metadata.size !== artifact.sizeBytes) {
244
+ throw new Error("Quick material size or type changed.");
245
+ }
246
+ const hash = createHash("sha256");
247
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
248
+ let total = 0;
249
+ while (true) {
250
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.byteLength, null);
251
+ if (bytesRead === 0) break;
252
+ total += bytesRead;
253
+ hash.update(buffer.subarray(0, bytesRead));
254
+ }
255
+ if (total !== artifact.sizeBytes || `sha256:${hash.digest("hex")}` !== artifact.sha256) {
256
+ throw new Error("Quick material bytes changed.");
257
+ }
258
+ } finally {
259
+ closeSync(descriptor);
260
+ }
261
+ }
262
+
263
+ function validateQuickMaterial(root, loaded) {
264
+ const quick = loaded.value;
265
+ if (!validQuickReceiptShape(quick)
266
+ || !quickSnapshotFingerprintValid(quick.workspaceBefore)
267
+ || !quickSnapshotFingerprintValid(quick.workspaceAfter)) {
268
+ throw new Error("Runtime transition requires a strict Quick receipt.");
269
+ }
270
+ const sourceRoot = quick.schemaVersion === "OwlCodaQuickVerificationReceiptV3"
271
+ ? quick.sourceWorkspaceRoot
272
+ : quick.exactCommand.cwd;
273
+ if (realpathSync(sourceRoot) !== root) throw new Error("Quick receipt belongs to another source workspace.");
274
+ verifyArtifactBytes(root, quick.outputArtifacts.stdout);
275
+ verifyArtifactBytes(root, quick.outputArtifacts.stderr);
276
+ if (quick.inputArtifacts?.stdin !== undefined) verifyArtifactBytes(root, quick.inputArtifacts.stdin);
277
+ return quick;
278
+ }
279
+
280
+ function receiptBody({ root, quickLoaded, reportLoaded }) {
281
+ const quick = validateQuickMaterial(root, quickLoaded);
282
+ const report = normalizeRuntimeReport(reportLoaded.value, {
283
+ commandExitCode: quick.exitResult.exitCode,
284
+ commandSignal: quick.exitResult.signal,
285
+ });
286
+ const quickRelative = path.relative(root, quickLoaded.absolute).split(path.sep).join("/");
287
+ const reportRelative = path.relative(root, reportLoaded.absolute).split(path.sep).join("/");
288
+ if (!safeArtifactPath(quickRelative, "receipt.json")
289
+ || !safeArtifactPath(reportRelative, "runtime-report.json")
290
+ || path.posix.dirname(quickRelative) !== path.posix.dirname(reportRelative)) {
291
+ throw new Error("Runtime report must be beside its Quick receipt.");
292
+ }
293
+ const workspaceMutationValue = workspaceMutation(quick);
294
+ return {
295
+ schemaVersion: RECEIPT_SCHEMA,
296
+ receiptId: `runtime-${quick.receiptId}`,
297
+ quickReceipt: {
298
+ receiptId: quick.receiptId,
299
+ path: quickRelative,
300
+ sha256: sha256Bytes(quickLoaded.bytes),
301
+ },
302
+ runtimeReport: {
303
+ path: reportRelative,
304
+ sha256: sha256Bytes(reportLoaded.bytes),
305
+ sizeBytes: reportLoaded.bytes.byteLength,
306
+ },
307
+ reportingBasis: "exact_command_report",
308
+ workspaceMutation: workspaceMutationValue,
309
+ externalMutation: report.externalMutation,
310
+ rollbackPerformed: report.rollbackPerformed,
311
+ finalExternalState: report.finalExternalState,
312
+ failedStage: report.failedStage,
313
+ lastPassedStage: report.lastPassedStage,
314
+ rollbackOutcome: report.rollbackOutcome,
315
+ stages: report.stages,
316
+ recoveryEvidence: report.recoveryEvidence,
317
+ transitionDecision: transitionDecision({
318
+ quickReceipt: quick,
319
+ report,
320
+ workspaceMutationValue,
321
+ }),
322
+ proven: [
323
+ "The Quick receipt bytes, command-produced runtime report bytes, and projected transition fields are bound together.",
324
+ "workspaceMutation is derived from the Quick before/after workspace evidence.",
325
+ ],
326
+ notProven: [
327
+ "RunKit did not independently observe the external runtime; external fields are exact-command reports.",
328
+ "No Git, release, deployment, production, or business authority is granted.",
329
+ ],
330
+ createdAt: new Date().toISOString(),
331
+ authorizationGranted: false,
332
+ };
333
+ }
334
+
335
+ function persistReceipt(filePath, body) {
336
+ const receipt = { ...body, receiptSha256: sha256Canonical(body) };
337
+ const temporary = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
338
+ writeFileSync(temporary, `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx", mode: 0o600 });
339
+ renameSync(temporary, filePath);
340
+ return receipt;
341
+ }
342
+
343
+ function receiptSummary(receipt, receiptPath) {
344
+ return {
345
+ status: "runtime_transition_recorded",
346
+ receiptPath,
347
+ receiptSha256: receipt.receiptSha256,
348
+ reportingBasis: receipt.reportingBasis,
349
+ workspaceMutation: receipt.workspaceMutation,
350
+ externalMutation: receipt.externalMutation,
351
+ rollbackPerformed: receipt.rollbackPerformed,
352
+ finalExternalState: receipt.finalExternalState,
353
+ failedStage: receipt.failedStage,
354
+ lastPassedStage: receipt.lastPassedStage,
355
+ rollbackOutcome: receipt.rollbackOutcome,
356
+ stages: receipt.stages,
357
+ recoveryEvidence: receipt.recoveryEvidence,
358
+ transitionDecision: receipt.transitionDecision,
359
+ proven: receipt.proven,
360
+ notProven: receipt.notProven,
361
+ authorizationGranted: false,
362
+ };
363
+ }
364
+
365
+ export function runtimeReportPath(receiptRoot) {
366
+ return path.join(receiptRoot, "runtime-report.json");
367
+ }
368
+
369
+ export function captureRuntimeTransition({ workspaceRoot, quickReceiptPath, runtimeReportPath: reportPath }) {
370
+ const root = realpathSync(workspaceRoot);
371
+ const quickLoaded = readBoundedJson(root, quickReceiptPath);
372
+ const reportLoaded = readBoundedJson(root, reportPath);
373
+ const body = receiptBody({ root, quickLoaded, reportLoaded });
374
+ const receiptPath = path.join(path.dirname(quickLoaded.absolute), "runtime-transition.json");
375
+ const receipt = persistReceipt(receiptPath, body);
376
+ const verified = verifyRuntimeTransitionReceipt({ workspaceRoot: root, receiptPath });
377
+ if (verified.status !== "runtime_transition_receipt_valid") {
378
+ throw new Error(`Persisted runtime transition receipt is invalid: ${verified.issueCodes.join(",")}`);
379
+ }
380
+ return receiptSummary(receipt, receiptPath);
381
+ }
382
+
383
+ export function verifyRuntimeTransitionReceipt({ workspaceRoot, receiptPath }) {
384
+ const issues = [];
385
+ try {
386
+ const root = realpathSync(workspaceRoot);
387
+ const loaded = readBoundedJson(root, receiptPath);
388
+ const receipt = loaded.value;
389
+ if (!hasExactKeys(receipt, [
390
+ "schemaVersion",
391
+ "receiptId",
392
+ "quickReceipt",
393
+ "runtimeReport",
394
+ "reportingBasis",
395
+ "workspaceMutation",
396
+ "externalMutation",
397
+ "rollbackPerformed",
398
+ "finalExternalState",
399
+ "failedStage",
400
+ "lastPassedStage",
401
+ "rollbackOutcome",
402
+ "stages",
403
+ "recoveryEvidence",
404
+ "transitionDecision",
405
+ "proven",
406
+ "notProven",
407
+ "createdAt",
408
+ "authorizationGranted",
409
+ "receiptSha256",
410
+ ]) || receipt.schemaVersion !== RECEIPT_SCHEMA
411
+ || receipt.authorizationGranted !== false
412
+ || !SAFE_ID.test(receipt.receiptId)
413
+ || !hasExactKeys(receipt.quickReceipt, ["receiptId", "path", "sha256"])
414
+ || !SAFE_ID.test(receipt.quickReceipt.receiptId)
415
+ || !safeArtifactPath(receipt.quickReceipt.path, "receipt.json")
416
+ || !SHA256.test(receipt.quickReceipt.sha256)
417
+ || !hasExactKeys(receipt.runtimeReport, ["path", "sha256", "sizeBytes"])
418
+ || !safeArtifactPath(receipt.runtimeReport.path, "runtime-report.json")
419
+ || !SHA256.test(receipt.runtimeReport.sha256)
420
+ || !Number.isSafeInteger(receipt.runtimeReport.sizeBytes)
421
+ || receipt.runtimeReport.sizeBytes < 0
422
+ || receipt.reportingBasis !== "exact_command_report"
423
+ || !Array.isArray(receipt.proven)
424
+ || receipt.proven.length < 2
425
+ || receipt.proven.some(value => !safeText(value))
426
+ || !Array.isArray(receipt.notProven)
427
+ || receipt.notProven.length < 2
428
+ || receipt.notProven.some(value => !safeText(value))
429
+ || !SHA256.test(receipt.receiptSha256)
430
+ || !Number.isFinite(Date.parse(receipt.createdAt))) {
431
+ throw new Error("Runtime transition receipt shape is invalid.");
432
+ }
433
+ const { receiptSha256, ...withoutHash } = receipt;
434
+ if (sha256Canonical(withoutHash) !== receiptSha256) issues.push("runtime_transition_receipt_hash_mismatch");
435
+ const quickLoaded = readBoundedJson(root, receipt.quickReceipt.path);
436
+ if (sha256Bytes(quickLoaded.bytes) !== receipt.quickReceipt.sha256
437
+ || quickLoaded.value.receiptId !== receipt.quickReceipt.receiptId) {
438
+ issues.push("quick_receipt_mismatch");
439
+ }
440
+ const reportLoaded = readBoundedJson(root, receipt.runtimeReport.path);
441
+ if (sha256Bytes(reportLoaded.bytes) !== receipt.runtimeReport.sha256
442
+ || reportLoaded.bytes.byteLength !== receipt.runtimeReport.sizeBytes) {
443
+ issues.push("runtime_report_mismatch");
444
+ }
445
+ let expected;
446
+ try {
447
+ expected = receiptBody({ root, quickLoaded, reportLoaded });
448
+ } catch (error) {
449
+ issues.push("runtime_transition_material_invalid");
450
+ }
451
+ if (expected !== undefined) {
452
+ const comparable = { ...expected, createdAt: receipt.createdAt };
453
+ if (JSON.stringify(comparable) !== JSON.stringify(withoutHash)) {
454
+ issues.push("runtime_transition_projection_mismatch");
455
+ }
456
+ }
457
+ return {
458
+ status: issues.length === 0
459
+ ? "runtime_transition_receipt_valid"
460
+ : "runtime_transition_receipt_invalid",
461
+ exitCode: issues.length === 0 ? 0 : 1,
462
+ transitionDecision: receipt.transitionDecision,
463
+ issueCodes: [...new Set(issues)].sort(compareCodeUnits),
464
+ receiptPath: loaded.absolute,
465
+ receiptSha256: sha256Bytes(loaded.bytes),
466
+ authorizationGranted: false,
467
+ };
468
+ } catch (error) {
469
+ return {
470
+ status: "runtime_transition_receipt_invalid",
471
+ exitCode: 1,
472
+ transitionDecision: null,
473
+ issueCodes: ["runtime_transition_unreadable_or_invalid"],
474
+ issues: [error instanceof Error ? error.message : String(error)],
475
+ authorizationGranted: false,
476
+ };
477
+ }
478
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://owlcoda.dev/schemas/runkit/efficiency-event-v1.json",
4
+ "title": "OwlCoda RunKit Efficiency Event V1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schemaVersion",
9
+ "eventId",
10
+ "kind",
11
+ "occurredAt",
12
+ "workflowId",
13
+ "summary",
14
+ "evidenceRefs",
15
+ "authorizationGranted"
16
+ ],
17
+ "properties": {
18
+ "schemaVersion": { "const": "OwlCodaRunKitEfficiencyEventV1" },
19
+ "eventId": { "$ref": "#/$defs/id" },
20
+ "kind": {
21
+ "enum": [
22
+ "workflow_started",
23
+ "false_block_confirmed",
24
+ "manual_intervention",
25
+ "acceptable_result_reached"
26
+ ]
27
+ },
28
+ "occurredAt": { "type": "string", "format": "date-time" },
29
+ "workflowId": { "$ref": "#/$defs/id" },
30
+ "summary": { "type": "string", "minLength": 1, "maxLength": 2000 },
31
+ "evidenceRefs": {
32
+ "type": "array",
33
+ "maxItems": 100,
34
+ "uniqueItems": true,
35
+ "items": { "type": "string", "minLength": 1, "maxLength": 2048 }
36
+ },
37
+ "authorizationGranted": { "const": false }
38
+ },
39
+ "$defs": {
40
+ "id": {
41
+ "type": "string",
42
+ "maxLength": 128,
43
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
44
+ }
45
+ },
46
+ "examples": [{
47
+ "schemaVersion": "OwlCodaRunKitEfficiencyEventV1",
48
+ "eventId": "workflow-start-001",
49
+ "kind": "workflow_started",
50
+ "occurredAt": "2026-08-13T00:00:00.000Z",
51
+ "workflowId": "wave-001",
52
+ "summary": "The bounded workflow started.",
53
+ "evidenceRefs": [],
54
+ "authorizationGranted": false
55
+ }]
56
+ }