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.
- package/README.md +142 -67
- package/corpus/dev/README.md +20 -13
- package/corpus/dev/compile-seal-example/brief.json +1 -1
- package/corpus/dev/compile-seal-example/contract.json +1 -1
- package/corpus/dev/contracts/absent-collection-locations.json +1 -1
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
- package/corpus/dev/contracts/absent-success-indicator.json +1 -1
- package/corpus/dev/contracts/empty-channel-roles.json +1 -1
- package/corpus/dev/contracts/empty-collection-locations.json +1 -1
- package/corpus/dev/contracts/empty-request-shapes.json +1 -1
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
- package/corpus/dev/contracts/fragment-selection.json +1 -0
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
- package/corpus/dev/contracts/no-operation-inventory.json +1 -1
- package/corpus/dev/contracts/no-read-back-relation.json +1 -1
- package/corpus/dev/contracts/no-state-change-marker.json +1 -1
- package/corpus/dev/contracts/no-type-violating-step.json +1 -1
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
- package/corpus/dev/contracts/review-corpus.json +1 -0
- package/corpus/dev/contracts/satisfied-declarations.json +1 -1
- package/corpus/dev/contracts/single-required-response-key.json +1 -1
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
- package/corpus/dev/index.json +1 -1
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.js +1 -0
- package/dist/application/score.d.ts +48 -0
- package/dist/application/score.js +186 -0
- package/dist/cli/arguments.d.ts +7 -3
- package/dist/cli/arguments.js +73 -5
- package/dist/cli/exit-codes.d.ts +6 -2
- package/dist/cli/exit-codes.js +8 -9
- package/dist/cli/main.js +9 -0
- package/dist/cli/render.d.ts +3 -6
- package/dist/cli/render.js +57 -5
- package/dist/cli/run.d.ts +13 -3
- package/dist/cli/run.js +136 -16
- package/dist/core/canonical/canonicalize.d.ts +12 -0
- package/dist/core/canonical/canonicalize.js +18 -4
- package/dist/core/compile/bindings.d.ts +89 -0
- package/dist/core/compile/bindings.js +334 -0
- package/dist/core/compile/compile.d.ts +17 -0
- package/dist/core/compile/compile.js +37 -1
- package/dist/core/compile/excluded-content.d.ts +11 -0
- package/dist/core/compile/excluded-content.js +42 -0
- package/dist/core/compile/expression-legality.d.ts +49 -0
- package/dist/core/compile/expression-legality.js +158 -38
- package/dist/core/compile/forbidden-inputs.d.ts +8 -1
- package/dist/core/compile/forbidden-inputs.js +16 -3
- package/dist/core/compile/interface-inventory.d.ts +57 -2
- package/dist/core/compile/interface-inventory.js +119 -13
- package/dist/core/compile/reachability.d.ts +28 -1
- package/dist/core/compile/reachability.js +167 -35
- package/dist/core/compile/schema-version.d.ts +2 -0
- package/dist/core/compile/schema-version.js +25 -0
- package/dist/core/compile/sensitivity-witness.d.ts +31 -12
- package/dist/core/compile/sensitivity-witness.js +110 -23
- package/dist/core/compile/step-reference.d.ts +2 -0
- package/dist/core/compile/step-reference.js +49 -0
- package/dist/core/coverage/operations.d.ts +62 -0
- package/dist/core/coverage/operations.js +57 -0
- package/dist/core/coverage/relevance.d.ts +4 -2
- package/dist/core/coverage/relevance.js +22 -23
- package/dist/core/coverage/satisfaction.d.ts +2 -2
- package/dist/core/coverage/satisfaction.js +73 -48
- package/dist/core/declared-inputs.d.ts +83 -4
- package/dist/core/declared-inputs.js +105 -8
- package/dist/core/emit/emit.d.ts +9 -0
- package/dist/core/emit/emit.js +214 -0
- package/dist/core/emit/private-artifact-digest.d.ts +11 -0
- package/dist/core/emit/private-artifact-digest.js +31 -0
- package/dist/core/evaluate/evidence-resolution.d.ts +10 -3
- package/dist/core/evaluate/evidence-resolution.js +74 -11
- package/dist/core/evaluate/operators.d.ts +22 -0
- package/dist/core/evaluate/operators.js +17 -1
- package/dist/core/evaluate/resolution.d.ts +14 -3
- package/dist/core/evaluate/resolution.js +63 -3
- package/dist/core/excluded-content.d.ts +65 -0
- package/dist/core/excluded-content.js +113 -0
- package/dist/core/failure-codes.d.ts +2 -2
- package/dist/core/failure-codes.js +7 -2
- package/dist/core/ingest/conditions.d.ts +201 -0
- package/dist/core/ingest/conditions.js +48 -0
- package/dist/core/ingest/index.d.ts +13 -0
- package/dist/core/ingest/index.js +11 -0
- package/dist/core/ingest/ingest.d.ts +68 -0
- package/dist/core/ingest/ingest.js +318 -0
- package/dist/core/lineage/stage-table.d.ts +21 -4
- package/dist/core/lineage/stage-table.js +35 -3
- package/dist/core/preflight/plan.d.ts +7 -5
- package/dist/core/preflight/plan.js +75 -32
- package/dist/core/preflight/projection.d.ts +6 -3
- package/dist/core/preflight/projection.js +22 -2
- package/dist/core/preflight/reduce.js +47 -7
- package/dist/core/preflight/witness-evidence.d.ts +5 -5
- package/dist/core/preflight/witness-evidence.js +73 -20
- package/dist/core/schemas/artifact.d.ts +1049 -30
- package/dist/core/schemas/constraint-ledger.js +51 -0
- package/dist/core/schemas/defect-signature.d.ts +695 -0
- package/dist/core/schemas/defect-signature.js +175 -0
- package/dist/core/schemas/eval-contract.d.ts +550 -12
- package/dist/core/schemas/eval-contract.js +32 -2
- package/dist/core/schemas/evidence-artifact.d.ts +109 -19
- package/dist/core/schemas/evidence-artifact.js +64 -8
- package/dist/core/schemas/interface.d.ts +741 -13
- package/dist/core/schemas/interface.js +120 -7
- package/dist/core/schemas/isolation-manifest.js +16 -9
- package/dist/core/schemas/plan.d.ts +329 -3
- package/dist/core/schemas/plan.js +89 -3
- package/dist/core/schemas/pointer.d.ts +47 -1
- package/dist/core/schemas/pointer.js +89 -8
- package/dist/core/schemas/port-messages.d.ts +194 -10
- package/dist/core/schemas/port-messages.js +73 -6
- package/dist/core/schemas/probe-body.d.ts +18 -0
- package/dist/core/schemas/probe-body.js +13 -0
- package/dist/core/schemas/probe-qualification.d.ts +168 -0
- package/dist/core/schemas/probe-qualification.js +106 -0
- package/dist/core/schemas/probe.d.ts +431 -0
- package/dist/core/schemas/probe.js +6 -2
- package/dist/core/schemas/scoring-policy.d.ts +1 -0
- package/dist/core/schemas/scoring-policy.js +6 -1
- package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
- package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
- package/dist/core/schemas/sealed-run-record.d.ts +161 -12
- package/dist/core/schemas/sealed-run-record.js +92 -15
- package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
- package/dist/core/schemas/sensitivity-witness.js +61 -5
- package/dist/core/score/binding-order.d.ts +31 -0
- package/dist/core/score/binding-order.js +88 -0
- package/dist/core/score/bindings.d.ts +93 -0
- package/dist/core/score/bindings.js +309 -0
- package/dist/core/score/ladder-table.d.ts +23 -0
- package/dist/core/score/ladder-table.js +109 -0
- package/dist/core/score/ladder.d.ts +229 -0
- package/dist/core/score/ladder.js +464 -0
- package/dist/core/score/mode-agreement.d.ts +30 -0
- package/dist/core/score/mode-agreement.js +16 -0
- package/dist/core/score/outcome-table.d.ts +17 -0
- package/dist/core/score/outcome-table.js +172 -0
- package/dist/core/score/outcome.d.ts +466 -0
- package/dist/core/score/outcome.js +562 -0
- package/dist/core/score/qualification.d.ts +83 -0
- package/dist/core/score/qualification.js +645 -0
- package/dist/core/score/quotation.d.ts +56 -0
- package/dist/core/score/quotation.js +226 -0
- package/dist/core/score/reduce-trials.d.ts +80 -0
- package/dist/core/score/reduce-trials.js +90 -0
- package/dist/core/score/score.d.ts +72 -0
- package/dist/core/score/score.js +607 -0
- package/dist/core/score/selection.d.ts +80 -0
- package/dist/core/score/selection.js +98 -0
- package/dist/core/score/strength.d.ts +58 -0
- package/dist/core/score/strength.js +227 -0
- package/dist/core/score/witness.d.ts +138 -0
- package/dist/core/score/witness.js +320 -0
- package/dist/core/seal/derived-reference.d.ts +2 -9
- package/dist/core/seal/derived-reference.js +293 -46
- package/dist/core/seal/plan-index.d.ts +18 -3
- package/dist/core/seal/plan-index.js +44 -8
- package/dist/core/seal/seal.js +8 -3
- package/dist/core/stage-contracts.d.ts +62 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ports/environment-probe-port.d.ts +60 -4
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/probe-conformance.d.ts +29 -0
- package/dist/testing/probe-conformance.js +34 -5
- package/package.json +12 -3
- package/schemas/eval-contract.schema.json +1787 -797
- package/schemas/evidence-artifact.schema.json +126 -9
- package/schemas/isolation-manifest.schema.json +17 -10
- package/schemas/probe.schema.json +636 -5
- package/schemas/rubric.schema.json +1 -1
- package/schemas/scoring-policy.schema.json +8 -1
- package/schemas/sealed-evaluator-brief.schema.json +11 -1
- package/schemas/sealed-run-record.schema.json +314 -40
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { SEVERITY_LEVELS } from '../schemas/eval-contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* AD-21's exit codes for this rung. Not imported from `src/cli/exit-codes.ts`:
|
|
4
|
+
* `core/` imports only `core/schemas` (AD-1), so the three numbers are
|
|
5
|
+
* repeated here rather than shared. Exported so a test outside `core/` can
|
|
6
|
+
* assert this restatement still agrees with that file's own `EXIT_*`
|
|
7
|
+
* constants and `evidenceConditionsOnly` rule, which neither file can check
|
|
8
|
+
* of itself.
|
|
9
|
+
*/
|
|
10
|
+
export const LADDER_EXIT_CODES = {
|
|
11
|
+
invalid: 3,
|
|
12
|
+
FAIL: 2,
|
|
13
|
+
CONCERNS: 0,
|
|
14
|
+
WAIVED: 0,
|
|
15
|
+
PASS: 0,
|
|
16
|
+
};
|
|
17
|
+
const BEHAVIOURAL_FAILURE_STATES = [
|
|
18
|
+
'missed',
|
|
19
|
+
'abstained',
|
|
20
|
+
'bypassed',
|
|
21
|
+
'false-positive',
|
|
22
|
+
];
|
|
23
|
+
const INVALIDATING_STATES = [
|
|
24
|
+
'oracle-error',
|
|
25
|
+
'judge-error',
|
|
26
|
+
'infrastructure-error',
|
|
27
|
+
];
|
|
28
|
+
const isBehaviouralFailure = (state) => BEHAVIOURAL_FAILURE_STATES.includes(state);
|
|
29
|
+
const isInvalidatingState = (state) => INVALIDATING_STATES.includes(state);
|
|
30
|
+
const atOrAboveFloor = (severity, floor) => SEVERITY_LEVELS.indexOf(severity) >= SEVERITY_LEVELS.indexOf(floor);
|
|
31
|
+
const requiredOutcomes = (inputs) => inputs.outcomeState.outcomes.filter((outcome) => outcome.required);
|
|
32
|
+
const lineageChainPassed = (chain) => chain.lengthConsistent && chain.noRepeatedDigest && chain.noGap;
|
|
33
|
+
/**
|
|
34
|
+
* Invalid, identical between the two ladders: AD-21's text carries no mode
|
|
35
|
+
* split for it. Nine of AD-21's own clauses plus three owed item 4 leaves
|
|
36
|
+
* for this module to close: selector-ambiguity and unwitnessed-claim, an
|
|
37
|
+
* AD-6 selector-cardinality condition and an AD-40 witness-match result, and
|
|
38
|
+
* a third, separately assigned condition -- the unwitnessed-quotation audit
|
|
39
|
+
* `quotation.ts` ships with no caller. The
|
|
40
|
+
* "unrecognised evaluator recommendation value" clause is omitted per this
|
|
41
|
+
* module's own header comment.
|
|
42
|
+
*
|
|
43
|
+
* `selector-ambiguity` and `unwitnessed-claim` already coincide with an
|
|
44
|
+
* `infrastructure-error` state and so are already covered by
|
|
45
|
+
* `invalidating-state`; both are still named explicitly so `verdictBasis`
|
|
46
|
+
* carries the specific condition AD-21's spine prose never spelled out, not
|
|
47
|
+
* only the generic state.
|
|
48
|
+
*
|
|
49
|
+
* Ten more rows follow: eight previously-rungless ingest conditions, each
|
|
50
|
+
* newly given a rung, plus two conditions `score.ts` itself computes and no
|
|
51
|
+
* `ingest` condition names -- an ambiguous `operationId` across
|
|
52
|
+
* `permittedInterfaces`, and a trial set disagreeing with itself on
|
|
53
|
+
* `mode` or `evaluatorRecommendation`.
|
|
54
|
+
*/
|
|
55
|
+
const INVALID_ROWS = [
|
|
56
|
+
{
|
|
57
|
+
id: 'invalidating-state',
|
|
58
|
+
rung: 'invalid',
|
|
59
|
+
guard: 'an outcome resolved an AD-6 invalidating state (oracle-error, judge-error, or infrastructure-error)',
|
|
60
|
+
evidenceCondition: false,
|
|
61
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
62
|
+
.filter((outcome) => isInvalidatingState(outcome.resolution.state))
|
|
63
|
+
.map((outcome) => `oracle ${outcome.oracleId} resolved ${outcome.resolution.state}`),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'failed-preflight',
|
|
67
|
+
rung: 'invalid',
|
|
68
|
+
guard: 'a failed pre-flight',
|
|
69
|
+
evidenceCondition: false,
|
|
70
|
+
reasons: (inputs) => inputs.preflightPassed ? [] : ['pre-flight verdict did not pass'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'isolation-manifest-violation',
|
|
74
|
+
rung: 'invalid',
|
|
75
|
+
guard: 'an unaccounted isolation manifest under AD-16',
|
|
76
|
+
evidenceCondition: false,
|
|
77
|
+
reasons: (inputs) => inputs.evidenceIntegrity.isolationViolation.map((violation) => `isolation manifest violation: ${violation}`),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 're-execution-cap-breach',
|
|
81
|
+
rung: 'invalid',
|
|
82
|
+
guard: 'a re-execution cap breach under AD-6',
|
|
83
|
+
evidenceCondition: false,
|
|
84
|
+
reasons: (inputs) => {
|
|
85
|
+
const { invalidatedAttempts } = inputs.outcomeState.trials;
|
|
86
|
+
return invalidatedAttempts.length > inputs.outcomeState.reExecutionCap
|
|
87
|
+
? [
|
|
88
|
+
`${invalidatedAttempts.length} invalidated attempts exceeded the re-execution cap of ${inputs.outcomeState.reExecutionCap}`,
|
|
89
|
+
]
|
|
90
|
+
: [];
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'disposition-missing',
|
|
95
|
+
rung: 'invalid',
|
|
96
|
+
guard: 'a required oracle carrying no disposition in the run record under AD-23',
|
|
97
|
+
evidenceCondition: false,
|
|
98
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
99
|
+
.filter((outcome) => outcome.resolution.invalidatingConditions.includes('disposition-missing'))
|
|
100
|
+
.map((outcome) => `oracle ${outcome.oracleId} is required and carries no disposition`),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'required-check-unresolved',
|
|
104
|
+
rung: 'invalid',
|
|
105
|
+
guard: 'not every required check resolved',
|
|
106
|
+
evidenceCondition: false,
|
|
107
|
+
reasons: (inputs) => requiredOutcomes(inputs)
|
|
108
|
+
.filter((outcome) => !outcome.checkResolved)
|
|
109
|
+
.map((outcome) => `oracle ${outcome.oracleId} is required and its check never resolved`),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'selector-ambiguity',
|
|
113
|
+
rung: 'invalid',
|
|
114
|
+
guard: 'a step matched several observations under a single-valued cardinality (owed item 2)',
|
|
115
|
+
evidenceCondition: false,
|
|
116
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
117
|
+
.filter((outcome) => outcome.resolution.invalidatingConditions.includes('selector-ambiguity'))
|
|
118
|
+
.map((outcome) => `oracle ${outcome.oracleId}: selector ambiguity`),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'unwitnessed-claim',
|
|
122
|
+
rung: 'invalid',
|
|
123
|
+
guard: 'a defect finding claimed a detection no candidate observation witnesses (AD-40)',
|
|
124
|
+
evidenceCondition: false,
|
|
125
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
126
|
+
.filter((outcome) => outcome.resolution.invalidatingConditions.includes('unwitnessed-detection-claim'))
|
|
127
|
+
.map((outcome) => `oracle ${outcome.oracleId}: unwitnessed detection claim`),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'unwitnessed-quotation',
|
|
131
|
+
rung: 'invalid',
|
|
132
|
+
guard: "a defect finding's quoted evidence appears in no cited observation (AD-32)",
|
|
133
|
+
evidenceCondition: false,
|
|
134
|
+
reasons: (inputs) => inputs.outcomeState.unwitnessedQuotations.map((quoted) => `finding ${quoted.findingId}: unwitnessed quotation on channel ${quoted.channel}`),
|
|
135
|
+
},
|
|
136
|
+
// Eight previously-rungless conditions, each newly given a rung here.
|
|
137
|
+
// `ingest` already shipped the detection; this is the first ladder row
|
|
138
|
+
// that reports it.
|
|
139
|
+
{
|
|
140
|
+
id: 'duplicate-record-identifier',
|
|
141
|
+
rung: 'invalid',
|
|
142
|
+
guard: 'the record uses one observation, finding, or oracle-disposition identifier twice',
|
|
143
|
+
evidenceCondition: false,
|
|
144
|
+
reasons: (inputs) => inputs.evidenceIntegrity.duplicateRecordIdentifiers.map((condition) => `duplicate ${condition.subject} identifier "${condition.identifier}" (${condition.occurrences} occurrences)`),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'dangling-citation',
|
|
148
|
+
rung: 'invalid',
|
|
149
|
+
guard: 'a finding cites an observation identifier the record does not declare',
|
|
150
|
+
evidenceCondition: false,
|
|
151
|
+
reasons: (inputs) => inputs.evidenceIntegrity.danglingCitations.map((condition) => `finding ${condition.findingId} cites unresolved observation(s): ${condition.unresolvedObservationIds.join(', ')}`),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'dangling-disposition-citation',
|
|
155
|
+
rung: 'invalid',
|
|
156
|
+
guard: 'an oracle disposition cites an observation identifier the record does not declare',
|
|
157
|
+
evidenceCondition: false,
|
|
158
|
+
reasons: (inputs) => inputs.evidenceIntegrity.danglingDispositionCitations.map((condition) => `oracle ${condition.oracleId} disposition cites unresolved observation(s): ${condition.unresolvedObservationIds.join(', ')}`),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'forbidden-input-not-withheld',
|
|
162
|
+
rung: 'invalid',
|
|
163
|
+
guard: 'AD-16 forbidden input admitted rather than withheld',
|
|
164
|
+
evidenceCondition: false,
|
|
165
|
+
reasons: (inputs) => inputs.evidenceIntegrity.forbiddenInputsNotWithheld.flatMap((condition) => condition.inputs.map((input) => `forbidden input admitted rather than withheld: ${input}`)),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'cross-artifact-disagreement',
|
|
169
|
+
rung: 'invalid',
|
|
170
|
+
guard: 'the sealed run record and the isolation manifest disagree on a field AD-32 requires them to agree on',
|
|
171
|
+
evidenceCondition: false,
|
|
172
|
+
reasons: (inputs) => inputs.evidenceIntegrity.crossArtifactDisagreements.map((condition) => `record and manifest disagree on ${condition.field}: record "${condition.recordValue}", manifest "${condition.manifestValue}"`),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'evaluator-configuration-absent',
|
|
176
|
+
rung: 'invalid',
|
|
177
|
+
guard: 'the evaluator configuration artifact is absent',
|
|
178
|
+
evidenceCondition: false,
|
|
179
|
+
reasons: (inputs) => inputs.evidenceIntegrity.evaluatorConfigurationAbsent.map(() => 'evaluator configuration absent'),
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'evaluator-configuration-digest-mismatch',
|
|
183
|
+
rung: 'invalid',
|
|
184
|
+
guard: 'the evaluator configuration digest the record declares does not recompute from the artifact',
|
|
185
|
+
evidenceCondition: false,
|
|
186
|
+
reasons: (inputs) => inputs.evidenceIntegrity.evaluatorConfigurationDigestMismatches.map((condition) => `evaluator configuration digest mismatch: declared "${condition.declaredDigest}", computed "${condition.computedDigest}"`),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'judge-result-unscored',
|
|
190
|
+
rung: 'invalid',
|
|
191
|
+
guard: 'a judge result carries `score: null`',
|
|
192
|
+
evidenceCondition: false,
|
|
193
|
+
reasons: (inputs) => inputs.evidenceIntegrity.judgeResultsUnscored.map((condition) => `judge result unscored: rubric ${condition.rubricId} criterion ${condition.criterionId}`),
|
|
194
|
+
},
|
|
195
|
+
// The two score-computed conditions. Neither is an `IngestCondition`, so
|
|
196
|
+
// `score.ts` renders each entry itself and this row only adds the outer
|
|
197
|
+
// category label, following `isolation-manifest-violation`'s own
|
|
198
|
+
// double-wrap precedent above.
|
|
199
|
+
{
|
|
200
|
+
id: 'operation-identifier-collision',
|
|
201
|
+
rung: 'invalid',
|
|
202
|
+
guard: "an observation's operationId matches an operation in more than one permittedInterfaces entry",
|
|
203
|
+
evidenceCondition: false,
|
|
204
|
+
reasons: (inputs) => inputs.evidenceIntegrity.operationIdentifierCollisions.map((collision) => `operation identifier collision: ${collision}`),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'trial-set-field-disagreement',
|
|
208
|
+
rung: 'invalid',
|
|
209
|
+
guard: 'two trials in the same trial set disagree on `mode` or `evaluatorRecommendation`',
|
|
210
|
+
evidenceCondition: false,
|
|
211
|
+
reasons: (inputs) => inputs.evidenceIntegrity.trialSetDisagreements.map((disagreement) => `trial-set field disagreement: ${disagreement}`),
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
/**
|
|
215
|
+
* FAIL, shared between the two ladders. `evaluator-recommendation-fail` is
|
|
216
|
+
* production-only and spliced in by `PRODUCTION_LADDER` alone: AD-21 states
|
|
217
|
+
* plainly that the contract-scoring recommendation "is recorded as an input
|
|
218
|
+
* rather than promoted to a rung".
|
|
219
|
+
*/
|
|
220
|
+
const FAIL_ROWS_SHARED = [
|
|
221
|
+
{
|
|
222
|
+
id: 'behavioural-failure-at-or-above-floor',
|
|
223
|
+
rung: 'FAIL',
|
|
224
|
+
guard: "an AD-6 behavioural failure at or above the scoring policy's severity floor",
|
|
225
|
+
evidenceCondition: false,
|
|
226
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
227
|
+
.filter((outcome) => isBehaviouralFailure(outcome.resolution.state) &&
|
|
228
|
+
atOrAboveFloor(outcome.severity, inputs.severityFloor))
|
|
229
|
+
.map((outcome) => `oracle ${outcome.oracleId} resolved ${outcome.resolution.state} at or above the severity floor`),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'evidence-incomplete',
|
|
233
|
+
rung: 'FAIL',
|
|
234
|
+
guard: 'evidence reported incomplete under AD-17',
|
|
235
|
+
evidenceCondition: false,
|
|
236
|
+
reasons: (inputs) => inputs.evidenceIntegrity.disclosure.reportedIncomplete
|
|
237
|
+
? ['evidence reported incomplete']
|
|
238
|
+
: [],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'evidence-over-truncated',
|
|
242
|
+
rung: 'FAIL',
|
|
243
|
+
guard: 'evidence truncated past its disclosed bound under AD-17',
|
|
244
|
+
evidenceCondition: false,
|
|
245
|
+
reasons: (inputs) => inputs.evidenceIntegrity.overTruncated
|
|
246
|
+
? ['evidence truncated past its disclosed bound']
|
|
247
|
+
: [],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'evidence-unavailable',
|
|
251
|
+
rung: 'FAIL',
|
|
252
|
+
guard: 'evidence unavailable under AD-17',
|
|
253
|
+
evidenceCondition: false,
|
|
254
|
+
reasons: (inputs) => inputs.evidenceIntegrity.unavailable ? ['evidence unavailable'] : [],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'evidence-internally-inconsistent',
|
|
258
|
+
rung: 'FAIL',
|
|
259
|
+
guard: 'evidence internally inconsistent under AD-17',
|
|
260
|
+
evidenceCondition: false,
|
|
261
|
+
reasons: (inputs) => inputs.evidenceIntegrity.internallyInconsistent
|
|
262
|
+
? ['evidence internally inconsistent']
|
|
263
|
+
: [],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'lineage-chain-inconsistent',
|
|
267
|
+
rung: 'FAIL',
|
|
268
|
+
guard: 'a presented lineage chain internally inconsistent under AD-12',
|
|
269
|
+
evidenceCondition: false,
|
|
270
|
+
reasons: (inputs) => lineageChainPassed(inputs.remediationState)
|
|
271
|
+
? []
|
|
272
|
+
: ['presented lineage chain is internally inconsistent'],
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
const EVALUATOR_RECOMMENDATION_FAIL_ROW = {
|
|
276
|
+
id: 'evaluator-recommendation-fail',
|
|
277
|
+
rung: 'FAIL',
|
|
278
|
+
guard: 'an ingested evaluator recommendation of FAIL',
|
|
279
|
+
evidenceCondition: false,
|
|
280
|
+
reasons: (inputs) => inputs.evaluatorRecommendation === 'FAIL'
|
|
281
|
+
? ['evaluator recommendation FAIL']
|
|
282
|
+
: [],
|
|
283
|
+
};
|
|
284
|
+
/**
|
|
285
|
+
* CONCERNS, shared between the two ladders. `below-minimum-trial-count` and
|
|
286
|
+
* `oracle-unreached` are AD-21's own two evidence conditions: "a run that
|
|
287
|
+
* completed fewer trials than the policy's declared minimum, or any oracle
|
|
288
|
+
* resolving `unreached`. The last two are evidence conditions."
|
|
289
|
+
*/
|
|
290
|
+
const CONCERNS_ROWS_SHARED = [
|
|
291
|
+
{
|
|
292
|
+
id: 'behavioural-failure-below-floor',
|
|
293
|
+
rung: 'CONCERNS',
|
|
294
|
+
guard: "an AD-6 behavioural failure below the scoring policy's severity floor",
|
|
295
|
+
evidenceCondition: false,
|
|
296
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
297
|
+
.filter((outcome) => isBehaviouralFailure(outcome.resolution.state) &&
|
|
298
|
+
!atOrAboveFloor(outcome.severity, inputs.severityFloor))
|
|
299
|
+
.map((outcome) => `oracle ${outcome.oracleId} resolved ${outcome.resolution.state} below the severity floor`),
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'coverage-gap-at-or-above-floor',
|
|
303
|
+
rung: 'CONCERNS',
|
|
304
|
+
guard: 'an unsatisfied coverage gap at or above the severity floor under AD-20',
|
|
305
|
+
evidenceCondition: false,
|
|
306
|
+
reasons: (inputs) => inputs.coverageGaps
|
|
307
|
+
.filter((gap) => !gap.satisfied &&
|
|
308
|
+
atOrAboveFloor(gap.severity, inputs.severityFloor))
|
|
309
|
+
.map((gap) => `coverage gap ${gap.rule} unsatisfied at or above the severity floor`),
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'finding-confidence-below-threshold',
|
|
313
|
+
rung: 'CONCERNS',
|
|
314
|
+
guard: 'a finding whose confidence falls below the policy threshold',
|
|
315
|
+
evidenceCondition: false,
|
|
316
|
+
reasons: (inputs) => inputs.findings
|
|
317
|
+
.filter((finding) => finding.confidence < inputs.confidenceThreshold)
|
|
318
|
+
.map((finding) => `finding ${finding.findingId} confidence ${finding.confidence} below the policy threshold`),
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'uncited-defect-finding',
|
|
322
|
+
rung: 'CONCERNS',
|
|
323
|
+
guard: 'an ingested defect finding citing no oracle',
|
|
324
|
+
// No severity-floor gate, unlike the two rows above: an uncited defect
|
|
325
|
+
// finding is an evaluator already catching something real, not the
|
|
326
|
+
// possibly-harmless under-declared corner a floor exists to excuse.
|
|
327
|
+
evidenceCondition: false,
|
|
328
|
+
reasons: (inputs) => inputs.uncitedDefectFindings.map((gap) => `finding ${gap.findingId}: defect finding citing no oracle`),
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: 'below-minimum-trial-count',
|
|
332
|
+
rung: 'CONCERNS',
|
|
333
|
+
guard: "the run completed fewer trials than the policy's declared minimum",
|
|
334
|
+
evidenceCondition: true,
|
|
335
|
+
reasons: (inputs) => {
|
|
336
|
+
const { completed, declaredMinimum } = inputs.outcomeState.trials;
|
|
337
|
+
return completed < declaredMinimum
|
|
338
|
+
? [
|
|
339
|
+
`${completed} completed trials below the declared minimum of ${declaredMinimum}`,
|
|
340
|
+
]
|
|
341
|
+
: [];
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: 'oracle-unreached',
|
|
346
|
+
rung: 'CONCERNS',
|
|
347
|
+
guard: 'an oracle resolved `unreached`',
|
|
348
|
+
evidenceCondition: true,
|
|
349
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
350
|
+
.filter((outcome) => outcome.resolution.state === 'unreached')
|
|
351
|
+
.map((outcome) => `oracle ${outcome.oracleId} resolved unreached`),
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
const EVALUATOR_RECOMMENDATION_CONCERNS_ROW = {
|
|
355
|
+
id: 'evaluator-recommendation-concerns',
|
|
356
|
+
rung: 'CONCERNS',
|
|
357
|
+
guard: 'an ingested recommendation of CONCERNS',
|
|
358
|
+
evidenceCondition: false,
|
|
359
|
+
reasons: (inputs) => inputs.evaluatorRecommendation === 'CONCERNS'
|
|
360
|
+
? ['evaluator recommendation CONCERNS']
|
|
361
|
+
: [],
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* WAIVED, shared between the two ladders. Reads `OutcomeResolution.waiverRule`
|
|
365
|
+
* directly rather than re-deriving waiver state: AD-33's own Stage C already
|
|
366
|
+
* decided which oracle earned `not-applicable` against an unexpired waiver,
|
|
367
|
+
* and `waiver-honoured` is that decision's own name for it.
|
|
368
|
+
*/
|
|
369
|
+
const WAIVED_ROWS = [
|
|
370
|
+
{
|
|
371
|
+
id: 'waiver-honoured',
|
|
372
|
+
rung: 'WAIVED',
|
|
373
|
+
guard: 'every required check resolved and at least one resolved `not-applicable` against an unexpired waiver',
|
|
374
|
+
evidenceCondition: false,
|
|
375
|
+
reasons: (inputs) => inputs.outcomeState.outcomes
|
|
376
|
+
.filter((outcome) => outcome.resolution.waiverRule === 'waiver-honoured')
|
|
377
|
+
.map((outcome) => `oracle ${outcome.oracleId} waived (not-applicable)`),
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
/**
|
|
381
|
+
* Production's ladder: the shared rows plus the two rows that read an
|
|
382
|
+
* ingested evaluator recommendation, which contract-scoring's never does.
|
|
383
|
+
*/
|
|
384
|
+
export const PRODUCTION_LADDER = [
|
|
385
|
+
...INVALID_ROWS,
|
|
386
|
+
...FAIL_ROWS_SHARED,
|
|
387
|
+
EVALUATOR_RECOMMENDATION_FAIL_ROW,
|
|
388
|
+
...CONCERNS_ROWS_SHARED,
|
|
389
|
+
EVALUATOR_RECOMMENDATION_CONCERNS_ROW,
|
|
390
|
+
...WAIVED_ROWS,
|
|
391
|
+
];
|
|
392
|
+
/**
|
|
393
|
+
* Contract-scoring's ladder: FAIL/CONCERNS/WAIVED/PASS mirror production's
|
|
394
|
+
* minus every clause that promotes `evaluatorRecommendation`, per AD-21.
|
|
395
|
+
*/
|
|
396
|
+
export const CONTRACT_LADDER = [
|
|
397
|
+
...INVALID_ROWS,
|
|
398
|
+
...FAIL_ROWS_SHARED,
|
|
399
|
+
...CONCERNS_ROWS_SHARED,
|
|
400
|
+
...WAIVED_ROWS,
|
|
401
|
+
];
|
|
402
|
+
const RUNG_PRECEDENCE = ['invalid', 'FAIL', 'CONCERNS', 'WAIVED'];
|
|
403
|
+
/**
|
|
404
|
+
* First-match-wins over tiers, in AD-21's own precedence order; within the
|
|
405
|
+
* winning tier every row that holds contributes to `basis`, so a persistent
|
|
406
|
+
* judge fault cannot mask a real regression, which is AD-21's own stated
|
|
407
|
+
* reason for recording every condition that fired. PASS is the explicit
|
|
408
|
+
* final rung: reached only when every tier above is empty, never an
|
|
409
|
+
* `otherwise` branch written into the loop.
|
|
410
|
+
*/
|
|
411
|
+
function resolve(ladder, inputs) {
|
|
412
|
+
for (const rung of RUNG_PRECEDENCE) {
|
|
413
|
+
const fired = ladder
|
|
414
|
+
.filter((row) => row.rung === rung)
|
|
415
|
+
.map((row) => ({ row, reasons: row.reasons(inputs) }))
|
|
416
|
+
.filter((candidate) => candidate.reasons.length > 0);
|
|
417
|
+
if (fired.length === 0)
|
|
418
|
+
continue;
|
|
419
|
+
const basis = fired.flatMap((candidate) => candidate.reasons);
|
|
420
|
+
if (rung === 'invalid') {
|
|
421
|
+
return {
|
|
422
|
+
verdict: null,
|
|
423
|
+
exitCode: LADDER_EXIT_CODES.invalid,
|
|
424
|
+
strictPromotable: true,
|
|
425
|
+
basis,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (rung === 'CONCERNS') {
|
|
429
|
+
const strictPromotable = fired.some((candidate) => !candidate.row.evidenceCondition);
|
|
430
|
+
return {
|
|
431
|
+
verdict: 'CONCERNS',
|
|
432
|
+
exitCode: LADDER_EXIT_CODES.CONCERNS,
|
|
433
|
+
strictPromotable,
|
|
434
|
+
basis,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
verdict: rung,
|
|
439
|
+
exitCode: LADDER_EXIT_CODES[rung],
|
|
440
|
+
strictPromotable: true,
|
|
441
|
+
basis,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
verdict: 'PASS',
|
|
446
|
+
exitCode: LADDER_EXIT_CODES.PASS,
|
|
447
|
+
strictPromotable: true,
|
|
448
|
+
basis: [],
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/** AD-21's production-mode ladder: the subject is the system under test. */
|
|
452
|
+
export function resolveProductionVerdict(assessment) {
|
|
453
|
+
return resolve(PRODUCTION_LADDER, assessment);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* AD-21's contract-scoring ladder: the subject is the contract. No row here
|
|
457
|
+
* reads `evaluatorRecommendation`; `systemRecommendationRecorded` and
|
|
458
|
+
* `systemRecommendationNote` are carried on the input for symmetry with
|
|
459
|
+
* `EvidenceArtifact`'s own contract-scoring branch and are read by nothing in
|
|
460
|
+
* this module.
|
|
461
|
+
*/
|
|
462
|
+
export function resolveContractVerdict(assessment) {
|
|
463
|
+
return resolve(CONTRACT_LADDER, assessment);
|
|
464
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-32's cross-artifact mode-agreement check: a sealed run record's `mode`
|
|
3
|
+
* must agree with the evidence artifact's own `mode` discriminant, in both
|
|
4
|
+
* directions. A hand-written function at the assembly boundary rather than a
|
|
5
|
+
* schema refinement, mirroring `isolation-manifest.ts`'s
|
|
6
|
+
* `evaluatorConfigurationDigest` agreement note: no single schema sees both
|
|
7
|
+
* artifacts.
|
|
8
|
+
*
|
|
9
|
+
* `sealed-run-record.ts`'s own `mode` description states the rule this
|
|
10
|
+
* function enforces: mode is "fixed before ingest" and the evidence artifact
|
|
11
|
+
* "restates it and is never the source". A mismatch is therefore always a
|
|
12
|
+
* caller defect, never a legitimate re-labelling.
|
|
13
|
+
*/
|
|
14
|
+
import type { EvidenceArtifact } from '../schemas/evidence-artifact.ts';
|
|
15
|
+
import type { RunModeValue, SealedRunRecord } from '../schemas/sealed-run-record.ts';
|
|
16
|
+
export type ModeAgreement = {
|
|
17
|
+
readonly agrees: true;
|
|
18
|
+
readonly mode: RunModeValue;
|
|
19
|
+
} | {
|
|
20
|
+
readonly agrees: false;
|
|
21
|
+
readonly recordMode: RunModeValue;
|
|
22
|
+
readonly artifactMode: RunModeValue;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A plain equality check, which is what "rejects, in both directions"
|
|
26
|
+
* amounts to: `(production, contract-scoring)` and its reverse pairing are
|
|
27
|
+
* both instances of the two arguments disagreeing, and neither is a
|
|
28
|
+
* distinguished direction this function reads differently.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkModeAgreement(record: Pick<SealedRunRecord, 'mode'>, artifact: Pick<EvidenceArtifact, 'mode'>): ModeAgreement;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A plain equality check, which is what "rejects, in both directions"
|
|
3
|
+
* amounts to: `(production, contract-scoring)` and its reverse pairing are
|
|
4
|
+
* both instances of the two arguments disagreeing, and neither is a
|
|
5
|
+
* distinguished direction this function reads differently.
|
|
6
|
+
*/
|
|
7
|
+
export function checkModeAgreement(record, artifact) {
|
|
8
|
+
if (record.mode === artifact.mode) {
|
|
9
|
+
return { agrees: true, mode: record.mode };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
agrees: false,
|
|
13
|
+
recordMode: record.mode,
|
|
14
|
+
artifactMode: artifact.mode,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type OutcomeResolution } from './outcome.ts';
|
|
2
|
+
/** One named implication over the declared input domains. */
|
|
3
|
+
export type StructuralConstraint = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly implication: string;
|
|
6
|
+
};
|
|
7
|
+
/** A pair of domain values no tuple satisfying the constraints contains. */
|
|
8
|
+
export type InfeasiblePair = {
|
|
9
|
+
readonly left: string;
|
|
10
|
+
readonly right: string;
|
|
11
|
+
};
|
|
12
|
+
export type OutcomeTableConstraints = {
|
|
13
|
+
readonly constraints: readonly StructuralConstraint[];
|
|
14
|
+
readonly infeasiblePairs: readonly InfeasiblePair[];
|
|
15
|
+
};
|
|
16
|
+
/** The whole document. Throws a worded diagnosis on an empty census cell. */
|
|
17
|
+
export declare function outcomeDecisionTable(cases: readonly OutcomeResolution[], constraints: OutcomeTableConstraints): string;
|