eval-quality 0.1.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 (230) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +355 -0
  3. package/corpus/dev/README.md +36 -0
  4. package/corpus/dev/compile-seal-example/brief.json +1 -0
  5. package/corpus/dev/compile-seal-example/contract.json +1 -0
  6. package/corpus/dev/contracts/absent-collection-locations.json +1 -0
  7. package/corpus/dev/contracts/absent-sibling-groups.json +1 -0
  8. package/corpus/dev/contracts/absent-success-indicator.json +1 -0
  9. package/corpus/dev/contracts/empty-channel-roles.json +1 -0
  10. package/corpus/dev/contracts/empty-collection-locations.json +1 -0
  11. package/corpus/dev/contracts/empty-request-shapes.json +1 -0
  12. package/corpus/dev/contracts/empty-sibling-groups.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -0
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -0
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -0
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -0
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -0
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -0
  19. package/corpus/dev/contracts/satisfied-declarations.json +1 -0
  20. package/corpus/dev/contracts/single-required-response-key.json +1 -0
  21. package/corpus/dev/contracts/split-indicator-oracle.json +1 -0
  22. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -0
  23. package/corpus/dev/contracts/unnamed-reference-set.json +1 -0
  24. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -0
  25. package/corpus/dev/index.json +1 -0
  26. package/dist/adapters/index.d.ts +12 -0
  27. package/dist/adapters/index.js +3 -0
  28. package/dist/adapters/local-corpus-adapter.d.ts +7 -0
  29. package/dist/adapters/local-corpus-adapter.js +78 -0
  30. package/dist/adapters/node-file-system-adapter.d.ts +7 -0
  31. package/dist/adapters/node-file-system-adapter.js +39 -0
  32. package/dist/adapters/port-boundary.d.ts +18 -0
  33. package/dist/adapters/port-boundary.js +83 -0
  34. package/dist/adapters/system-clock-adapter.d.ts +10 -0
  35. package/dist/adapters/system-clock-adapter.js +18 -0
  36. package/dist/application/compile.d.ts +4 -0
  37. package/dist/application/compile.js +24 -0
  38. package/dist/application/diagnostics.d.ts +22 -0
  39. package/dist/application/diagnostics.js +18 -0
  40. package/dist/application/index.d.ts +23 -0
  41. package/dist/application/index.js +17 -0
  42. package/dist/application/invoke-port.d.ts +2 -0
  43. package/dist/application/invoke-port.js +45 -0
  44. package/dist/application/preflight.d.ts +27 -0
  45. package/dist/application/preflight.js +131 -0
  46. package/dist/application/seal.d.ts +4 -0
  47. package/dist/application/seal.js +28 -0
  48. package/dist/application/serialize.d.ts +6 -0
  49. package/dist/application/serialize.js +9 -0
  50. package/dist/cli/arguments.d.ts +28 -0
  51. package/dist/cli/arguments.js +167 -0
  52. package/dist/cli/exit-codes.d.ts +36 -0
  53. package/dist/cli/exit-codes.js +39 -0
  54. package/dist/cli/main.d.ts +2 -0
  55. package/dist/cli/main.js +143 -0
  56. package/dist/cli/render.d.ts +26 -0
  57. package/dist/cli/render.js +53 -0
  58. package/dist/cli/run.d.ts +41 -0
  59. package/dist/cli/run.js +203 -0
  60. package/dist/core/canonical/canonicalize.d.ts +1 -0
  61. package/dist/core/canonical/canonicalize.js +113 -0
  62. package/dist/core/canonical/digest.d.ts +7 -0
  63. package/dist/core/canonical/digest.js +81 -0
  64. package/dist/core/canonical/scan-json.d.ts +1 -0
  65. package/dist/core/canonical/scan-json.js +298 -0
  66. package/dist/core/canonical/value-domain.d.ts +4 -0
  67. package/dist/core/canonical/value-domain.js +113 -0
  68. package/dist/core/compile/compile.d.ts +24 -0
  69. package/dist/core/compile/compile.js +67 -0
  70. package/dist/core/compile/declarations.d.ts +5 -0
  71. package/dist/core/compile/declarations.js +22 -0
  72. package/dist/core/compile/expression-legality.d.ts +14 -0
  73. package/dist/core/compile/expression-legality.js +346 -0
  74. package/dist/core/compile/forbidden-inputs.d.ts +5 -0
  75. package/dist/core/compile/forbidden-inputs.js +19 -0
  76. package/dist/core/compile/interface-inventory.d.ts +10 -0
  77. package/dist/core/compile/interface-inventory.js +55 -0
  78. package/dist/core/compile/oracle-alignment.d.ts +14 -0
  79. package/dist/core/compile/oracle-alignment.js +93 -0
  80. package/dist/core/compile/reachability.d.ts +17 -0
  81. package/dist/core/compile/reachability.js +177 -0
  82. package/dist/core/compile/rubrics.d.ts +55 -0
  83. package/dist/core/compile/rubrics.js +198 -0
  84. package/dist/core/compile/scripting-bound.d.ts +5 -0
  85. package/dist/core/compile/scripting-bound.js +151 -0
  86. package/dist/core/compile/sensitivity-witness.d.ts +73 -0
  87. package/dist/core/compile/sensitivity-witness.js +232 -0
  88. package/dist/core/compile/waivers.d.ts +3 -0
  89. package/dist/core/compile/waivers.js +21 -0
  90. package/dist/core/coverage/coverage.d.ts +18 -0
  91. package/dist/core/coverage/coverage.js +47 -0
  92. package/dist/core/coverage/relevance.d.ts +75 -0
  93. package/dist/core/coverage/relevance.js +186 -0
  94. package/dist/core/coverage/rules.d.ts +7 -0
  95. package/dist/core/coverage/rules.js +18 -0
  96. package/dist/core/coverage/satisfaction.d.ts +107 -0
  97. package/dist/core/coverage/satisfaction.js +415 -0
  98. package/dist/core/coverage/table.d.ts +26 -0
  99. package/dist/core/coverage/table.js +222 -0
  100. package/dist/core/declared-inputs.d.ts +16 -0
  101. package/dist/core/declared-inputs.js +18 -0
  102. package/dist/core/evaluate/evidence-resolution.d.ts +54 -0
  103. package/dist/core/evaluate/evidence-resolution.js +153 -0
  104. package/dist/core/evaluate/operators.d.ts +87 -0
  105. package/dist/core/evaluate/operators.js +350 -0
  106. package/dist/core/evaluate/resolution.d.ts +40 -0
  107. package/dist/core/evaluate/resolution.js +336 -0
  108. package/dist/core/evaluate/resolved-value.d.ts +9 -0
  109. package/dist/core/evaluate/resolved-value.js +6 -0
  110. package/dist/core/failure-codes.d.ts +18 -0
  111. package/dist/core/failure-codes.js +50 -0
  112. package/dist/core/lineage/chain.d.ts +49 -0
  113. package/dist/core/lineage/chain.js +210 -0
  114. package/dist/core/lineage/freeze.d.ts +12 -0
  115. package/dist/core/lineage/freeze.js +35 -0
  116. package/dist/core/lineage/stage-table.d.ts +43 -0
  117. package/dist/core/lineage/stage-table.js +106 -0
  118. package/dist/core/preflight/plan.d.ts +67 -0
  119. package/dist/core/preflight/plan.js +313 -0
  120. package/dist/core/preflight/projection.d.ts +40 -0
  121. package/dist/core/preflight/projection.js +96 -0
  122. package/dist/core/preflight/reduce.d.ts +8 -0
  123. package/dist/core/preflight/reduce.js +180 -0
  124. package/dist/core/preflight/witness-evidence.d.ts +42 -0
  125. package/dist/core/preflight/witness-evidence.js +88 -0
  126. package/dist/core/probe/target-policy.d.ts +63 -0
  127. package/dist/core/probe/target-policy.js +328 -0
  128. package/dist/core/schemas/artifact-reference.d.ts +19 -0
  129. package/dist/core/schemas/artifact-reference.js +30 -0
  130. package/dist/core/schemas/artifact.d.ts +1153 -0
  131. package/dist/core/schemas/artifact.js +84 -0
  132. package/dist/core/schemas/constraint-ledger.d.ts +57 -0
  133. package/dist/core/schemas/constraint-ledger.js +143 -0
  134. package/dist/core/schemas/eval-contract.d.ts +402 -0
  135. package/dist/core/schemas/eval-contract.js +147 -0
  136. package/dist/core/schemas/evaluator-configuration.d.ts +37 -0
  137. package/dist/core/schemas/evaluator-configuration.js +43 -0
  138. package/dist/core/schemas/evidence-artifact.d.ts +471 -0
  139. package/dist/core/schemas/evidence-artifact.js +232 -0
  140. package/dist/core/schemas/expression.d.ts +197 -0
  141. package/dist/core/schemas/expression.js +284 -0
  142. package/dist/core/schemas/faults.d.ts +9 -0
  143. package/dist/core/schemas/faults.js +32 -0
  144. package/dist/core/schemas/interface.d.ts +417 -0
  145. package/dist/core/schemas/interface.js +115 -0
  146. package/dist/core/schemas/isolation-manifest.d.ts +136 -0
  147. package/dist/core/schemas/isolation-manifest.js +92 -0
  148. package/dist/core/schemas/lineage.d.ts +21 -0
  149. package/dist/core/schemas/lineage.js +28 -0
  150. package/dist/core/schemas/oracle.d.ts +72 -0
  151. package/dist/core/schemas/oracle.js +35 -0
  152. package/dist/core/schemas/plan.d.ts +115 -0
  153. package/dist/core/schemas/plan.js +58 -0
  154. package/dist/core/schemas/pointer.d.ts +63 -0
  155. package/dist/core/schemas/pointer.js +94 -0
  156. package/dist/core/schemas/port-messages.d.ts +103 -0
  157. package/dist/core/schemas/port-messages.js +90 -0
  158. package/dist/core/schemas/preflight-verdict.d.ts +60 -0
  159. package/dist/core/schemas/preflight-verdict.js +38 -0
  160. package/dist/core/schemas/primitives.d.ts +78 -0
  161. package/dist/core/schemas/primitives.js +121 -0
  162. package/dist/core/schemas/private-artifact-manifest.d.ts +56 -0
  163. package/dist/core/schemas/private-artifact-manifest.js +46 -0
  164. package/dist/core/schemas/probe-body.d.ts +25 -0
  165. package/dist/core/schemas/probe-body.js +23 -0
  166. package/dist/core/schemas/probe-policy.d.ts +76 -0
  167. package/dist/core/schemas/probe-policy.js +33 -0
  168. package/dist/core/schemas/probe.d.ts +189 -0
  169. package/dist/core/schemas/probe.js +82 -0
  170. package/dist/core/schemas/publish.d.ts +30 -0
  171. package/dist/core/schemas/publish.js +135 -0
  172. package/dist/core/schemas/reference-set.d.ts +18 -0
  173. package/dist/core/schemas/reference-set.js +27 -0
  174. package/dist/core/schemas/rubric.d.ts +62 -0
  175. package/dist/core/schemas/rubric.js +68 -0
  176. package/dist/core/schemas/scoring-policy.d.ts +28 -0
  177. package/dist/core/schemas/scoring-policy.js +51 -0
  178. package/dist/core/schemas/sealed-evaluator-brief.d.ts +80 -0
  179. package/dist/core/schemas/sealed-evaluator-brief.js +59 -0
  180. package/dist/core/schemas/sealed-run-record.d.ts +382 -0
  181. package/dist/core/schemas/sealed-run-record.js +215 -0
  182. package/dist/core/schemas/sensitivity-witness.d.ts +138 -0
  183. package/dist/core/schemas/sensitivity-witness.js +86 -0
  184. package/dist/core/schemas/verdict.d.ts +25 -0
  185. package/dist/core/schemas/verdict.js +20 -0
  186. package/dist/core/schemas/waiver.d.ts +17 -0
  187. package/dist/core/schemas/waiver.js +24 -0
  188. package/dist/core/seal/derived-reference.d.ts +36 -0
  189. package/dist/core/seal/derived-reference.js +383 -0
  190. package/dist/core/seal/direction-prose.d.ts +13 -0
  191. package/dist/core/seal/direction-prose.js +120 -0
  192. package/dist/core/seal/plan-index.d.ts +63 -0
  193. package/dist/core/seal/plan-index.js +155 -0
  194. package/dist/core/seal/scripting-audit.d.ts +15 -0
  195. package/dist/core/seal/scripting-audit.js +51 -0
  196. package/dist/core/seal/seal.d.ts +18 -0
  197. package/dist/core/seal/seal.js +155 -0
  198. package/dist/core/stage-contracts.d.ts +30 -0
  199. package/dist/core/stage-contracts.js +1 -0
  200. package/dist/index.d.ts +15 -0
  201. package/dist/index.js +22 -0
  202. package/dist/ports/clock-port.d.ts +13 -0
  203. package/dist/ports/clock-port.js +7 -0
  204. package/dist/ports/corpus-port.d.ts +16 -0
  205. package/dist/ports/corpus-port.js +7 -0
  206. package/dist/ports/environment-probe-port.d.ts +76 -0
  207. package/dist/ports/environment-probe-port.js +32 -0
  208. package/dist/ports/file-system-port.d.ts +27 -0
  209. package/dist/ports/file-system-port.js +11 -0
  210. package/dist/ports/port.d.ts +33 -0
  211. package/dist/ports/port.js +14 -0
  212. package/dist/testing/conformance.d.ts +88 -0
  213. package/dist/testing/conformance.js +281 -0
  214. package/dist/testing/index.d.ts +26 -0
  215. package/dist/testing/index.js +18 -0
  216. package/dist/testing/probe-conformance.d.ts +45 -0
  217. package/dist/testing/probe-conformance.js +186 -0
  218. package/package.json +130 -0
  219. package/schemas/artifact-reference.schema.json +64 -0
  220. package/schemas/eval-contract.schema.json +2145 -0
  221. package/schemas/evaluator-configuration.schema.json +199 -0
  222. package/schemas/evidence-artifact.schema.json +1229 -0
  223. package/schemas/isolation-manifest.schema.json +394 -0
  224. package/schemas/preflight-verdict.schema.json +114 -0
  225. package/schemas/private-artifact-manifest.schema.json +103 -0
  226. package/schemas/probe.schema.json +1151 -0
  227. package/schemas/rubric.schema.json +148 -0
  228. package/schemas/scoring-policy.schema.json +90 -0
  229. package/schemas/sealed-evaluator-brief.schema.json +268 -0
  230. package/schemas/sealed-run-record.schema.json +875 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * AD-12's presented-chain reader and AD-29's one constructor. Nothing here
3
+ * holds state between calls, and no lineage is tracked across invocations
4
+ * (AD-12). `checks` is AD-12's three named booleans, the shape `emit`
5
+ * serializes onto every Evidence Artifact; `findings` says which of the three
6
+ * failed and why.
7
+ *
8
+ * Chain defects are returned. AD-29 commands no code in either registry and
9
+ * AD-28's audit rule makes minting one a spine amendment, so the nine codes
10
+ * below are a module-local vocabulary in neither registry. Two faults are
11
+ * still thrown, both already registered and both commanded by another AD:
12
+ * `schema-version-mismatch` (AD-11) and, from `digestArtifact`,
13
+ * `non-canonicalizable-value` (AD-36).
14
+ *
15
+ * Each member must be a whole artifact already parsed against one published
16
+ * schema, the precondition every `core/` function has. A member narrowed to
17
+ * the three lineage fields digests differently and breaks its own chain.
18
+ */
19
+ import { digestArtifact } from '../canonical/digest.js';
20
+ import { RuntimeFault } from '../schemas/faults.js';
21
+ import { freezeArtifact } from './freeze.js';
22
+ export const LINEAGE_DEFECT_CODES = [
23
+ 'lineage-root-invalid',
24
+ 'lineage-duplicate-artifact',
25
+ 'lineage-no-root',
26
+ 'lineage-multiple-roots',
27
+ 'lineage-parent-absent',
28
+ 'lineage-revision-not-successor',
29
+ 'lineage-revision-conflict',
30
+ 'lineage-length-inconsistent',
31
+ 'lineage-remediation-cap-exceeded',
32
+ ];
33
+ /**
34
+ * Which of AD-12's three booleans each code clears. A `Record` over the code
35
+ * union, so a tenth code is a compile error here before it can reach a report
36
+ * with no boolean to affect. The cap maps to null; AD-12 records it in
37
+ * `Remediation.cap`, beside `lineageChain`.
38
+ */
39
+ export const CHECK_PROJECTION = {
40
+ 'lineage-root-invalid': 'noGap',
41
+ 'lineage-duplicate-artifact': 'noRepeatedDigest',
42
+ 'lineage-no-root': 'noGap',
43
+ 'lineage-multiple-roots': 'noGap',
44
+ 'lineage-parent-absent': 'noGap',
45
+ 'lineage-revision-not-successor': 'noGap',
46
+ 'lineage-revision-conflict': 'noGap',
47
+ 'lineage-length-inconsistent': 'lengthConsistent',
48
+ 'lineage-remediation-cap-exceeded': null,
49
+ };
50
+ const codeRank = (code) => LINEAGE_DEFECT_CODES.indexOf(code);
51
+ function groupBy(items, key) {
52
+ const groups = new Map();
53
+ for (const item of items) {
54
+ const bucket = groups.get(key(item));
55
+ if (bucket === undefined)
56
+ groups.set(key(item), [item]);
57
+ else
58
+ bucket.push(item);
59
+ }
60
+ return groups;
61
+ }
62
+ function readMembers(artifacts, options) {
63
+ return artifacts.map((artifact) => {
64
+ if (artifact.schemaVersion !== options.acceptedSchemaVersion) {
65
+ throw new RuntimeFault('schema-version-mismatch', options.artifactPath, `chain member carries "schemaVersion" ${artifact.schemaVersion} where the reader accepts ${options.acceptedSchemaVersion} (AD-11)`);
66
+ }
67
+ return {
68
+ digest: digestArtifact(artifact, options.artifactPath),
69
+ parentDigest: artifact.parentDigest,
70
+ revisionCount: artifact.revisionCount,
71
+ };
72
+ });
73
+ }
74
+ function assertCount(value, label, path) {
75
+ if (Number.isSafeInteger(value) && value >= 0)
76
+ return;
77
+ throw new TypeError(`validateLineageChain() received a "${label}" that is not a non-negative safe integer: ${path}`);
78
+ }
79
+ /**
80
+ * Validates one presented chain of revisions of a single artifact type
81
+ * (AD-12, AD-29). Every defect is collected, so a chain with three problems
82
+ * reports three findings.
83
+ */
84
+ export function validateLineageChain(artifacts, options) {
85
+ const { artifactPath } = options;
86
+ assertCount(options.declaredRevisionCount, 'declaredRevisionCount', artifactPath);
87
+ if (options.remediationCap !== null) {
88
+ assertCount(options.remediationCap, 'remediationCap', artifactPath);
89
+ }
90
+ const members = readMembers(artifacts, options);
91
+ const at = (digest) => `${artifactPath}[digest=${digest}]`;
92
+ const findings = [];
93
+ const add = (code, path, detail) => {
94
+ findings.push({ code, artifactPath: path, detail });
95
+ };
96
+ // The biconditional `lineage.ts` states and the constraint ledger records
97
+ // as not expressible in the published schema.
98
+ for (const member of members) {
99
+ const isRoot = member.revisionCount === 0;
100
+ if (isRoot === (member.parentDigest === null))
101
+ continue;
102
+ add('lineage-root-invalid', at(member.digest), isRoot
103
+ ? '"revisionCount" is 0 with a non-null "parentDigest" (AD-29)'
104
+ : `"revisionCount" is ${member.revisionCount} with a null "parentDigest" (AD-29)`);
105
+ }
106
+ for (const [digest, group] of groupBy(members, (member) => member.digest)) {
107
+ if (group.length < 2)
108
+ continue;
109
+ add('lineage-duplicate-artifact', at(digest), `${group.length} members share this digest, so one artifact is presented more than once (AD-12)`);
110
+ }
111
+ const roots = members.filter((member) => member.revisionCount === 0);
112
+ if (roots.length === 0) {
113
+ add('lineage-no-root', artifactPath, `no member carries "revisionCount" 0, so the chain of ${members.length} has no origin (AD-29)`);
114
+ }
115
+ else if (roots.length > 1) {
116
+ add('lineage-multiple-roots', artifactPath, `${roots.length} members carry "revisionCount" 0, so the presented set is more than one lineage (AD-29)`);
117
+ }
118
+ const byDigest = new Map(members.map((member) => [member.digest, member]));
119
+ for (const member of members) {
120
+ if (member.parentDigest === null)
121
+ continue;
122
+ const parent = byDigest.get(member.parentDigest);
123
+ if (parent === undefined) {
124
+ add('lineage-parent-absent', at(member.digest), `names parent "${member.parentDigest}", which no presented member digests to (AD-12)`);
125
+ continue;
126
+ }
127
+ if (member.revisionCount === parent.revisionCount + 1)
128
+ continue;
129
+ add('lineage-revision-not-successor', at(member.digest), `carries "revisionCount" ${member.revisionCount} where its parent carries ${parent.revisionCount} (AD-29)`);
130
+ }
131
+ // AD-29's conflict is same parent, same count, differing content. A group of
132
+ // byte-identical members is reported as a duplicate above.
133
+ const revisions = members.filter((member) => member.parentDigest !== null);
134
+ for (const [, group] of groupBy(revisions, (member) => `${member.parentDigest} ${member.revisionCount}`)) {
135
+ const distinct = [...new Set(group.map((member) => member.digest))].sort();
136
+ if (distinct.length < 2)
137
+ continue;
138
+ const first = group[0];
139
+ if (first === undefined)
140
+ continue;
141
+ add('lineage-revision-conflict', `${artifactPath}[parent=${first.parentDigest},revision=${first.revisionCount}]`, `${distinct.length} differing members claim this parent and revision: ${distinct.join(', ')} (AD-29)`);
142
+ }
143
+ if (members.length !== options.declaredRevisionCount + 1) {
144
+ add('lineage-length-inconsistent', artifactPath, `presents ${members.length} members against a declared revision count of ${options.declaredRevisionCount} (AD-12)`);
145
+ }
146
+ if (options.remediationCap !== null &&
147
+ options.declaredRevisionCount > options.remediationCap) {
148
+ add('lineage-remediation-cap-exceeded', artifactPath, `declares ${options.declaredRevisionCount} revisions against a caller-attested cap of ${options.remediationCap} (AD-12)`);
149
+ }
150
+ // Addresses are content-derived, so this order holds for any permutation of
151
+ // the input. Two byte-identical members produce two findings at one address
152
+ // with identical details, so an equal key is a genuine tie.
153
+ findings.sort((left, right) => {
154
+ const byCode = codeRank(left.code) - codeRank(right.code);
155
+ if (byCode !== 0)
156
+ return byCode;
157
+ if (left.artifactPath === right.artifactPath)
158
+ return 0;
159
+ return left.artifactPath < right.artifactPath ? -1 : 1;
160
+ });
161
+ const checks = {
162
+ lengthConsistent: true,
163
+ noRepeatedDigest: true,
164
+ noGap: true,
165
+ };
166
+ for (const finding of findings) {
167
+ const bucket = CHECK_PROJECTION[finding.code];
168
+ if (bucket !== null)
169
+ checks[bucket] = false;
170
+ }
171
+ return freezeArtifact({
172
+ artifactPath,
173
+ findings,
174
+ checks,
175
+ passed: findings.length === 0,
176
+ });
177
+ }
178
+ /**
179
+ * Mints the next revision of `parent`: a new artifact carrying the parent's
180
+ * digest and a revision count one greater (AD-29). The clone is load-bearing.
181
+ * `{ ...body }` shares its subtrees with the caller and `freezeArtifact`
182
+ * freezes in place, so without it this call would freeze the caller's `body`.
183
+ */
184
+ export function reviseArtifact(parent, body, artifactPath) {
185
+ if ((parent.revisionCount === 0) !== (parent.parentDigest === null)) {
186
+ throw new TypeError(`reviseArtifact() received a parent whose parentDigest and revisionCount disagree: ${artifactPath}`);
187
+ }
188
+ const next = parent.revisionCount + 1;
189
+ if (!Number.isSafeInteger(parent.revisionCount) || parent.revisionCount < 0) {
190
+ throw new TypeError(`reviseArtifact() received a parent whose revisionCount is not a non-negative safe integer: ${artifactPath}`);
191
+ }
192
+ if (!Number.isSafeInteger(next)) {
193
+ throw new TypeError(`reviseArtifact() would mint a revisionCount outside the safe integer range: ${artifactPath}`);
194
+ }
195
+ if (body.schemaVersion !== parent.schemaVersion) {
196
+ throw new TypeError(`reviseArtifact() received a body at schemaVersion ${body.schemaVersion} for a parent at ${parent.schemaVersion}: ${artifactPath}`);
197
+ }
198
+ // The type forbids these keys and a cast defeats the type, so the guard is
199
+ // what stops a stale count winning or losing on spread order.
200
+ for (const field of ['parentDigest', 'revisionCount']) {
201
+ if (!Object.hasOwn(body, field))
202
+ continue;
203
+ throw new TypeError(`reviseArtifact() received a body carrying "${field}", which the producing stage owns: ${artifactPath}`);
204
+ }
205
+ return freezeArtifact(structuredClone({
206
+ ...body,
207
+ parentDigest: digestArtifact(parent, artifactPath),
208
+ revisionCount: next,
209
+ }));
210
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * AD-29's "created once and never edited in place", made mechanical. A stage
3
+ * freezes the artifact it owns; `application/` freezes what crosses the
4
+ * package boundary.
5
+ */
6
+ /**
7
+ * Deep-freezes a parsed artifact in place and returns it. Call it on a value
8
+ * you own: every wired site hands it a Zod clone or a fresh literal. An
9
+ * already-frozen node stops the walk, which is what terminates a cycle and
10
+ * what leaves a hand-frozen subtree's descendants alone.
11
+ */
12
+ export declare function freezeArtifact<T>(value: T): T;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * AD-29's "created once and never edited in place", made mechanical. A stage
3
+ * freezes the artifact it owns; `application/` freezes what crosses the
4
+ * package boundary.
5
+ */
6
+ /**
7
+ * Arrays and plain objects, every node an AD-27 JSON artifact has. Freezing a
8
+ * `Date`, `Map`, or `Set` protects nothing they hold, and `Object.freeze` on a
9
+ * non-empty typed array throws.
10
+ */
11
+ function isJsonContainer(value) {
12
+ if (value === null || typeof value !== 'object')
13
+ return false;
14
+ if (Array.isArray(value))
15
+ return true;
16
+ const prototype = Object.getPrototypeOf(value);
17
+ return prototype === Object.prototype || prototype === null;
18
+ }
19
+ /**
20
+ * Deep-freezes a parsed artifact in place and returns it. Call it on a value
21
+ * you own: every wired site hands it a Zod clone or a fresh literal. An
22
+ * already-frozen node stops the walk, which is what terminates a cycle and
23
+ * what leaves a hand-frozen subtree's descendants alone.
24
+ */
25
+ export function freezeArtifact(value) {
26
+ if (!isJsonContainer(value))
27
+ return value;
28
+ if (Object.isFrozen(value))
29
+ return value;
30
+ Object.freeze(value);
31
+ // An array's indices are its enumerable own keys, so this covers both.
32
+ for (const member of Object.values(value))
33
+ freezeArtifact(member);
34
+ return value;
35
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * AD-24's stage-signature table and AD-29's producer map, as data: six stages
3
+ * with their inputs, their one owned output, and the lineage edge each writes;
4
+ * twelve interchange artifacts with one producer apiece. The `check:lineage`
5
+ * scanner derives its allowlist from `module`, so the registry import stays
6
+ * type-only to keep zod off that gate's load path.
7
+ */
8
+ import type { InterchangeArtifactKey } from '../schemas/artifact.ts';
9
+ /** AD-24's six stages, in the order the Consistency Conventions list them. */
10
+ export declare const PIPELINE_STAGES: readonly ['compile', 'seal', 'ingest', 'preflight', 'score', 'emit'];
11
+ export type PipelineStage = (typeof PIPELINE_STAGES)[number];
12
+ /**
13
+ * Typed stage products no schema publishes (AD-24). `probe-plan` comes from
14
+ * pre-flight's plan half and `probe-observations` from the probe port, so
15
+ * neither is a stage's owned output.
16
+ */
17
+ export declare const INTERNAL_PRODUCTS: readonly ['probe-plan', 'probe-observations', 'validated-observations', 'scored-outcomes-and-verdict'];
18
+ export type InternalProduct = (typeof INTERNAL_PRODUCTS)[number];
19
+ /**
20
+ * What a stage does to the two AD-29 fields on the artifact it owns. `mints`
21
+ * covers a root and a revision alike, since both write the fields.
22
+ */
23
+ export type LineageEdge = 'mints' | 'carries-through' | 'none';
24
+ /** Who produces an interchange artifact. */
25
+ export type ArtifactProducer = PipelineStage | 'caller' | 'embedded';
26
+ export type StageSignature = {
27
+ readonly inputs: readonly (InterchangeArtifactKey | InternalProduct)[];
28
+ readonly owns: InterchangeArtifactKey | InternalProduct;
29
+ /** the owned output's registry key; null when the output is internal. */
30
+ readonly ownsInterchange: InterchangeArtifactKey | null;
31
+ readonly lineage: LineageEdge;
32
+ /** the module that writes the artifact; null while the stage is unbuilt. */
33
+ readonly module: string | null;
34
+ };
35
+ export declare const STAGE_SIGNATURES: Record<PipelineStage, StageSignature>;
36
+ export declare const ARTIFACT_PRODUCERS: Record<InterchangeArtifactKey, ArtifactProducer>;
37
+ /**
38
+ * The modules permitted to write `parentDigest` or `revisionCount`, derived
39
+ * from the table it is handed. A function, because a module-level constant is
40
+ * evaluated once at import and a test mutating the table would see nothing.
41
+ */
42
+ export declare function deriveLineageWriterModules(signatures: Record<PipelineStage, StageSignature>): readonly string[];
43
+ export declare const LINEAGE_WRITER_MODULES: readonly string[];
@@ -0,0 +1,106 @@
1
+ /** AD-24's six stages, in the order the Consistency Conventions list them. */
2
+ export const PIPELINE_STAGES = [
3
+ 'compile',
4
+ 'seal',
5
+ 'ingest',
6
+ 'preflight',
7
+ 'score',
8
+ 'emit',
9
+ ];
10
+ /**
11
+ * Typed stage products no schema publishes (AD-24). `probe-plan` comes from
12
+ * pre-flight's plan half and `probe-observations` from the probe port, so
13
+ * neither is a stage's owned output.
14
+ */
15
+ export const INTERNAL_PRODUCTS = [
16
+ 'probe-plan',
17
+ 'probe-observations',
18
+ 'validated-observations',
19
+ 'scored-outcomes-and-verdict',
20
+ ];
21
+ export const STAGE_SIGNATURES = {
22
+ compile: {
23
+ inputs: ['eval-contract'],
24
+ owns: 'eval-contract',
25
+ ownsInterchange: 'eval-contract',
26
+ // The caller authors the contract and its lineage; `compile` validates
27
+ // and returns it. The Seed's behaviour-input transformation is unbuilt,
28
+ // so the row is an identity.
29
+ lineage: 'carries-through',
30
+ module: 'src/core/compile/compile.ts',
31
+ },
32
+ seal: {
33
+ inputs: ['eval-contract'],
34
+ owns: 'sealed-evaluator-brief',
35
+ ownsInterchange: 'sealed-evaluator-brief',
36
+ lineage: 'mints',
37
+ module: 'src/core/seal/seal.ts',
38
+ },
39
+ ingest: {
40
+ inputs: [
41
+ 'sealed-run-record',
42
+ 'isolation-manifest',
43
+ 'evaluator-configuration',
44
+ ],
45
+ owns: 'validated-observations',
46
+ ownsInterchange: null,
47
+ lineage: 'none',
48
+ module: null,
49
+ },
50
+ preflight: {
51
+ // AD-34 splits the stage into `plan` and `reduce`; both halves' inputs
52
+ // are the stage's inputs, and only `reduce` returns an artifact.
53
+ inputs: ['eval-contract', 'probe', 'probe-plan', 'probe-observations'],
54
+ owns: 'preflight-verdict',
55
+ ownsInterchange: 'preflight-verdict',
56
+ lineage: 'mints',
57
+ module: 'src/core/preflight/reduce.ts',
58
+ },
59
+ score: {
60
+ inputs: [
61
+ 'eval-contract',
62
+ 'validated-observations',
63
+ 'probe',
64
+ 'preflight-verdict',
65
+ 'scoring-policy',
66
+ ],
67
+ // AD-24: "score produces the outcome and verdict values emit
68
+ // serializes". Owed item 6 records that type as unnamed; this names it.
69
+ owns: 'scored-outcomes-and-verdict',
70
+ ownsInterchange: null,
71
+ lineage: 'none',
72
+ module: null,
73
+ },
74
+ emit: {
75
+ inputs: ['scored-outcomes-and-verdict'],
76
+ owns: 'evidence-artifact',
77
+ ownsInterchange: 'evidence-artifact',
78
+ lineage: 'mints',
79
+ module: null,
80
+ },
81
+ };
82
+ export const ARTIFACT_PRODUCERS = {
83
+ 'eval-contract': 'compile',
84
+ rubric: 'caller',
85
+ 'sealed-evaluator-brief': 'seal',
86
+ 'sealed-run-record': 'caller',
87
+ 'isolation-manifest': 'caller',
88
+ 'evaluator-configuration': 'caller',
89
+ probe: 'caller',
90
+ 'artifact-reference': 'embedded',
91
+ 'private-artifact-manifest': 'caller',
92
+ 'preflight-verdict': 'preflight',
93
+ 'scoring-policy': 'caller',
94
+ 'evidence-artifact': 'emit',
95
+ };
96
+ /**
97
+ * The modules permitted to write `parentDigest` or `revisionCount`, derived
98
+ * from the table it is handed. A function, because a module-level constant is
99
+ * evaluated once at import and a test mutating the table would see nothing.
100
+ */
101
+ export function deriveLineageWriterModules(signatures) {
102
+ return PIPELINE_STAGES.map((stage) => signatures[stage]).flatMap((signature) => signature.lineage === 'mints' && signature.module !== null
103
+ ? [signature.module]
104
+ : []);
105
+ }
106
+ export const LINEAGE_WRITER_MODULES = deriveLineageWriterModules(STAGE_SIGNATURES);
@@ -0,0 +1,67 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ import type { Operation } from '../schemas/interface.ts';
3
+ import type { ProbeRequest } from '../schemas/port-messages.ts';
4
+ import type { JsonValue } from '../schemas/primitives.ts';
5
+ import type { Probe } from '../schemas/probe.ts';
6
+ import type { ManifestationWitness, SensitivityWitness, WitnessInputs } from '../schemas/sensitivity-witness.ts';
7
+ import type { PlanStage } from '../stage-contracts.ts';
8
+ export type PreflightPlanInput = {
9
+ readonly contract: EvalContract;
10
+ readonly probes: readonly Probe[];
11
+ readonly runId: string;
12
+ };
13
+ export type PlannedLegPurpose = 'sensitivity' | 'control-observe' | 'control-mutate' | 'control-reset' | 'seeded-fault';
14
+ export type PlannedLeg = {
15
+ readonly legId: string;
16
+ readonly purpose: PlannedLegPurpose;
17
+ readonly request: ProbeRequest;
18
+ readonly operation: Operation;
19
+ readonly inputs: WitnessInputs;
20
+ };
21
+ export type PlannedCheck = {
22
+ readonly kind: 'interface-present';
23
+ readonly interfaceId: string;
24
+ readonly operationId: string;
25
+ readonly legIds: readonly string[];
26
+ } | {
27
+ readonly kind: 'input-sensitivity';
28
+ readonly interfaceId: string;
29
+ readonly operationId: string;
30
+ readonly witness: SensitivityWitness | null;
31
+ readonly operation: Operation;
32
+ } | {
33
+ readonly kind: 'state-reset';
34
+ readonly legIds: readonly [string, string];
35
+ } | {
36
+ readonly kind: 'clean-control';
37
+ readonly legIds: readonly string[];
38
+ } | {
39
+ readonly kind: 'seeded-faults-scoped';
40
+ readonly defectId: string;
41
+ readonly witness: ManifestationWitness;
42
+ readonly operation: Operation;
43
+ readonly cleanLegIds: readonly string[];
44
+ } | {
45
+ readonly kind: 'seeded-fault-fired';
46
+ readonly defectId: string;
47
+ readonly witness: ManifestationWitness | null;
48
+ readonly operation: Operation | null;
49
+ };
50
+ export type PreflightPlan = {
51
+ readonly runId: string;
52
+ readonly legs: readonly PlannedLeg[];
53
+ readonly checks: readonly PlannedCheck[];
54
+ readonly referenceSets: Readonly<Record<string, JsonValue[]>>;
55
+ };
56
+ /**
57
+ * AD-10's plan, derived from the interfaces the contract's probes exercise.
58
+ *
59
+ * Three constructions worth knowing. An `interface-present` check is emitted
60
+ * only for an operation that has at least one leg, because a check over no legs
61
+ * is satisfied vacuously and asserts nothing. The `state-reset` and
62
+ * `clean-control` checks are emitted only when control legs could be planned at
63
+ * all, for the same reason. And the fixture reset's own leg is one more leg
64
+ * through the same port, so its identifier shares the namespace every other leg
65
+ * identifier sits in.
66
+ */
67
+ export declare const planPreflight: PlanStage<PreflightPlanInput, PreflightPlan>;