eval-quality 0.1.0 → 0.2.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 +17 -11
- 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/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/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 +1 -1
- package/dist/cli/render.js +6 -4
- 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 +70 -0
- package/dist/core/compile/bindings.js +317 -0
- package/dist/core/compile/compile.d.ts +5 -0
- package/dist/core/compile/compile.js +9 -0
- package/dist/core/compile/expression-legality.d.ts +49 -0
- package/dist/core/compile/expression-legality.js +105 -31
- 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 +23 -1
- package/dist/core/compile/interface-inventory.js +32 -3
- package/dist/core/compile/reachability.d.ts +18 -1
- package/dist/core/compile/reachability.js +46 -1
- package/dist/core/compile/sensitivity-witness.d.ts +1 -1
- package/dist/core/compile/sensitivity-witness.js +1 -1
- 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 +11 -1
- package/dist/core/evaluate/evidence-resolution.js +4 -1
- package/dist/core/failure-codes.d.ts +2 -2
- package/dist/core/failure-codes.js +4 -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 +310 -0
- package/dist/core/lineage/stage-table.d.ts +21 -4
- package/dist/core/lineage/stage-table.js +35 -3
- package/dist/core/preflight/witness-evidence.js +10 -0
- package/dist/core/schemas/artifact.d.ts +385 -14
- package/dist/core/schemas/constraint-ledger.js +40 -0
- package/dist/core/schemas/defect-signature.d.ts +273 -0
- package/dist/core/schemas/defect-signature.js +127 -0
- package/dist/core/schemas/eval-contract.d.ts +33 -0
- package/dist/core/schemas/eval-contract.js +14 -0
- package/dist/core/schemas/evidence-artifact.d.ts +99 -19
- package/dist/core/schemas/evidence-artifact.js +64 -8
- package/dist/core/schemas/plan.d.ts +112 -0
- package/dist/core/schemas/plan.js +67 -2
- 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 +312 -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 +30 -3
- package/dist/core/schemas/sealed-run-record.js +37 -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 +297 -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 +615 -0
- package/dist/core/score/quotation.d.ts +79 -0
- package/dist/core/score/quotation.js +201 -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 +581 -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 +49 -0
- package/dist/core/score/strength.js +193 -0
- package/dist/core/score/witness.d.ts +117 -0
- package/dist/core/score/witness.js +315 -0
- package/dist/core/seal/derived-reference.d.ts +1 -1
- package/dist/core/seal/derived-reference.js +236 -28
- 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/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/package.json +12 -3
- package/schemas/eval-contract.schema.json +100 -2
- package/schemas/evidence-artifact.schema.json +96 -9
- package/schemas/probe.schema.json +508 -3
- package/schemas/scoring-policy.schema.json +8 -1
- package/schemas/sealed-evaluator-brief.schema.json +11 -1
- package/schemas/sealed-run-record.schema.json +19 -2
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owed item 3's score-time half: resolving a captured pointer to the scalar an
|
|
3
|
+
* earlier step's observation carried, and filtering a step's matched
|
|
4
|
+
* observations against its own selection predicate before reporting a
|
|
5
|
+
* cardinality verdict.
|
|
6
|
+
*
|
|
7
|
+
* An AD-39 selector is an input binding and an optional temporal clause, so
|
|
8
|
+
* `selectWithBindings` resolves both halves. `selectObservations` matches on
|
|
9
|
+
* `operationId` alone and is untouched, so its permutation guarantee and its
|
|
10
|
+
* tests stand; this wraps it.
|
|
11
|
+
*
|
|
12
|
+
* Pure and total, like the module it wraps: nothing throws on unresolvable
|
|
13
|
+
* evidence, and no AD-6 outcome state is assigned.
|
|
14
|
+
*/
|
|
15
|
+
import { capturedBindings } from '../compile/bindings.js';
|
|
16
|
+
import { channelRoot, walkTail } from '../evaluate/evidence-resolution.js';
|
|
17
|
+
import { ABSENT } from '../evaluate/resolved-value.js';
|
|
18
|
+
import { TRANSPORT_CHANNELS, } from '../schemas/pointer.js';
|
|
19
|
+
import { parseEvidenceTarget } from '../seal/plan-index.js';
|
|
20
|
+
import { bindingOrder } from './binding-order.js';
|
|
21
|
+
import { selectObservations } from './selection.js';
|
|
22
|
+
const ABSENT_RESOLUTION = { status: 'absent' };
|
|
23
|
+
/**
|
|
24
|
+
* The key one captured binding occupies in the resolution map. Keyed by
|
|
25
|
+
* binding site rather than by pointer: one step can carry several captured
|
|
26
|
+
* bindings, and two of them may name the same pointer in different channels.
|
|
27
|
+
* JSON-encoded rather than delimiter-joined, since a parameter key is arbitrary
|
|
28
|
+
* caller-supplied text and could contain any separator.
|
|
29
|
+
*/
|
|
30
|
+
export function bindingSiteKey(stepId, transportChannel, key) {
|
|
31
|
+
return JSON.stringify([stepId, transportChannel, key]);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves one captured pointer against the record.
|
|
35
|
+
*
|
|
36
|
+
* The referenced step is selected through `selectWithBindings`, so its own
|
|
37
|
+
* selection predicate applies before a value is read off it. Selecting it with
|
|
38
|
+
* `selectObservations` alone would match on `operationId` and leave a step that
|
|
39
|
+
* only its own bindings separate reporting the named ambiguity, which would
|
|
40
|
+
* make a capture from either half of a literal-bound collision pair resolve
|
|
41
|
+
* nothing at all. `resolved` carries the referenced step's own captured values,
|
|
42
|
+
* already filled by the tier below it, which is what makes the tiering
|
|
43
|
+
* load-bearing.
|
|
44
|
+
*
|
|
45
|
+
* The three dispositions already settled hold unchanged over the filtered
|
|
46
|
+
* result: one match binds, several under a declared `any` binds the lowest
|
|
47
|
+
* `sequence`, and several under `exactly-one`/`at-most-one` is the named
|
|
48
|
+
* ambiguity, returned as data with no value resolved.
|
|
49
|
+
*
|
|
50
|
+
* A pointer whose step the plan does not declare, or whose referenced step
|
|
51
|
+
* matched nothing, resolves `absent`.
|
|
52
|
+
*
|
|
53
|
+
* `resolved` as a status means an observation was selected, which is a
|
|
54
|
+
* different fact from the pointer finding a value in it: a tail that walks off
|
|
55
|
+
* the observed body comes back `resolved` carrying `ABSENT`, with the source
|
|
56
|
+
* observation and its `sequence` intact. That is why `CapturedResolution.value`
|
|
57
|
+
* is `ResolvedValue`. AD-26 makes absent an observation rather than an error,
|
|
58
|
+
* and `deepEquals` is false against it, so the referencing step still selects
|
|
59
|
+
* `none`; keeping the source observation means the ordering floor stays
|
|
60
|
+
* computable either way.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveCapturedValue(pointer, index, observations, resolved) {
|
|
63
|
+
const target = parseEvidenceTarget(pointer);
|
|
64
|
+
const step = index.stepOf(target.stepId);
|
|
65
|
+
if (step === undefined)
|
|
66
|
+
return ABSENT_RESOLUTION;
|
|
67
|
+
const selected = selectOne(step, observations, index, resolved, new Set());
|
|
68
|
+
if (selected === null)
|
|
69
|
+
return ABSENT_RESOLUTION;
|
|
70
|
+
if (selected.ambiguous !== null) {
|
|
71
|
+
return { status: 'ambiguous', matchedObservationIds: selected.ambiguous };
|
|
72
|
+
}
|
|
73
|
+
const { source } = selected;
|
|
74
|
+
if (source === null)
|
|
75
|
+
return ABSENT_RESOLUTION;
|
|
76
|
+
return {
|
|
77
|
+
status: 'resolved',
|
|
78
|
+
value: walkTail(channelRoot(source, target), target.tail),
|
|
79
|
+
observationId: source.observationId,
|
|
80
|
+
sequence: source.sequence,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function selectOne(step, observations, index, resolved, guard) {
|
|
84
|
+
if (guard.has(step.stepId))
|
|
85
|
+
return null;
|
|
86
|
+
const selection = selectFiltered(step, observations, index, resolved, new Set([...guard, step.stepId]));
|
|
87
|
+
const [first] = selection.matchedObservationIds;
|
|
88
|
+
if (selection.result === 'none' || first === undefined) {
|
|
89
|
+
return { source: null, ambiguous: null };
|
|
90
|
+
}
|
|
91
|
+
if (selection.result === 'several' && step.cardinality !== 'any') {
|
|
92
|
+
return { source: null, ambiguous: selection.matchedObservationIds };
|
|
93
|
+
}
|
|
94
|
+
// Ascending-`sequence` order already holds the lowest-sequence match first,
|
|
95
|
+
// which is `resolveTemporalAnchor`'s own rule for a declared `any`.
|
|
96
|
+
const source = observations.find((observation) => observation.observationId === first);
|
|
97
|
+
return { source: source ?? null, ambiguous: null };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Every captured binding in the plan, resolved in `bindingOrder`'s tiers and
|
|
101
|
+
* keyed by binding site. Walking the tiers in order is what lets a step's
|
|
102
|
+
* captured values be resolved against a referenced step whose own captures are
|
|
103
|
+
* already in the map. Steps `bindingOrder` reports cyclic are left out, so
|
|
104
|
+
* their bindings resolve as unlisted and filter every candidate away.
|
|
105
|
+
*/
|
|
106
|
+
export function resolveCapturedBindings(interactionPlan, index, observations) {
|
|
107
|
+
const resolved = new Map();
|
|
108
|
+
for (const tier of bindingOrder(interactionPlan).tiers) {
|
|
109
|
+
for (const stepId of tier) {
|
|
110
|
+
const step = index.stepOf(stepId);
|
|
111
|
+
if (step === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
for (const capture of capturedBindings(step)) {
|
|
114
|
+
resolved.set(bindingSiteKey(stepId, capture.transportChannel, capture.key), resolveCapturedValue(capture.pointer, index, observations, resolved));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return resolved;
|
|
119
|
+
}
|
|
120
|
+
// AD-4 separates `equality` from `deep-equality`; a literal binding is
|
|
121
|
+
// `JsonValue` and admits objects and arrays, so this is the deep one. Spelled
|
|
122
|
+
// here rather than reached for in `core/evaluate`'s operator set: that set
|
|
123
|
+
// resolves operands inside an oracle tree, and a binding filter is not an
|
|
124
|
+
// oracle.
|
|
125
|
+
// Exported so the AD-40 probe-side selector follows these rules rather than
|
|
126
|
+
// re-deriving them: two copies of a deep comparison that drift flip a witness
|
|
127
|
+
// match silently.
|
|
128
|
+
export function deepEquals(a, b) {
|
|
129
|
+
if (a === ABSENT || b === ABSENT)
|
|
130
|
+
return false;
|
|
131
|
+
if (a === b)
|
|
132
|
+
return true;
|
|
133
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
134
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return a.every((element, position) => deepEquals(element, b[position]));
|
|
138
|
+
}
|
|
139
|
+
if (a === null || b === null)
|
|
140
|
+
return false;
|
|
141
|
+
if (typeof a !== 'object' || typeof b !== 'object')
|
|
142
|
+
return false;
|
|
143
|
+
const keys = Object.keys(a);
|
|
144
|
+
if (keys.length !== Object.keys(b).length)
|
|
145
|
+
return false;
|
|
146
|
+
return keys.every((key) => Object.hasOwn(b, key) &&
|
|
147
|
+
deepEquals(a[key], b[key]));
|
|
148
|
+
}
|
|
149
|
+
export function jsonTypeOf(value) {
|
|
150
|
+
if (value === null)
|
|
151
|
+
return 'null';
|
|
152
|
+
if (Array.isArray(value))
|
|
153
|
+
return 'array';
|
|
154
|
+
return typeof value;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The highest `sequence` any of this step's captured bindings resolved from, or
|
|
158
|
+
* `null` when one of them resolved nothing at all. A candidate must come
|
|
159
|
+
* strictly after every captured value, so the highest is the binding bound.
|
|
160
|
+
*/
|
|
161
|
+
function capturedFloor(step, resolved) {
|
|
162
|
+
let floor = null;
|
|
163
|
+
for (const capture of capturedBindings(step)) {
|
|
164
|
+
const resolution = resolved.get(bindingSiteKey(step.stepId, capture.transportChannel, capture.key));
|
|
165
|
+
if (resolution === undefined || resolution.status !== 'resolved')
|
|
166
|
+
return null;
|
|
167
|
+
floor =
|
|
168
|
+
floor === null
|
|
169
|
+
? resolution.sequence
|
|
170
|
+
: Math.max(floor, resolution.sequence);
|
|
171
|
+
}
|
|
172
|
+
return floor;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Whether one observation's recorded call inputs satisfy one step's declared
|
|
176
|
+
* bindings. Four rules over the four tagged forms, and one shared
|
|
177
|
+
* precondition: every channel of `observation.callInputs` is nullable, and a
|
|
178
|
+
* `null` channel means the key is not present, so a binding into one filters
|
|
179
|
+
* the candidate out. That is the same fail-closed disposition as an absent
|
|
180
|
+
* captured value, and AD-26's.
|
|
181
|
+
*
|
|
182
|
+
* - `{ literal: v }`: the observed value deep-equals `v`, key order
|
|
183
|
+
* irrelevant.
|
|
184
|
+
* - `{ captured: p }`: the observed value deep-equals the resolved captured
|
|
185
|
+
* value. The ordering half is `capturedFloor`'s, applied before this runs.
|
|
186
|
+
* - `{ matcher: 'any' }` and `{ principal }`: the key is present, and nothing
|
|
187
|
+
* more. Neither declares a value the contract knows: a principal's value is
|
|
188
|
+
* provisioned by the harness at runtime, which is the whole reason the
|
|
189
|
+
* binding exists. The consequence is that two steps differing only by which
|
|
190
|
+
* principal they bind cannot be separated here, because no field of a sealed
|
|
191
|
+
* run record says which principal the harness used.
|
|
192
|
+
* - `{ matcher: 'type-violating' }`: the observed value's JSON type differs
|
|
193
|
+
* from the operation's declared type for that key. A key whose declared type
|
|
194
|
+
* is absent or `null` fails closed: an indeterminate type cannot prove a
|
|
195
|
+
* violation.
|
|
196
|
+
*/
|
|
197
|
+
function satisfiesBindings(step, observation, index, resolved) {
|
|
198
|
+
const operation = index.operationOf(step.operationId);
|
|
199
|
+
for (const channel of TRANSPORT_CHANNELS) {
|
|
200
|
+
const binding = step.inputBinding[channel];
|
|
201
|
+
if (binding === null)
|
|
202
|
+
continue;
|
|
203
|
+
const observed = observation.callInputs[channel];
|
|
204
|
+
if (observed === null)
|
|
205
|
+
return false;
|
|
206
|
+
for (const key of Object.keys(binding)) {
|
|
207
|
+
const value = binding[key];
|
|
208
|
+
if (value === undefined)
|
|
209
|
+
continue;
|
|
210
|
+
if (!Object.hasOwn(observed, key))
|
|
211
|
+
return false;
|
|
212
|
+
const actual = observed[key];
|
|
213
|
+
if ('literal' in value) {
|
|
214
|
+
if (!deepEquals(actual, value.literal))
|
|
215
|
+
return false;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if ('captured' in value) {
|
|
219
|
+
const resolution = resolved.get(bindingSiteKey(step.stepId, channel, key));
|
|
220
|
+
if (resolution === undefined || resolution.status !== 'resolved') {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (!deepEquals(actual, resolution.value))
|
|
224
|
+
return false;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if ('principal' in value)
|
|
228
|
+
continue;
|
|
229
|
+
if (value.matcher === 'any')
|
|
230
|
+
continue;
|
|
231
|
+
const declared = operation?.requestShape[channel].types[key];
|
|
232
|
+
if (declared === undefined || declared === null)
|
|
233
|
+
return false;
|
|
234
|
+
if (jsonTypeOf(actual) === declared)
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
function temporalFloor(step, index, observations, resolved, guard) {
|
|
241
|
+
if (step.after === null)
|
|
242
|
+
return { kind: 'none' };
|
|
243
|
+
const anchorStep = index.stepOf(step.after);
|
|
244
|
+
if (anchorStep === undefined)
|
|
245
|
+
return { kind: 'none' };
|
|
246
|
+
const anchor = selectOne(anchorStep, observations, index, resolved, guard);
|
|
247
|
+
if (anchor === null || anchor.source === null)
|
|
248
|
+
return { kind: 'unsatisfiable' };
|
|
249
|
+
return { kind: 'after', sequence: anchor.source.sequence };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* `selectObservations`, then the step's own selection predicate as a filter
|
|
253
|
+
* over the matches, then the cardinality verdict over what survived.
|
|
254
|
+
*
|
|
255
|
+
* The order is temporal clause, then capture ordering, then the binding
|
|
256
|
+
* filters. Ordering is enforced at score time as well as at compile time: the
|
|
257
|
+
* persistence read-back this exists for is exactly a claim about order, and a
|
|
258
|
+
* record whose `GET` sits at `sequence` 2 and whose `POST` sits at `sequence` 9
|
|
259
|
+
* would otherwise satisfy the binding and pass an oracle proving the opposite.
|
|
260
|
+
*
|
|
261
|
+
* `resolved` is the map `resolveCapturedBindings` fills from `bindingOrder`'s
|
|
262
|
+
* tiers, keyed by binding site. An unlisted or unresolved site filters every
|
|
263
|
+
* candidate out, so a caller that skipped a tier gets `none` rather than a
|
|
264
|
+
* silently wrong match.
|
|
265
|
+
*
|
|
266
|
+
* A filter over zero bindings and no clause separates nothing, so two steps
|
|
267
|
+
* sharing an operation and declaring neither both still return `several`: the
|
|
268
|
+
* declared structure does not distinguish them.
|
|
269
|
+
*/
|
|
270
|
+
export function selectWithBindings(step, observations, index, resolved) {
|
|
271
|
+
return selectFiltered(step, observations, index, resolved, new Set());
|
|
272
|
+
}
|
|
273
|
+
// `guard` carries the steps already being resolved further up the call, so an
|
|
274
|
+
// `after` clause cycle in an uncompiled plan terminates instead of recursing.
|
|
275
|
+
function selectFiltered(step, observations, index, resolved, guard) {
|
|
276
|
+
const base = selectObservations(step, observations);
|
|
277
|
+
const temporal = temporalFloor(step, index, observations, resolved, new Set([...guard, step.stepId]));
|
|
278
|
+
const captured = capturedFloor(step, resolved);
|
|
279
|
+
const hasCaptures = capturedBindings(step).length > 0;
|
|
280
|
+
if (temporal.kind === 'unsatisfiable' || (hasCaptures && captured === null)) {
|
|
281
|
+
return { result: 'none', matchedObservationIds: [] };
|
|
282
|
+
}
|
|
283
|
+
const floor = Math.max(temporal.kind === 'after' ? temporal.sequence : Number.NEGATIVE_INFINITY, captured ?? Number.NEGATIVE_INFINITY);
|
|
284
|
+
const byId = new Map(observations.map((observation) => [observation.observationId, observation]));
|
|
285
|
+
const matchedObservationIds = base.matchedObservationIds.filter((id) => {
|
|
286
|
+
const observation = byId.get(id);
|
|
287
|
+
return (observation !== undefined &&
|
|
288
|
+
observation.sequence > floor &&
|
|
289
|
+
satisfiesBindings(step, observation, index, resolved));
|
|
290
|
+
});
|
|
291
|
+
const result = matchedObservationIds.length === 0
|
|
292
|
+
? 'none'
|
|
293
|
+
: matchedObservationIds.length === 1
|
|
294
|
+
? 'one'
|
|
295
|
+
: 'several';
|
|
296
|
+
return { result, matchedObservationIds };
|
|
297
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-21's published decision table, emitted from the two ladders' own
|
|
3
|
+
* condition rows and from the fixture set that exercises them, mirroring
|
|
4
|
+
* `outcome-table.ts`'s builder: pure, so the writer and the drift check
|
|
5
|
+
* cannot disagree about bytes, and every census count throws on zero rather
|
|
6
|
+
* than publishing a coverage claim the fixture set does not back.
|
|
7
|
+
*
|
|
8
|
+
* `LadderResolution.basis` is free text (`evidence-artifact.ts`'s own
|
|
9
|
+
* `verdictBasis` shape), so the per-condition census does not parse it: it
|
|
10
|
+
* re-evaluates each row's own `reasons` function against the same fixture
|
|
11
|
+
* input the resolution was built from, the same way `resolveProductionVerdict`
|
|
12
|
+
* and `resolveContractVerdict` do internally.
|
|
13
|
+
*/
|
|
14
|
+
import { type ContractAssessment, type ProductionAssessment } from './ladder.ts';
|
|
15
|
+
export type LadderFixtureCase = {
|
|
16
|
+
readonly ladder: 'production';
|
|
17
|
+
readonly assessment: ProductionAssessment;
|
|
18
|
+
} | {
|
|
19
|
+
readonly ladder: 'contract-scoring';
|
|
20
|
+
readonly assessment: ContractAssessment;
|
|
21
|
+
};
|
|
22
|
+
/** The whole document. Throws a worded diagnosis on an empty census cell. */
|
|
23
|
+
export declare function ladderDecisionTable(cases: readonly LadderFixtureCase[]): string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-21's published decision table, emitted from the two ladders' own
|
|
3
|
+
* condition rows and from the fixture set that exercises them, mirroring
|
|
4
|
+
* `outcome-table.ts`'s builder: pure, so the writer and the drift check
|
|
5
|
+
* cannot disagree about bytes, and every census count throws on zero rather
|
|
6
|
+
* than publishing a coverage claim the fixture set does not back.
|
|
7
|
+
*
|
|
8
|
+
* `LadderResolution.basis` is free text (`evidence-artifact.ts`'s own
|
|
9
|
+
* `verdictBasis` shape), so the per-condition census does not parse it: it
|
|
10
|
+
* re-evaluates each row's own `reasons` function against the same fixture
|
|
11
|
+
* input the resolution was built from, the same way `resolveProductionVerdict`
|
|
12
|
+
* and `resolveContractVerdict` do internally.
|
|
13
|
+
*/
|
|
14
|
+
import { CONTRACT_LADDER, LADDER_EXIT_CODES, PRODUCTION_LADDER, resolveContractVerdict, resolveProductionVerdict, } from './ladder.js';
|
|
15
|
+
/** `|` escaped: a guard is prose, and a broken table is still byte-stable. */
|
|
16
|
+
const cell = (text) => text.replace(/\|/g, '\\|');
|
|
17
|
+
const row = (cells) => `| ${cells.map(cell).join(' | ')} |`;
|
|
18
|
+
const table = (headers, rows) => [
|
|
19
|
+
row(headers),
|
|
20
|
+
`| ${headers.map(() => '---').join(' | ')} |`,
|
|
21
|
+
...rows.map(row),
|
|
22
|
+
];
|
|
23
|
+
const code = (text) => `\`${text}\``;
|
|
24
|
+
const HEADER = [
|
|
25
|
+
'# AD-21 verdict decision',
|
|
26
|
+
'',
|
|
27
|
+
'Generated by `npm run generate:ad21-table` from the two rule tables in',
|
|
28
|
+
'`src/core/score/ladder.ts` and from the fixture set that exercises them. Guarded by',
|
|
29
|
+
'`npm run check:ad21-table`, a byte-exact drift check. A hand edit fails that check; regenerate.',
|
|
30
|
+
'',
|
|
31
|
+
'Each ladder is pure and total: every input value returns a defined resolution and nothing',
|
|
32
|
+
'throws. Precedence is Invalid, then FAIL, then CONCERNS, then WAIVED, then PASS. Within the',
|
|
33
|
+
'winning tier every condition that holds contributes to the basis; none is masked by another',
|
|
34
|
+
'that fired first.',
|
|
35
|
+
'',
|
|
36
|
+
'The exit-code column names the rung, not `--strict`. Every CONCERNS row shows 0 because that is',
|
|
37
|
+
"the rung's own exit code before `--strict` runs; passing `--strict` promotes the resolution to 1,",
|
|
38
|
+
'except when every firing row for that resolution has "Evidence condition" `yes`, which `--strict`',
|
|
39
|
+
'never promotes.',
|
|
40
|
+
];
|
|
41
|
+
const RUNG_LABELS = ['invalid', 'FAIL', 'CONCERNS', 'WAIVED', 'PASS'];
|
|
42
|
+
const rungLabelOf = (verdict) => verdict === null ? 'invalid' : verdict;
|
|
43
|
+
/**
|
|
44
|
+
* Counts each key across one ladder's cases, then reports the first key no
|
|
45
|
+
* case reached. A zero census is what a narrowed fixture set looks like, and
|
|
46
|
+
* no regeneration repairs it.
|
|
47
|
+
*/
|
|
48
|
+
function census(ladderName, label, keys, countOf) {
|
|
49
|
+
return keys.map((key) => {
|
|
50
|
+
const count = countOf(key);
|
|
51
|
+
if (count === 0) {
|
|
52
|
+
throw new Error(`ladderDecisionTable: no ${ladderName} case reaches ${label} ${key}, so the published census would claim coverage the fixture set does not have`);
|
|
53
|
+
}
|
|
54
|
+
return [code(key), String(count)];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function ladderSection(name, ladderKey, ladderRows, cases) {
|
|
58
|
+
const ladderCases = cases.filter((entry) => entry.ladder === ladderKey);
|
|
59
|
+
if (ladderCases.length === 0) {
|
|
60
|
+
throw new Error(`ladderDecisionTable: no resolved cases for the ${ladderKey} ladder`);
|
|
61
|
+
}
|
|
62
|
+
const resolved = ladderCases.map((entry) => entry.ladder === 'production'
|
|
63
|
+
? resolveProductionVerdict(entry.assessment)
|
|
64
|
+
: resolveContractVerdict(entry.assessment));
|
|
65
|
+
const conditionRows = ladderRows.map((conditionRow) => [
|
|
66
|
+
code(conditionRow.id),
|
|
67
|
+
conditionRow.rung === 'invalid' ? 'Invalid' : conditionRow.rung,
|
|
68
|
+
conditionRow.guard,
|
|
69
|
+
conditionRow.evidenceCondition ? 'yes' : 'no',
|
|
70
|
+
String(LADDER_EXIT_CODES[conditionRow.rung]),
|
|
71
|
+
]);
|
|
72
|
+
const rungCensus = census(ladderKey, 'the rung', [...RUNG_LABELS], (key) => resolved.filter((entry) => rungLabelOf(entry.verdict) === key).length);
|
|
73
|
+
const conditionCensus = ladderRows.map((conditionRow) => {
|
|
74
|
+
const count = ladderCases.filter((entry) => conditionRow.reasons(entry.assessment).length > 0).length;
|
|
75
|
+
if (count === 0) {
|
|
76
|
+
throw new Error(`ladderDecisionTable: no ${ladderKey} case reaches the condition ${conditionRow.id}, so the published census would claim coverage the fixture set does not have`);
|
|
77
|
+
}
|
|
78
|
+
return [code(conditionRow.id), String(count)];
|
|
79
|
+
});
|
|
80
|
+
return [
|
|
81
|
+
`## ${name}`,
|
|
82
|
+
'',
|
|
83
|
+
...table(['Condition', 'Rung', 'Guard', 'Evidence condition', 'Exit code'], conditionRows),
|
|
84
|
+
'',
|
|
85
|
+
`### ${name} census, by rung`,
|
|
86
|
+
'',
|
|
87
|
+
`Over the ${ladderCases.length} resolved ${ladderKey} fixture cases.`,
|
|
88
|
+
'',
|
|
89
|
+
...table(['Rung', 'Cases'], rungCensus),
|
|
90
|
+
'',
|
|
91
|
+
`### ${name} census, by condition`,
|
|
92
|
+
'',
|
|
93
|
+
...table(['Condition', 'Cases'], conditionCensus),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
/** The whole document. Throws a worded diagnosis on an empty census cell. */
|
|
97
|
+
export function ladderDecisionTable(cases) {
|
|
98
|
+
if (cases.length === 0) {
|
|
99
|
+
throw new Error('ladderDecisionTable: no resolved cases, so every census below would be empty');
|
|
100
|
+
}
|
|
101
|
+
const lines = [
|
|
102
|
+
...HEADER,
|
|
103
|
+
'',
|
|
104
|
+
...ladderSection('Production ladder', 'production', PRODUCTION_LADDER, cases),
|
|
105
|
+
'',
|
|
106
|
+
...ladderSection('Contract-scoring ladder', 'contract-scoring', CONTRACT_LADDER, cases),
|
|
107
|
+
];
|
|
108
|
+
return `${lines.join('\n')}\n`;
|
|
109
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-21's total decision table, built for the first time. Owed item 4's
|
|
3
|
+
* remaining three clauses: `ProductionAssessment` and `ContractAssessment`
|
|
4
|
+
* as separate input types, each resolved by its own pure, total,
|
|
5
|
+
* first-match-wins ladder over the same seven state categories AD-21 names.
|
|
6
|
+
*
|
|
7
|
+
* Every rung is derived independently per category, so a condition never
|
|
8
|
+
* masks another that fired in the same tier -- the same independence
|
|
9
|
+
* `outcome.ts`'s Stage A uses for AD-6's invalidating conditions. Precedence
|
|
10
|
+
* across tiers is Invalid, then FAIL, then CONCERNS, then WAIVED, then PASS,
|
|
11
|
+
* exactly AD-21's own order; within a tier every condition that holds is
|
|
12
|
+
* recorded, never only the first.
|
|
13
|
+
*
|
|
14
|
+
* `verdict: null` is AD-21's "Invalid" rung. It is not one of the four
|
|
15
|
+
* `Verdict` members: AD-21 itself says a failed run "never becomes a
|
|
16
|
+
* contract verdict", and exit code 3 sits outside the verdict range
|
|
17
|
+
* `src/cli/exit-codes.ts` defines. This module cannot import that file
|
|
18
|
+
* (`core/` imports only `core/schemas`, AD-1), so the three exit-code
|
|
19
|
+
* literals are repeated here rather than shared.
|
|
20
|
+
*
|
|
21
|
+
* AD-21's own "unrecognised evaluator recommendation value" clause is
|
|
22
|
+
* omitted: `verdict.ts:22-25` records that a schema-valid
|
|
23
|
+
* `EvaluatorRecommendation` cannot carry one, since an unrecognised value
|
|
24
|
+
* fails to parse before either ladder runs.
|
|
25
|
+
*/
|
|
26
|
+
import type { IngestCondition } from '../ingest/conditions.ts';
|
|
27
|
+
import { type Severity } from '../schemas/eval-contract.ts';
|
|
28
|
+
import type { CoverageGap, LineageChain, Trials, UncitedFindingGap } from '../schemas/evidence-artifact.ts';
|
|
29
|
+
import type { EvidenceDisclosure } from '../schemas/sealed-run-record.ts';
|
|
30
|
+
import type { EvaluatorRecommendation, Verdict } from '../schemas/verdict.ts';
|
|
31
|
+
import type { OutcomeResolution } from './outcome.ts';
|
|
32
|
+
import type { UnwitnessedQuotation } from './quotation.ts';
|
|
33
|
+
/**
|
|
34
|
+
* One oracle's AD-33 resolution, paired with the two fields `OutcomeResolution`
|
|
35
|
+
* does not carry: `severity` is caller-owned on the persisted `Outcome`
|
|
36
|
+
* (`evidence-artifact.ts`'s own field, sourced from `Behavior.severity`), and
|
|
37
|
+
* `checkResolved` is the caller's own `OutcomeInputs.checkResolution !== null`,
|
|
38
|
+
* needed for AD-21's "no required check resolved" Invalid clause, which
|
|
39
|
+
* `OutcomeResolution` has no field for.
|
|
40
|
+
*/
|
|
41
|
+
export type ScoredOutcome = {
|
|
42
|
+
readonly oracleId: string;
|
|
43
|
+
readonly required: boolean;
|
|
44
|
+
readonly severity: Severity;
|
|
45
|
+
readonly checkResolved: boolean;
|
|
46
|
+
readonly resolution: OutcomeResolution;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* AD-21's "outcome state" category. A composite rather than a bare
|
|
50
|
+
* `OutcomeState[]`: the three new Invalid conditions below each need
|
|
51
|
+
* per-condition detail a state enum alone cannot carry, so this bundles the
|
|
52
|
+
* per-oracle resolutions, the record-level `auditQuotation` result, and
|
|
53
|
+
* `Trials` alongside the re-execution cap (Design Notes: the AD-6
|
|
54
|
+
* re-execution cap, never AD-12's `Remediation.cap`).
|
|
55
|
+
*/
|
|
56
|
+
export type OutcomeStateInputs = {
|
|
57
|
+
readonly outcomes: readonly ScoredOutcome[];
|
|
58
|
+
readonly unwitnessedQuotations: readonly UnwitnessedQuotation[];
|
|
59
|
+
readonly trials: Trials;
|
|
60
|
+
readonly reExecutionCap: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* AD-21's "evidence-integrity state" category, read for the FAIL rung's
|
|
64
|
+
* "incomplete, over-truncated, unavailable, or internally inconsistent under
|
|
65
|
+
* AD-17" clause. `EvidenceDisclosure` carries only the two caller-declared
|
|
66
|
+
* halves (`reportedIncomplete`, `truncationBound`); the other two have no
|
|
67
|
+
* source anywhere in the tree today (`evidence-disclosure`'s own describe:
|
|
68
|
+
* "unavailable is an ArtifactReference that does not resolve through the
|
|
69
|
+
* corpus port, and internally inconsistent is AD-32's cross-artifact
|
|
70
|
+
* agreement check"), so they arrive declared rather than derived, the same
|
|
71
|
+
* posture `outcome.ts`'s `judgeConduct` and `waiver` inputs take.
|
|
72
|
+
* `overTruncated` is declared for the same reason: it compares the disclosed
|
|
73
|
+
* bound against the evidence actually carried, which this pure function does
|
|
74
|
+
* not read.
|
|
75
|
+
*/
|
|
76
|
+
export type EvidenceIntegrityInputs = {
|
|
77
|
+
readonly disclosure: EvidenceDisclosure;
|
|
78
|
+
readonly overTruncated: boolean;
|
|
79
|
+
readonly unavailable: boolean;
|
|
80
|
+
readonly internallyInconsistent: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* AD-16's unaccounted-manifest Invalid condition: `IsolationManifest.violation`.
|
|
83
|
+
* Widened from a single nullable string to an array, matching
|
|
84
|
+
* `ValidatedObservations.isolationViolation`'s shape (`ingest.ts:78`): empty
|
|
85
|
+
* when nothing fired, one entry per offending value.
|
|
86
|
+
*/
|
|
87
|
+
readonly isolationViolation: readonly string[];
|
|
88
|
+
/**
|
|
89
|
+
* Eight ingest conditions, each a structured payload `ingest` already
|
|
90
|
+
* computed, `Extract`-typed against `IngestCondition` so a rename in
|
|
91
|
+
* `ingest/conditions.ts` collapses the field's type to `never[]` rather
|
|
92
|
+
* than silently reading a stale shape. Ascending `EvidenceIntegrityInputs`
|
|
93
|
+
* field order matches `INGEST_CONDITION_KINDS`' declaration order.
|
|
94
|
+
*/
|
|
95
|
+
readonly duplicateRecordIdentifiers: readonly Extract<IngestCondition, {
|
|
96
|
+
kind: 'duplicate-record-identifier';
|
|
97
|
+
}>[];
|
|
98
|
+
readonly danglingCitations: readonly Extract<IngestCondition, {
|
|
99
|
+
kind: 'dangling-citation';
|
|
100
|
+
}>[];
|
|
101
|
+
readonly danglingDispositionCitations: readonly Extract<IngestCondition, {
|
|
102
|
+
kind: 'dangling-disposition-citation';
|
|
103
|
+
}>[];
|
|
104
|
+
readonly forbiddenInputsNotWithheld: readonly Extract<IngestCondition, {
|
|
105
|
+
kind: 'forbidden-input-not-withheld';
|
|
106
|
+
}>[];
|
|
107
|
+
readonly crossArtifactDisagreements: readonly Extract<IngestCondition, {
|
|
108
|
+
kind: 'cross-artifact-disagreement';
|
|
109
|
+
}>[];
|
|
110
|
+
readonly evaluatorConfigurationAbsent: readonly Extract<IngestCondition, {
|
|
111
|
+
kind: 'evaluator-configuration-absent';
|
|
112
|
+
}>[];
|
|
113
|
+
readonly evaluatorConfigurationDigestMismatches: readonly Extract<IngestCondition, {
|
|
114
|
+
kind: 'evaluator-configuration-digest-mismatch';
|
|
115
|
+
}>[];
|
|
116
|
+
readonly judgeResultsUnscored: readonly Extract<IngestCondition, {
|
|
117
|
+
kind: 'judge-result-unscored';
|
|
118
|
+
}>[];
|
|
119
|
+
/**
|
|
120
|
+
* The two score-computed conditions, pre-rendered like `isolationViolation`
|
|
121
|
+
* rather than structured: `score.ts` computes and renders both itself
|
|
122
|
+
* (neither is an `IngestCondition`), so the ladder only needs to display
|
|
123
|
+
* them.
|
|
124
|
+
*/
|
|
125
|
+
readonly operationIdentifierCollisions: readonly string[];
|
|
126
|
+
readonly trialSetDisagreements: readonly string[];
|
|
127
|
+
};
|
|
128
|
+
export type FindingConfidence = {
|
|
129
|
+
readonly findingId: string;
|
|
130
|
+
readonly confidence: number;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The seven category values, common to both modes; AD-21 is explicit the two
|
|
134
|
+
* verdicts "never share a field" beyond this. `uncitedDefectFindings` sits in
|
|
135
|
+
* the "coverage condition" category: owed item 5 calls an uncited defect
|
|
136
|
+
* finding, in contract-scoring, "the strongest available evidence of a
|
|
137
|
+
* coverage gap".
|
|
138
|
+
*/
|
|
139
|
+
type AssessmentCommon = {
|
|
140
|
+
readonly outcomeState: OutcomeStateInputs;
|
|
141
|
+
readonly evidenceIntegrity: EvidenceIntegrityInputs;
|
|
142
|
+
readonly evaluatorRecommendation: EvaluatorRecommendation;
|
|
143
|
+
readonly coverageGaps: readonly CoverageGap[];
|
|
144
|
+
/**
|
|
145
|
+
* Owed item 5, per `outcome.ts`'s `uncitedDefectFindingGaps`. Only presence
|
|
146
|
+
* and each entry's `findingId` are read below; the rest is carried for the
|
|
147
|
+
* persisted record.
|
|
148
|
+
*/
|
|
149
|
+
readonly uncitedDefectFindings: readonly UncitedFindingGap[];
|
|
150
|
+
readonly findings: readonly FindingConfidence[];
|
|
151
|
+
readonly confidenceThreshold: number;
|
|
152
|
+
/** AD-21's "remediation state": `Remediation.lineageChain`'s conjunction only, never `Remediation.cap` (AD-12's contract-revision cap). */
|
|
153
|
+
readonly remediationState: LineageChain;
|
|
154
|
+
/** AD-21's "pre-flight state": `PreflightVerdict.passed`. */
|
|
155
|
+
readonly preflightPassed: boolean;
|
|
156
|
+
readonly severityFloor: Severity;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* A literal `mode` discriminant, mirroring `EvidenceArtifact`'s own union, so
|
|
160
|
+
* the two types are nominally distinct rather than structurally compatible in
|
|
161
|
+
* one direction only.
|
|
162
|
+
*/
|
|
163
|
+
export type ProductionAssessment = AssessmentCommon & {
|
|
164
|
+
readonly mode: 'production';
|
|
165
|
+
};
|
|
166
|
+
export type ContractAssessment = AssessmentCommon & {
|
|
167
|
+
readonly mode: 'contract-scoring';
|
|
168
|
+
readonly systemRecommendationRecorded: EvaluatorRecommendation;
|
|
169
|
+
readonly systemRecommendationNote: string | null;
|
|
170
|
+
};
|
|
171
|
+
export type LadderResolution = {
|
|
172
|
+
/** `null` is AD-21's Invalid rung: never a `Verdict`, and a run that never becomes a contract verdict. */
|
|
173
|
+
readonly verdict: Verdict | null;
|
|
174
|
+
readonly exitCode: number;
|
|
175
|
+
/** `false` for a CONCERNS whose only firing conditions are the two evidence conditions; `cli/exit-codes.ts`'s `evidenceConditionsOnly` semantics, restated for the ladder to agree with rather than re-derive. */
|
|
176
|
+
readonly strictPromotable: boolean;
|
|
177
|
+
readonly basis: readonly string[];
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* AD-21's exit codes for this rung. Not imported from `src/cli/exit-codes.ts`:
|
|
181
|
+
* `core/` imports only `core/schemas` (AD-1), so the three numbers are
|
|
182
|
+
* repeated here rather than shared. Exported so a test outside `core/` can
|
|
183
|
+
* assert this restatement still agrees with that file's own `EXIT_*`
|
|
184
|
+
* constants and `evidenceConditionsOnly` rule, which neither file can check
|
|
185
|
+
* of itself.
|
|
186
|
+
*/
|
|
187
|
+
export declare const LADDER_EXIT_CODES: {
|
|
188
|
+
readonly invalid: 3;
|
|
189
|
+
readonly FAIL: 2;
|
|
190
|
+
readonly CONCERNS: 0;
|
|
191
|
+
readonly WAIVED: 0;
|
|
192
|
+
readonly PASS: 0;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* One condition this table can independently confirm or deny. `reasons`
|
|
196
|
+
* returns zero or more free-text entries (`evidence-artifact.ts`'s own
|
|
197
|
+
* `verdictBasis` shape: non-empty strings, empty array legal); more than one
|
|
198
|
+
* entry names each affected oracle, gap, or finding separately, matching
|
|
199
|
+
* AD-21's "the record carries every condition that fired".
|
|
200
|
+
*/
|
|
201
|
+
type LadderConditionRow = {
|
|
202
|
+
readonly id: string;
|
|
203
|
+
readonly rung: 'invalid' | 'FAIL' | 'CONCERNS' | 'WAIVED';
|
|
204
|
+
readonly guard: string;
|
|
205
|
+
/** the two evidence conditions AD-21 names as a thinner measurement rather than a system claim; `strictPromotable` reads this. */
|
|
206
|
+
readonly evidenceCondition: boolean;
|
|
207
|
+
readonly reasons: (inputs: AssessmentCommon) => readonly string[];
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Production's ladder: the shared rows plus the two rows that read an
|
|
211
|
+
* ingested evaluator recommendation, which contract-scoring's never does.
|
|
212
|
+
*/
|
|
213
|
+
export declare const PRODUCTION_LADDER: readonly LadderConditionRow[];
|
|
214
|
+
/**
|
|
215
|
+
* Contract-scoring's ladder: FAIL/CONCERNS/WAIVED/PASS mirror production's
|
|
216
|
+
* minus every clause that promotes `evaluatorRecommendation`, per AD-21.
|
|
217
|
+
*/
|
|
218
|
+
export declare const CONTRACT_LADDER: readonly LadderConditionRow[];
|
|
219
|
+
/** AD-21's production-mode ladder: the subject is the system under test. */
|
|
220
|
+
export declare function resolveProductionVerdict(assessment: ProductionAssessment): LadderResolution;
|
|
221
|
+
/**
|
|
222
|
+
* AD-21's contract-scoring ladder: the subject is the contract. No row here
|
|
223
|
+
* reads `evaluatorRecommendation`; `systemRecommendationRecorded` and
|
|
224
|
+
* `systemRecommendationNote` are carried on the input for symmetry with
|
|
225
|
+
* `EvidenceArtifact`'s own contract-scoring branch and are read by nothing in
|
|
226
|
+
* this module.
|
|
227
|
+
*/
|
|
228
|
+
export declare function resolveContractVerdict(assessment: ContractAssessment): LadderResolution;
|
|
229
|
+
export {};
|