eval-quality 0.1.0 → 0.3.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 (179) hide show
  1. package/README.md +142 -67
  2. package/corpus/dev/README.md +20 -13
  3. package/corpus/dev/compile-seal-example/brief.json +1 -1
  4. package/corpus/dev/compile-seal-example/contract.json +1 -1
  5. package/corpus/dev/contracts/absent-collection-locations.json +1 -1
  6. package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
  7. package/corpus/dev/contracts/absent-success-indicator.json +1 -1
  8. package/corpus/dev/contracts/empty-channel-roles.json +1 -1
  9. package/corpus/dev/contracts/empty-collection-locations.json +1 -1
  10. package/corpus/dev/contracts/empty-request-shapes.json +1 -1
  11. package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
  12. package/corpus/dev/contracts/fragment-selection.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  19. package/corpus/dev/contracts/review-corpus.json +1 -0
  20. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  21. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  22. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  23. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  24. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  25. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  26. package/corpus/dev/index.json +1 -1
  27. package/dist/application/index.d.ts +2 -0
  28. package/dist/application/index.js +1 -0
  29. package/dist/application/score.d.ts +48 -0
  30. package/dist/application/score.js +186 -0
  31. package/dist/cli/arguments.d.ts +7 -3
  32. package/dist/cli/arguments.js +73 -5
  33. package/dist/cli/exit-codes.d.ts +6 -2
  34. package/dist/cli/exit-codes.js +8 -9
  35. package/dist/cli/main.js +9 -0
  36. package/dist/cli/render.d.ts +3 -6
  37. package/dist/cli/render.js +57 -5
  38. package/dist/cli/run.d.ts +13 -3
  39. package/dist/cli/run.js +136 -16
  40. package/dist/core/canonical/canonicalize.d.ts +12 -0
  41. package/dist/core/canonical/canonicalize.js +18 -4
  42. package/dist/core/compile/bindings.d.ts +89 -0
  43. package/dist/core/compile/bindings.js +334 -0
  44. package/dist/core/compile/compile.d.ts +17 -0
  45. package/dist/core/compile/compile.js +37 -1
  46. package/dist/core/compile/excluded-content.d.ts +11 -0
  47. package/dist/core/compile/excluded-content.js +42 -0
  48. package/dist/core/compile/expression-legality.d.ts +49 -0
  49. package/dist/core/compile/expression-legality.js +158 -38
  50. package/dist/core/compile/forbidden-inputs.d.ts +8 -1
  51. package/dist/core/compile/forbidden-inputs.js +16 -3
  52. package/dist/core/compile/interface-inventory.d.ts +57 -2
  53. package/dist/core/compile/interface-inventory.js +119 -13
  54. package/dist/core/compile/reachability.d.ts +28 -1
  55. package/dist/core/compile/reachability.js +167 -35
  56. package/dist/core/compile/schema-version.d.ts +2 -0
  57. package/dist/core/compile/schema-version.js +25 -0
  58. package/dist/core/compile/sensitivity-witness.d.ts +31 -12
  59. package/dist/core/compile/sensitivity-witness.js +110 -23
  60. package/dist/core/compile/step-reference.d.ts +2 -0
  61. package/dist/core/compile/step-reference.js +49 -0
  62. package/dist/core/coverage/operations.d.ts +62 -0
  63. package/dist/core/coverage/operations.js +57 -0
  64. package/dist/core/coverage/relevance.d.ts +4 -2
  65. package/dist/core/coverage/relevance.js +22 -23
  66. package/dist/core/coverage/satisfaction.d.ts +2 -2
  67. package/dist/core/coverage/satisfaction.js +73 -48
  68. package/dist/core/declared-inputs.d.ts +83 -4
  69. package/dist/core/declared-inputs.js +105 -8
  70. package/dist/core/emit/emit.d.ts +9 -0
  71. package/dist/core/emit/emit.js +214 -0
  72. package/dist/core/emit/private-artifact-digest.d.ts +11 -0
  73. package/dist/core/emit/private-artifact-digest.js +31 -0
  74. package/dist/core/evaluate/evidence-resolution.d.ts +10 -3
  75. package/dist/core/evaluate/evidence-resolution.js +74 -11
  76. package/dist/core/evaluate/operators.d.ts +22 -0
  77. package/dist/core/evaluate/operators.js +17 -1
  78. package/dist/core/evaluate/resolution.d.ts +14 -3
  79. package/dist/core/evaluate/resolution.js +63 -3
  80. package/dist/core/excluded-content.d.ts +65 -0
  81. package/dist/core/excluded-content.js +113 -0
  82. package/dist/core/failure-codes.d.ts +2 -2
  83. package/dist/core/failure-codes.js +7 -2
  84. package/dist/core/ingest/conditions.d.ts +201 -0
  85. package/dist/core/ingest/conditions.js +48 -0
  86. package/dist/core/ingest/index.d.ts +13 -0
  87. package/dist/core/ingest/index.js +11 -0
  88. package/dist/core/ingest/ingest.d.ts +68 -0
  89. package/dist/core/ingest/ingest.js +318 -0
  90. package/dist/core/lineage/stage-table.d.ts +21 -4
  91. package/dist/core/lineage/stage-table.js +35 -3
  92. package/dist/core/preflight/plan.d.ts +7 -5
  93. package/dist/core/preflight/plan.js +75 -32
  94. package/dist/core/preflight/projection.d.ts +6 -3
  95. package/dist/core/preflight/projection.js +22 -2
  96. package/dist/core/preflight/reduce.js +47 -7
  97. package/dist/core/preflight/witness-evidence.d.ts +5 -5
  98. package/dist/core/preflight/witness-evidence.js +73 -20
  99. package/dist/core/schemas/artifact.d.ts +1049 -30
  100. package/dist/core/schemas/constraint-ledger.js +51 -0
  101. package/dist/core/schemas/defect-signature.d.ts +695 -0
  102. package/dist/core/schemas/defect-signature.js +175 -0
  103. package/dist/core/schemas/eval-contract.d.ts +550 -12
  104. package/dist/core/schemas/eval-contract.js +32 -2
  105. package/dist/core/schemas/evidence-artifact.d.ts +109 -19
  106. package/dist/core/schemas/evidence-artifact.js +64 -8
  107. package/dist/core/schemas/interface.d.ts +741 -13
  108. package/dist/core/schemas/interface.js +120 -7
  109. package/dist/core/schemas/isolation-manifest.js +16 -9
  110. package/dist/core/schemas/plan.d.ts +329 -3
  111. package/dist/core/schemas/plan.js +89 -3
  112. package/dist/core/schemas/pointer.d.ts +47 -1
  113. package/dist/core/schemas/pointer.js +89 -8
  114. package/dist/core/schemas/port-messages.d.ts +194 -10
  115. package/dist/core/schemas/port-messages.js +73 -6
  116. package/dist/core/schemas/probe-body.d.ts +18 -0
  117. package/dist/core/schemas/probe-body.js +13 -0
  118. package/dist/core/schemas/probe-qualification.d.ts +168 -0
  119. package/dist/core/schemas/probe-qualification.js +106 -0
  120. package/dist/core/schemas/probe.d.ts +431 -0
  121. package/dist/core/schemas/probe.js +6 -2
  122. package/dist/core/schemas/scoring-policy.d.ts +1 -0
  123. package/dist/core/schemas/scoring-policy.js +6 -1
  124. package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
  125. package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
  126. package/dist/core/schemas/sealed-run-record.d.ts +161 -12
  127. package/dist/core/schemas/sealed-run-record.js +92 -15
  128. package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
  129. package/dist/core/schemas/sensitivity-witness.js +61 -5
  130. package/dist/core/score/binding-order.d.ts +31 -0
  131. package/dist/core/score/binding-order.js +88 -0
  132. package/dist/core/score/bindings.d.ts +93 -0
  133. package/dist/core/score/bindings.js +309 -0
  134. package/dist/core/score/ladder-table.d.ts +23 -0
  135. package/dist/core/score/ladder-table.js +109 -0
  136. package/dist/core/score/ladder.d.ts +229 -0
  137. package/dist/core/score/ladder.js +464 -0
  138. package/dist/core/score/mode-agreement.d.ts +30 -0
  139. package/dist/core/score/mode-agreement.js +16 -0
  140. package/dist/core/score/outcome-table.d.ts +17 -0
  141. package/dist/core/score/outcome-table.js +172 -0
  142. package/dist/core/score/outcome.d.ts +466 -0
  143. package/dist/core/score/outcome.js +562 -0
  144. package/dist/core/score/qualification.d.ts +83 -0
  145. package/dist/core/score/qualification.js +645 -0
  146. package/dist/core/score/quotation.d.ts +56 -0
  147. package/dist/core/score/quotation.js +226 -0
  148. package/dist/core/score/reduce-trials.d.ts +80 -0
  149. package/dist/core/score/reduce-trials.js +90 -0
  150. package/dist/core/score/score.d.ts +72 -0
  151. package/dist/core/score/score.js +607 -0
  152. package/dist/core/score/selection.d.ts +80 -0
  153. package/dist/core/score/selection.js +98 -0
  154. package/dist/core/score/strength.d.ts +58 -0
  155. package/dist/core/score/strength.js +227 -0
  156. package/dist/core/score/witness.d.ts +138 -0
  157. package/dist/core/score/witness.js +320 -0
  158. package/dist/core/seal/derived-reference.d.ts +2 -9
  159. package/dist/core/seal/derived-reference.js +293 -46
  160. package/dist/core/seal/plan-index.d.ts +18 -3
  161. package/dist/core/seal/plan-index.js +44 -8
  162. package/dist/core/seal/seal.js +8 -3
  163. package/dist/core/stage-contracts.d.ts +62 -2
  164. package/dist/index.d.ts +1 -1
  165. package/dist/index.js +1 -1
  166. package/dist/ports/environment-probe-port.d.ts +60 -4
  167. package/dist/testing/index.d.ts +2 -0
  168. package/dist/testing/index.js +1 -0
  169. package/dist/testing/probe-conformance.d.ts +29 -0
  170. package/dist/testing/probe-conformance.js +34 -5
  171. package/package.json +12 -3
  172. package/schemas/eval-contract.schema.json +1787 -797
  173. package/schemas/evidence-artifact.schema.json +126 -9
  174. package/schemas/isolation-manifest.schema.json +17 -10
  175. package/schemas/probe.schema.json +636 -5
  176. package/schemas/rubric.schema.json +1 -1
  177. package/schemas/scoring-policy.schema.json +8 -1
  178. package/schemas/sealed-evaluator-brief.schema.json +11 -1
  179. package/schemas/sealed-run-record.schema.json +314 -40
@@ -0,0 +1,68 @@
1
+ import type { Observation, OracleDisposition, RunModeValue, SealedRunRecord } from '../schemas/sealed-run-record.ts';
2
+ import type { EvaluatorRecommendation } from '../schemas/verdict.ts';
3
+ import { type UnwitnessedQuotation } from '../score/quotation.ts';
4
+ import type { IngestStage } from '../stage-contracts.ts';
5
+ import { type IngestCondition } from './conditions.ts';
6
+ /**
7
+ * `validated-observations`, the internal product AD-24 exempts from
8
+ * publication: a TypeScript type with no Zod schema, no registry entry, and no
9
+ * `schemaVersion`.
10
+ *
11
+ * `unwitnessedQuotations` and `isolationViolation` are derived here rather than
12
+ * left for the next stage to rebuild out of condition entries, because both are
13
+ * shapes the ladder already declares.
14
+ *
15
+ * The three carried arrays are copies and their elements are not: an
16
+ * observation, a finding, or a disposition on this product is the same object
17
+ * the record holds. The stage is pure and mutates nothing, so a caller that also
18
+ * does not mutate the record it passed in sees a stable product; deep-copying
19
+ * every observation to defend against a caller mutating its own input is a cost
20
+ * no consumer in this package asks for. `isolationViolation` is a list where
21
+ * `EvidenceIntegrityInputs` still declares one nullable string: the ladder's own
22
+ * row type says a multi-entry `reasons` "names each affected oracle, gap, or
23
+ * finding separately, matching AD-21's 'the record carries every condition that
24
+ * fired'", so the single-valued field is the shape that is wrong. Collapsing to
25
+ * it here would mean inventing a separator and an ordering rule that the field's
26
+ * widening then deletes.
27
+ */
28
+ export type ValidatedObservations = {
29
+ /** `record.runId`, restated for the same reason `mode` is: `score.ts` reads it off the first trial to build `emit`'s own `runId` field, and no later stage may re-derive or default it. */
30
+ readonly runId: string;
31
+ readonly mode: RunModeValue;
32
+ /**
33
+ * `AssessmentCommon.evaluatorRecommendation` (`score/ladder.ts:107`) is
34
+ * required and two shared ladder rows read it directly; nothing before
35
+ * this field supplied it to anything score-shaped. Read off
36
+ * `record.evaluatorRecommendation` the same way `mode` already is above:
37
+ * restated, never derived, recomputed, or defaulted.
38
+ */
39
+ readonly evaluatorRecommendation: EvaluatorRecommendation;
40
+ /** ascending `sequence`, then `observationId`, so the record's array order is never what a consumer reads. */
41
+ readonly observations: readonly Observation[];
42
+ /** ascending `findingId`; entries sharing one keep their presented order, which is all the record supplies. */
43
+ readonly findings: readonly SealedRunRecord['findings'][number][];
44
+ /** ascending `oracleId`, on the same terms as `findings`. */
45
+ readonly dispositions: readonly OracleDisposition[];
46
+ readonly conditions: readonly IngestCondition[];
47
+ readonly unwitnessedQuotations: readonly UnwitnessedQuotation[];
48
+ /** empty when nothing fired; one entry per offending value, never a joined summary. */
49
+ readonly isolationViolation: readonly string[];
50
+ };
51
+ /**
52
+ * The stage. Reads nothing outside its three declared inputs: a rule needing
53
+ * the eval contract, the rubric, or bytes resolved through the corpus port is
54
+ * routed to whoever declares them rather than absorbed here on the strength of
55
+ * being nearby.
56
+ *
57
+ * Two rules read the evaluator configuration and they are separate. AD-32's
58
+ * agreement rule compares the digest the record declares against the one the
59
+ * manifest declares, which is `cross-artifact-disagreement` and never reads the
60
+ * artifact. AD-24 and AD-11 require the digest to be recomputed from the
61
+ * artifact, which is the only one of the three that is not caller-attested and
62
+ * is why the artifact is a declared input at all.
63
+ *
64
+ * AD-24's "absent or incomplete" splits the same way the manifest's does:
65
+ * incomplete is a schema rejection the application boundary already raises, and
66
+ * absent is a shape no schema can describe, so the parameter admits `null`.
67
+ */
68
+ export declare const ingest: IngestStage<ValidatedObservations>;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * AD-24's ingest stage: three already-parsed artifacts in, the validated
3
+ * observations plus every condition detected out.
4
+ *
5
+ * The organizing rule is the isolation manifest's own `.meta` sentence, "a
6
+ * schema rejection is the correct expression of unparseable and incomplete, so
7
+ * nothing is admitted for their sake, and the violating case is
8
+ * `core/ingest`'s". Bytes that will not parse are the application layer's to
9
+ * reject; bytes that parse and then contradict each other are a finding about
10
+ * the run. So a detected problem is returned as data and nothing here throws.
11
+ *
12
+ * One fault does propagate and is named, from two call sites into the same
13
+ * module. `auditQuotation` canonicalizes the three structured evidence
14
+ * channels, and the evaluator configuration's digest is recomputed by
15
+ * canonicalizing the whole artifact, so an unsafe integer or a lone surrogate in
16
+ * either raises AD-28's `non-canonicalizable-value` out of `core/canonical`. It
17
+ * is neither caught nor re-raised here, which is what lets a caller tell an
18
+ * invalid run from a crashed one.
19
+ *
20
+ * Nothing here re-derives what `resolveOutcome` already decides. Its ten
21
+ * invalidating conditions are per oracle and reach the ladder through
22
+ * `outcome.resolution.invalidatingConditions`; these eleven are per record and
23
+ * reach it through `EvidenceIntegrityInputs` and `OutcomeStateInputs`. Two
24
+ * pairs are related rather than identical: `judge-result-unscored` is the
25
+ * derivation `OutcomeInputs.judgeConduct` has never had, and a
26
+ * `dangling-citation` and an `unwitnessed-detection-claim` can both fire on one
27
+ * finding, in which case the basis names both and the rung is the same either
28
+ * way.
29
+ */
30
+ import { digestArtifact } from '../canonical/digest.js';
31
+ import { FORBIDDEN_INPUT_FLOOR } from '../schemas/eval-contract.js';
32
+ import { auditQuotation, } from '../score/quotation.js';
33
+ import { AGREEMENT_FIELDS } from './conditions.js';
34
+ /**
35
+ * Observed values with no entry in their allowlist, in the manifest's own array
36
+ * order and each named once. A value observed twice outside its allowlist is one
37
+ * value outside its allowlist; the payload is what a consumer renders, and
38
+ * `isolationViolation` is specified as one entry per offending value.
39
+ */
40
+ const outsideAllowlist = (observed, allowed) => {
41
+ const permitted = new Set(allowed);
42
+ return [...new Set(observed)].filter((value) => !permitted.has(value));
43
+ };
44
+ /** The path AD-27 digests the evaluator configuration under, matching `seal.ts`'s treatment of the contract. */
45
+ const EVALUATOR_CONFIGURATION_ARTIFACT_PATH = 'EvaluatorConfiguration';
46
+ /**
47
+ * A total order over the two condition families whose source array declares no
48
+ * order of its own. A finding carries no sequence field and a judge result is
49
+ * addressed by its `(rubricId, criterionId)` pair, so reading either in array
50
+ * position would make the recorded conditions depend on a position NFR9 forbids
51
+ * reading; `auditQuotation` sorts its own result for the same reason.
52
+ *
53
+ * Keyed on the whole payload rather than on the identifier alone. `observations`
54
+ * is the only one of the three arrays the record schema refines for uniqueness,
55
+ * so a repeated `findingId` and a repeated `(rubricId, criterionId)` are both
56
+ * representable, and a comparator keyed on the identifier would leave the tied
57
+ * pair ordered by the array position the sort exists to stop reading. Entries
58
+ * that still tie here are equal values, so the order between them cannot be
59
+ * observed.
60
+ *
61
+ * Joined on U+0000 rather than compared position by position. Every identifier
62
+ * in a key is drawn from `IDENTIFIER_SOURCE` or a `X-digits` prefix form, whose
63
+ * lowest code point is U+002D, so the separator sorts below every character a
64
+ * segment can carry and the joined comparison is the element-wise one.
65
+ */
66
+ const compareKeys = (left, right) => {
67
+ const a = left.join('\u0000');
68
+ const b = right.join('\u0000');
69
+ return a < b ? -1 : a > b ? 1 : 0;
70
+ };
71
+ /**
72
+ * The stage. Reads nothing outside its three declared inputs: a rule needing
73
+ * the eval contract, the rubric, or bytes resolved through the corpus port is
74
+ * routed to whoever declares them rather than absorbed here on the strength of
75
+ * being nearby.
76
+ *
77
+ * Two rules read the evaluator configuration and they are separate. AD-32's
78
+ * agreement rule compares the digest the record declares against the one the
79
+ * manifest declares, which is `cross-artifact-disagreement` and never reads the
80
+ * artifact. AD-24 and AD-11 require the digest to be recomputed from the
81
+ * artifact, which is the only one of the three that is not caller-attested and
82
+ * is why the artifact is a declared input at all.
83
+ *
84
+ * AD-24's "absent or incomplete" splits the same way the manifest's does:
85
+ * incomplete is a schema rejection the application boundary already raises, and
86
+ * absent is a shape no schema can describe, so the parameter admits `null`.
87
+ */
88
+ export const ingest = (record, manifest, configuration) => {
89
+ const conditions = [];
90
+ // Prior to every other check: a record that uses one identifier twice cannot
91
+ // address its own entries, and every consumer downstream addresses by them.
92
+ // Reported before the conditions that name one, so a reader meets the
93
+ // ambiguity before a condition that relies on it.
94
+ //
95
+ // Observations lead because a repeat there changes an answer rather than an
96
+ // order: `auditQuotation` indexes them into a `Map` keyed on `observationId`,
97
+ // so the last entry with a repeated identifier wins and decides which body a
98
+ // quotation is checked against.
99
+ for (const [subject, identifiers] of [
100
+ [
101
+ 'observation',
102
+ record.observations.map((observation) => observation.observationId),
103
+ ],
104
+ ['finding', record.findings.map((finding) => finding.findingId)],
105
+ [
106
+ 'oracle-disposition',
107
+ record.oracleDispositions.map((disposition) => disposition.oracleId),
108
+ ],
109
+ // AD-17's "one judge call scoring all named criteria", read as far as
110
+ // one record can be read: a criterion scored twice is not one call's
111
+ // product whatever the rest of the record says. Keyed by the pair,
112
+ // since two rubrics may each declare a criterion of the same name.
113
+ [
114
+ 'judge-result',
115
+ record.judgeResults.map((result) => `${result.rubricId}/${result.criterionId}`),
116
+ ],
117
+ ]) {
118
+ const counts = new Map();
119
+ for (const identifier of identifiers) {
120
+ counts.set(identifier, (counts.get(identifier) ?? 0) + 1);
121
+ }
122
+ const repeated = [...counts.entries()]
123
+ .filter(([, occurrences]) => occurrences > 1)
124
+ .sort((a, b) => compareKeys([a[0]], [b[0]]));
125
+ for (const [identifier, occurrences] of repeated) {
126
+ conditions.push({
127
+ kind: 'duplicate-record-identifier',
128
+ subject,
129
+ identifier,
130
+ occurrences,
131
+ });
132
+ }
133
+ }
134
+ // A cited identifier matching a declared observation is the cross-artifact
135
+ // rule `observationIds` leaves to this stage. Declared on every finding
136
+ // branch, so the check is too: AD-23's word is "additionally", a floor on
137
+ // `defect` rather than a prohibition on the other two.
138
+ const declared = new Set(record.observations.map((observation) => observation.observationId));
139
+ const dangling = record.findings
140
+ .map((finding) => ({
141
+ findingId: finding.findingId,
142
+ // Deduplicated first, in first-citation order: the condition names
143
+ // which identifiers resolved to nothing, and a finding citing one
144
+ // twice has one bad citation to report rather than two.
145
+ unresolvedObservationIds: [...new Set(finding.observationIds)].filter((observationId) => !declared.has(observationId)),
146
+ }))
147
+ .filter((entry) => entry.unresolvedObservationIds.length > 0)
148
+ .sort((a, b) => compareKeys([a.findingId, ...a.unresolvedObservationIds], [b.findingId, ...b.unresolvedObservationIds]));
149
+ for (const entry of dangling) {
150
+ conditions.push({ kind: 'dangling-citation', ...entry });
151
+ }
152
+ // The same rule over the record's second citation site. `resolveOutcome`
153
+ // reads a disposition's `observationIds` only for emptiness, so a `violated`
154
+ // disposition whose one citation names nothing would otherwise be believed,
155
+ // which is the shape AD-33 says invalidates rather than being believed.
156
+ const danglingDispositions = record.oracleDispositions
157
+ .map((disposition) => ({
158
+ oracleId: disposition.oracleId,
159
+ unresolvedObservationIds: [...new Set(disposition.observationIds)].filter((observationId) => !declared.has(observationId)),
160
+ }))
161
+ .filter((entry) => entry.unresolvedObservationIds.length > 0)
162
+ .sort((a, b) => compareKeys([a.oracleId, ...a.unresolvedObservationIds], [b.oracleId, ...b.unresolvedObservationIds]));
163
+ for (const entry of danglingDispositions) {
164
+ conditions.push({ kind: 'dangling-disposition-citation', ...entry });
165
+ }
166
+ // ADR-009 Decision 2's precedence: cited identifiers govern the witness
167
+ // match and quotation audits it. This is that audit's caller.
168
+ const unwitnessedQuotations = auditQuotation(record);
169
+ if (unwitnessedQuotations.length > 0) {
170
+ conditions.push({
171
+ kind: 'unwitnessed-quotation',
172
+ quotations: unwitnessedQuotations,
173
+ });
174
+ }
175
+ const isolationViolation = [];
176
+ if (manifest === null) {
177
+ // AD-16's absent case. The agreement and forbidden-input checks read
178
+ // manifest fields and cannot run; every record-internal check still does,
179
+ // so a run that is both manifest-less and internally inconsistent reports
180
+ // both rather than only the first.
181
+ conditions.push({ kind: 'isolation-manifest-absent' });
182
+ isolationViolation.push('isolation manifest absent');
183
+ }
184
+ else {
185
+ const mounts = outsideAllowlist(manifest.observedMounts, manifest.allowedMounts);
186
+ const networkTargets = outsideAllowlist(manifest.observedNetworkTargets, manifest.networkAllowlist);
187
+ const toolCalls = outsideAllowlist(manifest.observedToolCalls, manifest.toolAllowlist);
188
+ // One count over the three allowlists. The condition is "some observed
189
+ // value sits outside its allowlist", and asking it as three
190
+ // short-circuiting disjuncts would leave two of them evaluated in one
191
+ // direction only.
192
+ const exceeded = mounts.length + networkTargets.length + toolCalls.length;
193
+ if (manifest.violation !== null || exceeded > 0) {
194
+ conditions.push({
195
+ kind: 'isolation-manifest-violation',
196
+ violation: manifest.violation,
197
+ mountsOutsideAllowlist: mounts,
198
+ networkTargetsOutsideAllowlist: networkTargets,
199
+ toolCallsOutsideAllowlist: toolCalls,
200
+ });
201
+ // The declared violation first, then mounts, network targets, and tool
202
+ // calls, each in the manifest's own array order and one entry per
203
+ // offending value.
204
+ if (manifest.violation !== null) {
205
+ isolationViolation.push(`manifest violation: ${manifest.violation}`);
206
+ }
207
+ for (const value of mounts) {
208
+ isolationViolation.push(`mount outside allowlist: ${value}`);
209
+ }
210
+ for (const value of networkTargets) {
211
+ isolationViolation.push(`network target outside allowlist: ${value}`);
212
+ }
213
+ for (const value of toolCalls) {
214
+ isolationViolation.push(`tool call outside allowlist: ${value}`);
215
+ }
216
+ }
217
+ // AD-16's "account for each forbidden input by name". Read off the floor
218
+ // rather than off the manifest's own key order, so the seven are named in
219
+ // the order the list that generates them declares.
220
+ const admitted = FORBIDDEN_INPUT_FLOOR.filter((input) => !manifest.forbiddenInputAccounting[input].withheld);
221
+ if (admitted.length > 0) {
222
+ conditions.push({
223
+ kind: 'forbidden-input-not-withheld',
224
+ inputs: admitted,
225
+ });
226
+ }
227
+ // AD-32's agreement rule. Both artifacts carry all three fields, so the
228
+ // comparison is between them and never against the configuration artifact
229
+ // standing behind the digest.
230
+ for (const field of AGREEMENT_FIELDS) {
231
+ if (record[field] === manifest[field])
232
+ continue;
233
+ conditions.push({
234
+ kind: 'cross-artifact-disagreement',
235
+ field,
236
+ recordValue: record[field],
237
+ manifestValue: manifest[field],
238
+ });
239
+ }
240
+ }
241
+ // AD-24: "ingest computes its digest from the artifact and invalidates the
242
+ // run when it is absent or incomplete", restated by AD-11 as the one
243
+ // scoring-version input this stage recomputes. The record's declaration is
244
+ // the operand because AD-32 puts the trust boundary there: a caller that
245
+ // declares one configuration and runs another leaves the two declarations
246
+ // agreeing and only the recomputation disagreeing.
247
+ if (configuration === null) {
248
+ conditions.push({ kind: 'evaluator-configuration-absent' });
249
+ }
250
+ else {
251
+ const computedDigest = digestArtifact(configuration, EVALUATOR_CONFIGURATION_ARTIFACT_PATH);
252
+ if (computedDigest !== record.evaluatorConfigurationDigest) {
253
+ conditions.push({
254
+ kind: 'evaluator-configuration-digest-mismatch',
255
+ declaredDigest: record.evaluatorConfigurationDigest,
256
+ computedDigest,
257
+ });
258
+ }
259
+ }
260
+ // AD-17's record-decidable half. `null` is the shape AD-6's `judge-error`
261
+ // fires on, which is why the schema must parse it and why this is a condition
262
+ // rather than a parse failure.
263
+ //
264
+ // Deduplicated by the pair that addresses the result: `judgeResults` carries
265
+ // no uniqueness refinement, so one criterion may appear twice, and two
266
+ // byte-identical conditions naming one criterion are one finding a basis line
267
+ // can act on rather than two.
268
+ //
269
+ // The two citation families deduplicate identifiers inside an entry and never
270
+ // the entries themselves, which is the opposite answer to the same shape and
271
+ // is deliberate. A repeated `(rubricId, criterionId)` is one criterion scored
272
+ // twice, so the second entry names nothing new. Two findings sharing a
273
+ // `findingId` are two findings the record cannot address apart, and their
274
+ // citations may differ, so collapsing them would drop a bad citation on the
275
+ // floor.
276
+ const unscored = [
277
+ ...new Map(record.judgeResults
278
+ .filter((result) => result.score === null)
279
+ .map((result) => [
280
+ `${result.rubricId}/${result.criterionId}`,
281
+ { rubricId: result.rubricId, criterionId: result.criterionId },
282
+ ])).values(),
283
+ ].sort((a, b) => compareKeys([a.rubricId, a.criterionId], [b.rubricId, b.criterionId]));
284
+ for (const entry of unscored) {
285
+ conditions.push({ kind: 'judge-result-unscored', ...entry });
286
+ }
287
+ return {
288
+ // Read off the record and restated, never derived, recomputed, or
289
+ // defaulted, the same posture `mode` below takes.
290
+ runId: record.runId,
291
+ // Read off the record and restated, never derived, recomputed, or
292
+ // defaulted: AD-21 fixes mode before ingest and there is no fourth
293
+ // parameter for a caller to disagree with it through.
294
+ mode: record.mode,
295
+ evaluatorRecommendation: record.evaluatorRecommendation,
296
+ // `selectObservations`' own sort, matched exactly so two readers of one
297
+ // record cannot disagree about which observation came first. The
298
+ // identifier tie-break cannot fire on a record that parsed, since
299
+ // `sequence` is refined unique per record; it is kept for comparator
300
+ // identity with the shipped sort rather than because the case is real.
301
+ observations: [...record.observations].sort((a, b) => a.sequence - b.sequence || (a.observationId < b.observationId ? -1 : 1)),
302
+ // Sorted for the same reason the conditions are: neither array declares an
303
+ // order, so carrying the record's would make the product depend on a
304
+ // position nothing may read. The identifier is a total key on any record
305
+ // that does not repeat one, and a record that does repeats it into a
306
+ // `duplicate-record-identifier` condition, so the one input whose product
307
+ // still varies with presentation order is one the run already reports as
308
+ // unaddressable. Ordering that pair by content instead would mean
309
+ // canonicalizing every finding, which buys determinism for a record
310
+ // already flagged at the cost of making `non-canonicalizable-value`
311
+ // reachable from a field no quotation cites.
312
+ findings: [...record.findings].sort((a, b) => compareKeys([a.findingId], [b.findingId])),
313
+ dispositions: [...record.oracleDispositions].sort((a, b) => compareKeys([a.oracleId], [b.oracleId])),
314
+ conditions,
315
+ unwitnessedQuotations,
316
+ isolationViolation,
317
+ };
318
+ };
@@ -1,9 +1,10 @@
1
1
  /**
2
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.
3
+ * with their artifact inputs, the non-artifact values they receive, their one
4
+ * owned output, and the lineage edge each writes; twelve interchange artifacts
5
+ * with one producer apiece. The `check:lineage` scanner derives its allowlist
6
+ * from `module`, so the registry import stays type-only to keep zod off that
7
+ * gate's load path.
7
8
  */
8
9
  import type { InterchangeArtifactKey } from '../schemas/artifact.ts';
9
10
  /** AD-24's six stages, in the order the Consistency Conventions list them. */
@@ -16,6 +17,20 @@ export type PipelineStage = (typeof PIPELINE_STAGES)[number];
16
17
  */
17
18
  export declare const INTERNAL_PRODUCTS: readonly ['probe-plan', 'probe-observations', 'validated-observations', 'scored-outcomes-and-verdict'];
18
19
  export type InternalProduct = (typeof INTERNAL_PRODUCTS)[number];
20
+ /**
21
+ * Values a stage reads at its boundary that the `inputs` column does not name.
22
+ * AD-24 writes the table in artifacts, and owed item 6 records the source of
23
+ * run mode as absent from it: a row naming `sealed-run-record` says which bytes
24
+ * arrive and leaves open which value fixes the run's mode. Closed rather than
25
+ * free text, so the column cannot become a notes field.
26
+ *
27
+ * The three digests join `mode` for the same reason: they are exactly what
28
+ * this column exists for, "values a stage reads at its boundary that the
29
+ * inputs column does not name" -- `emit`'s three caller-attested AD-11
30
+ * digests have no artifact source anywhere in this pipeline.
31
+ */
32
+ export declare const STAGE_VALUE_INPUTS: readonly ['mode', 'corpusDigest', 'fixtureDigest', 'evaluatorConfigurationDigest'];
33
+ export type StageValueInput = (typeof STAGE_VALUE_INPUTS)[number];
19
34
  /**
20
35
  * What a stage does to the two AD-29 fields on the artifact it owns. `mints`
21
36
  * covers a root and a revision alike, since both write the fields.
@@ -25,6 +40,8 @@ export type LineageEdge = 'mints' | 'carries-through' | 'none';
25
40
  export type ArtifactProducer = PipelineStage | 'caller' | 'embedded';
26
41
  export type StageSignature = {
27
42
  readonly inputs: readonly (InterchangeArtifactKey | InternalProduct)[];
43
+ /** non-artifact values the stage receives; see STAGE_VALUE_INPUTS. */
44
+ readonly valueInputs: readonly StageValueInput[];
28
45
  readonly owns: InterchangeArtifactKey | InternalProduct;
29
46
  /** the owned output's registry key; null when the output is internal. */
30
47
  readonly ownsInterchange: InterchangeArtifactKey | null;
@@ -18,9 +18,28 @@ export const INTERNAL_PRODUCTS = [
18
18
  'validated-observations',
19
19
  'scored-outcomes-and-verdict',
20
20
  ];
21
+ /**
22
+ * Values a stage reads at its boundary that the `inputs` column does not name.
23
+ * AD-24 writes the table in artifacts, and owed item 6 records the source of
24
+ * run mode as absent from it: a row naming `sealed-run-record` says which bytes
25
+ * arrive and leaves open which value fixes the run's mode. Closed rather than
26
+ * free text, so the column cannot become a notes field.
27
+ *
28
+ * The three digests join `mode` for the same reason: they are exactly what
29
+ * this column exists for, "values a stage reads at its boundary that the
30
+ * inputs column does not name" -- `emit`'s three caller-attested AD-11
31
+ * digests have no artifact source anywhere in this pipeline.
32
+ */
33
+ export const STAGE_VALUE_INPUTS = [
34
+ 'mode',
35
+ 'corpusDigest',
36
+ 'fixtureDigest',
37
+ 'evaluatorConfigurationDigest',
38
+ ];
21
39
  export const STAGE_SIGNATURES = {
22
40
  compile: {
23
41
  inputs: ['eval-contract'],
42
+ valueInputs: [],
24
43
  owns: 'eval-contract',
25
44
  ownsInterchange: 'eval-contract',
26
45
  // The caller authors the contract and its lineage; `compile` validates
@@ -31,6 +50,7 @@ export const STAGE_SIGNATURES = {
31
50
  },
32
51
  seal: {
33
52
  inputs: ['eval-contract'],
53
+ valueInputs: [],
34
54
  owns: 'sealed-evaluator-brief',
35
55
  ownsInterchange: 'sealed-evaluator-brief',
36
56
  lineage: 'mints',
@@ -42,15 +62,21 @@ export const STAGE_SIGNATURES = {
42
62
  'isolation-manifest',
43
63
  'evaluator-configuration',
44
64
  ],
65
+ // Mode arrives on the sealed run record and is named here because owed
66
+ // item 6 asks the table for the source of run mode. `ingest` is the last
67
+ // stage that may read it from the caller and the first that may reject
68
+ // its absence; no later stage derives, recomputes, or defaults it.
69
+ valueInputs: ['mode'],
45
70
  owns: 'validated-observations',
46
71
  ownsInterchange: null,
47
72
  lineage: 'none',
48
- module: null,
73
+ module: 'src/core/ingest/ingest.ts',
49
74
  },
50
75
  preflight: {
51
76
  // AD-34 splits the stage into `plan` and `reduce`; both halves' inputs
52
77
  // are the stage's inputs, and only `reduce` returns an artifact.
53
78
  inputs: ['eval-contract', 'probe', 'probe-plan', 'probe-observations'],
79
+ valueInputs: [],
54
80
  owns: 'preflight-verdict',
55
81
  ownsInterchange: 'preflight-verdict',
56
82
  lineage: 'mints',
@@ -64,19 +90,25 @@ export const STAGE_SIGNATURES = {
64
90
  'preflight-verdict',
65
91
  'scoring-policy',
66
92
  ],
93
+ valueInputs: [],
67
94
  // AD-24: "score produces the outcome and verdict values emit
68
95
  // serializes". Owed item 6 records that type as unnamed; this names it.
69
96
  owns: 'scored-outcomes-and-verdict',
70
97
  ownsInterchange: null,
71
98
  lineage: 'none',
72
- module: null,
99
+ module: 'src/core/score/score.ts',
73
100
  },
74
101
  emit: {
75
102
  inputs: ['scored-outcomes-and-verdict'],
103
+ valueInputs: [
104
+ 'corpusDigest',
105
+ 'fixtureDigest',
106
+ 'evaluatorConfigurationDigest',
107
+ ],
76
108
  owns: 'evidence-artifact',
77
109
  ownsInterchange: 'evidence-artifact',
78
110
  lineage: 'mints',
79
- module: null,
111
+ module: 'src/core/emit/emit.ts',
80
112
  },
81
113
  };
82
114
  export const ARTIFACT_PRODUCERS = {
@@ -1,5 +1,6 @@
1
+ import type { ReferenceSetKeys } from '../evaluate/resolution.ts';
1
2
  import type { EvalContract } from '../schemas/eval-contract.ts';
2
- import type { Operation } from '../schemas/interface.ts';
3
+ import type { AnyOperation } from '../schemas/interface.ts';
3
4
  import type { ProbeRequest } from '../schemas/port-messages.ts';
4
5
  import type { JsonValue } from '../schemas/primitives.ts';
5
6
  import type { Probe } from '../schemas/probe.ts';
@@ -15,7 +16,7 @@ export type PlannedLeg = {
15
16
  readonly legId: string;
16
17
  readonly purpose: PlannedLegPurpose;
17
18
  readonly request: ProbeRequest;
18
- readonly operation: Operation;
19
+ readonly operation: AnyOperation;
19
20
  readonly inputs: WitnessInputs;
20
21
  };
21
22
  export type PlannedCheck = {
@@ -28,7 +29,7 @@ export type PlannedCheck = {
28
29
  readonly interfaceId: string;
29
30
  readonly operationId: string;
30
31
  readonly witness: SensitivityWitness | null;
31
- readonly operation: Operation;
32
+ readonly operation: AnyOperation;
32
33
  } | {
33
34
  readonly kind: 'state-reset';
34
35
  readonly legIds: readonly [string, string];
@@ -39,19 +40,20 @@ export type PlannedCheck = {
39
40
  readonly kind: 'seeded-faults-scoped';
40
41
  readonly defectId: string;
41
42
  readonly witness: ManifestationWitness;
42
- readonly operation: Operation;
43
+ readonly operation: AnyOperation;
43
44
  readonly cleanLegIds: readonly string[];
44
45
  } | {
45
46
  readonly kind: 'seeded-fault-fired';
46
47
  readonly defectId: string;
47
48
  readonly witness: ManifestationWitness | null;
48
- readonly operation: Operation | null;
49
+ readonly operation: AnyOperation | null;
49
50
  };
50
51
  export type PreflightPlan = {
51
52
  readonly runId: string;
52
53
  readonly legs: readonly PlannedLeg[];
53
54
  readonly checks: readonly PlannedCheck[];
54
55
  readonly referenceSets: Readonly<Record<string, JsonValue[]>>;
56
+ readonly referenceSetKeys: ReferenceSetKeys;
55
57
  };
56
58
  /**
57
59
  * AD-10's plan, derived from the interfaces the contract's probes exercise.