eval-quality 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +355 -0
- package/corpus/dev/README.md +36 -0
- package/corpus/dev/compile-seal-example/brief.json +1 -0
- package/corpus/dev/compile-seal-example/contract.json +1 -0
- package/corpus/dev/contracts/absent-collection-locations.json +1 -0
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -0
- package/corpus/dev/contracts/absent-success-indicator.json +1 -0
- package/corpus/dev/contracts/empty-channel-roles.json +1 -0
- package/corpus/dev/contracts/empty-collection-locations.json +1 -0
- package/corpus/dev/contracts/empty-request-shapes.json +1 -0
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -0
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -0
- package/corpus/dev/contracts/no-operation-inventory.json +1 -0
- package/corpus/dev/contracts/no-read-back-relation.json +1 -0
- package/corpus/dev/contracts/no-state-change-marker.json +1 -0
- package/corpus/dev/contracts/no-type-violating-step.json +1 -0
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -0
- package/corpus/dev/contracts/satisfied-declarations.json +1 -0
- package/corpus/dev/contracts/single-required-response-key.json +1 -0
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -0
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -0
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -0
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -0
- package/corpus/dev/index.json +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/local-corpus-adapter.d.ts +7 -0
- package/dist/adapters/local-corpus-adapter.js +78 -0
- package/dist/adapters/node-file-system-adapter.d.ts +7 -0
- package/dist/adapters/node-file-system-adapter.js +39 -0
- package/dist/adapters/port-boundary.d.ts +18 -0
- package/dist/adapters/port-boundary.js +83 -0
- package/dist/adapters/system-clock-adapter.d.ts +10 -0
- package/dist/adapters/system-clock-adapter.js +18 -0
- package/dist/application/compile.d.ts +4 -0
- package/dist/application/compile.js +24 -0
- package/dist/application/diagnostics.d.ts +22 -0
- package/dist/application/diagnostics.js +18 -0
- package/dist/application/index.d.ts +23 -0
- package/dist/application/index.js +17 -0
- package/dist/application/invoke-port.d.ts +2 -0
- package/dist/application/invoke-port.js +45 -0
- package/dist/application/preflight.d.ts +27 -0
- package/dist/application/preflight.js +131 -0
- package/dist/application/seal.d.ts +4 -0
- package/dist/application/seal.js +28 -0
- package/dist/application/serialize.d.ts +6 -0
- package/dist/application/serialize.js +9 -0
- package/dist/cli/arguments.d.ts +28 -0
- package/dist/cli/arguments.js +167 -0
- package/dist/cli/exit-codes.d.ts +36 -0
- package/dist/cli/exit-codes.js +39 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +143 -0
- package/dist/cli/render.d.ts +26 -0
- package/dist/cli/render.js +53 -0
- package/dist/cli/run.d.ts +41 -0
- package/dist/cli/run.js +203 -0
- package/dist/core/canonical/canonicalize.d.ts +1 -0
- package/dist/core/canonical/canonicalize.js +113 -0
- package/dist/core/canonical/digest.d.ts +7 -0
- package/dist/core/canonical/digest.js +81 -0
- package/dist/core/canonical/scan-json.d.ts +1 -0
- package/dist/core/canonical/scan-json.js +298 -0
- package/dist/core/canonical/value-domain.d.ts +4 -0
- package/dist/core/canonical/value-domain.js +113 -0
- package/dist/core/compile/compile.d.ts +24 -0
- package/dist/core/compile/compile.js +67 -0
- package/dist/core/compile/declarations.d.ts +5 -0
- package/dist/core/compile/declarations.js +22 -0
- package/dist/core/compile/expression-legality.d.ts +14 -0
- package/dist/core/compile/expression-legality.js +346 -0
- package/dist/core/compile/forbidden-inputs.d.ts +5 -0
- package/dist/core/compile/forbidden-inputs.js +19 -0
- package/dist/core/compile/interface-inventory.d.ts +10 -0
- package/dist/core/compile/interface-inventory.js +55 -0
- package/dist/core/compile/oracle-alignment.d.ts +14 -0
- package/dist/core/compile/oracle-alignment.js +93 -0
- package/dist/core/compile/reachability.d.ts +17 -0
- package/dist/core/compile/reachability.js +177 -0
- package/dist/core/compile/rubrics.d.ts +55 -0
- package/dist/core/compile/rubrics.js +198 -0
- package/dist/core/compile/scripting-bound.d.ts +5 -0
- package/dist/core/compile/scripting-bound.js +151 -0
- package/dist/core/compile/sensitivity-witness.d.ts +73 -0
- package/dist/core/compile/sensitivity-witness.js +232 -0
- package/dist/core/compile/waivers.d.ts +3 -0
- package/dist/core/compile/waivers.js +21 -0
- package/dist/core/coverage/coverage.d.ts +18 -0
- package/dist/core/coverage/coverage.js +47 -0
- package/dist/core/coverage/relevance.d.ts +75 -0
- package/dist/core/coverage/relevance.js +186 -0
- package/dist/core/coverage/rules.d.ts +7 -0
- package/dist/core/coverage/rules.js +18 -0
- package/dist/core/coverage/satisfaction.d.ts +107 -0
- package/dist/core/coverage/satisfaction.js +415 -0
- package/dist/core/coverage/table.d.ts +26 -0
- package/dist/core/coverage/table.js +222 -0
- package/dist/core/declared-inputs.d.ts +16 -0
- package/dist/core/declared-inputs.js +18 -0
- package/dist/core/evaluate/evidence-resolution.d.ts +54 -0
- package/dist/core/evaluate/evidence-resolution.js +153 -0
- package/dist/core/evaluate/operators.d.ts +87 -0
- package/dist/core/evaluate/operators.js +350 -0
- package/dist/core/evaluate/resolution.d.ts +40 -0
- package/dist/core/evaluate/resolution.js +336 -0
- package/dist/core/evaluate/resolved-value.d.ts +9 -0
- package/dist/core/evaluate/resolved-value.js +6 -0
- package/dist/core/failure-codes.d.ts +18 -0
- package/dist/core/failure-codes.js +50 -0
- package/dist/core/lineage/chain.d.ts +49 -0
- package/dist/core/lineage/chain.js +210 -0
- package/dist/core/lineage/freeze.d.ts +12 -0
- package/dist/core/lineage/freeze.js +35 -0
- package/dist/core/lineage/stage-table.d.ts +43 -0
- package/dist/core/lineage/stage-table.js +106 -0
- package/dist/core/preflight/plan.d.ts +67 -0
- package/dist/core/preflight/plan.js +313 -0
- package/dist/core/preflight/projection.d.ts +40 -0
- package/dist/core/preflight/projection.js +96 -0
- package/dist/core/preflight/reduce.d.ts +8 -0
- package/dist/core/preflight/reduce.js +180 -0
- package/dist/core/preflight/witness-evidence.d.ts +42 -0
- package/dist/core/preflight/witness-evidence.js +88 -0
- package/dist/core/probe/target-policy.d.ts +63 -0
- package/dist/core/probe/target-policy.js +328 -0
- package/dist/core/schemas/artifact-reference.d.ts +19 -0
- package/dist/core/schemas/artifact-reference.js +30 -0
- package/dist/core/schemas/artifact.d.ts +1153 -0
- package/dist/core/schemas/artifact.js +84 -0
- package/dist/core/schemas/constraint-ledger.d.ts +57 -0
- package/dist/core/schemas/constraint-ledger.js +143 -0
- package/dist/core/schemas/eval-contract.d.ts +402 -0
- package/dist/core/schemas/eval-contract.js +147 -0
- package/dist/core/schemas/evaluator-configuration.d.ts +37 -0
- package/dist/core/schemas/evaluator-configuration.js +43 -0
- package/dist/core/schemas/evidence-artifact.d.ts +471 -0
- package/dist/core/schemas/evidence-artifact.js +232 -0
- package/dist/core/schemas/expression.d.ts +197 -0
- package/dist/core/schemas/expression.js +284 -0
- package/dist/core/schemas/faults.d.ts +9 -0
- package/dist/core/schemas/faults.js +32 -0
- package/dist/core/schemas/interface.d.ts +417 -0
- package/dist/core/schemas/interface.js +115 -0
- package/dist/core/schemas/isolation-manifest.d.ts +136 -0
- package/dist/core/schemas/isolation-manifest.js +92 -0
- package/dist/core/schemas/lineage.d.ts +21 -0
- package/dist/core/schemas/lineage.js +28 -0
- package/dist/core/schemas/oracle.d.ts +72 -0
- package/dist/core/schemas/oracle.js +35 -0
- package/dist/core/schemas/plan.d.ts +115 -0
- package/dist/core/schemas/plan.js +58 -0
- package/dist/core/schemas/pointer.d.ts +63 -0
- package/dist/core/schemas/pointer.js +94 -0
- package/dist/core/schemas/port-messages.d.ts +103 -0
- package/dist/core/schemas/port-messages.js +90 -0
- package/dist/core/schemas/preflight-verdict.d.ts +60 -0
- package/dist/core/schemas/preflight-verdict.js +38 -0
- package/dist/core/schemas/primitives.d.ts +78 -0
- package/dist/core/schemas/primitives.js +121 -0
- package/dist/core/schemas/private-artifact-manifest.d.ts +56 -0
- package/dist/core/schemas/private-artifact-manifest.js +46 -0
- package/dist/core/schemas/probe-body.d.ts +25 -0
- package/dist/core/schemas/probe-body.js +23 -0
- package/dist/core/schemas/probe-policy.d.ts +76 -0
- package/dist/core/schemas/probe-policy.js +33 -0
- package/dist/core/schemas/probe.d.ts +189 -0
- package/dist/core/schemas/probe.js +82 -0
- package/dist/core/schemas/publish.d.ts +30 -0
- package/dist/core/schemas/publish.js +135 -0
- package/dist/core/schemas/reference-set.d.ts +18 -0
- package/dist/core/schemas/reference-set.js +27 -0
- package/dist/core/schemas/rubric.d.ts +62 -0
- package/dist/core/schemas/rubric.js +68 -0
- package/dist/core/schemas/scoring-policy.d.ts +28 -0
- package/dist/core/schemas/scoring-policy.js +51 -0
- package/dist/core/schemas/sealed-evaluator-brief.d.ts +80 -0
- package/dist/core/schemas/sealed-evaluator-brief.js +59 -0
- package/dist/core/schemas/sealed-run-record.d.ts +382 -0
- package/dist/core/schemas/sealed-run-record.js +215 -0
- package/dist/core/schemas/sensitivity-witness.d.ts +138 -0
- package/dist/core/schemas/sensitivity-witness.js +86 -0
- package/dist/core/schemas/verdict.d.ts +25 -0
- package/dist/core/schemas/verdict.js +20 -0
- package/dist/core/schemas/waiver.d.ts +17 -0
- package/dist/core/schemas/waiver.js +24 -0
- package/dist/core/seal/derived-reference.d.ts +36 -0
- package/dist/core/seal/derived-reference.js +383 -0
- package/dist/core/seal/direction-prose.d.ts +13 -0
- package/dist/core/seal/direction-prose.js +120 -0
- package/dist/core/seal/plan-index.d.ts +63 -0
- package/dist/core/seal/plan-index.js +155 -0
- package/dist/core/seal/scripting-audit.d.ts +15 -0
- package/dist/core/seal/scripting-audit.js +51 -0
- package/dist/core/seal/seal.d.ts +18 -0
- package/dist/core/seal/seal.js +155 -0
- package/dist/core/stage-contracts.d.ts +30 -0
- package/dist/core/stage-contracts.js +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +22 -0
- package/dist/ports/clock-port.d.ts +13 -0
- package/dist/ports/clock-port.js +7 -0
- package/dist/ports/corpus-port.d.ts +16 -0
- package/dist/ports/corpus-port.js +7 -0
- package/dist/ports/environment-probe-port.d.ts +76 -0
- package/dist/ports/environment-probe-port.js +32 -0
- package/dist/ports/file-system-port.d.ts +27 -0
- package/dist/ports/file-system-port.js +11 -0
- package/dist/ports/port.d.ts +33 -0
- package/dist/ports/port.js +14 -0
- package/dist/testing/conformance.d.ts +88 -0
- package/dist/testing/conformance.js +281 -0
- package/dist/testing/index.d.ts +26 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/probe-conformance.d.ts +45 -0
- package/dist/testing/probe-conformance.js +186 -0
- package/package.json +130 -0
- package/schemas/artifact-reference.schema.json +64 -0
- package/schemas/eval-contract.schema.json +2145 -0
- package/schemas/evaluator-configuration.schema.json +199 -0
- package/schemas/evidence-artifact.schema.json +1229 -0
- package/schemas/isolation-manifest.schema.json +394 -0
- package/schemas/preflight-verdict.schema.json +114 -0
- package/schemas/private-artifact-manifest.schema.json +103 -0
- package/schemas/probe.schema.json +1151 -0
- package/schemas/rubric.schema.json +148 -0
- package/schemas/scoring-policy.schema.json +90 -0
- package/schemas/sealed-evaluator-brief.schema.json +268 -0
- package/schemas/sealed-run-record.schema.json +875 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-10's three declaration-side witness checks: that every input-bearing
|
|
3
|
+
* operation declares one, that a declared one is shaped so the plan can
|
|
4
|
+
* execute it, and that its leg identifiers are distinct from each other and
|
|
5
|
+
* from every interaction-plan step.
|
|
6
|
+
*
|
|
7
|
+
* No new AD-5 code is minted. The registry is closed at twenty-one and
|
|
8
|
+
* `check:ad5-registry` pins it against the spine, so each defect below takes the
|
|
9
|
+
* code that already names it. Two rows stretch that reading: a leg-id equality
|
|
10
|
+
* and a leg-id/step-id collision are identifier collisions, and
|
|
11
|
+
* `malformed-operator-expression` is the closest available code because the
|
|
12
|
+
* relation is illegal in its position once its operands cannot be told apart.
|
|
13
|
+
*/
|
|
14
|
+
import { declaresNoRequestKeys } from '../declared-inputs.js';
|
|
15
|
+
import { StructuralFailure } from '../failure-codes.js';
|
|
16
|
+
import { TRANSPORT_CHANNELS, } from '../schemas/pointer.js';
|
|
17
|
+
import { parseEvidenceTarget } from '../seal/plan-index.js';
|
|
18
|
+
export { declaresNoRequestKeys };
|
|
19
|
+
/**
|
|
20
|
+
* The keys one set of witness inputs supplies on one channel. A body that is
|
|
21
|
+
* absent, or JSON that is not an object, supplies no keys: the channel is
|
|
22
|
+
* declared as a keyed shape, so such a leg omits every required key rather
|
|
23
|
+
* than being exempt from the comparison.
|
|
24
|
+
*/
|
|
25
|
+
export function suppliedKeys(inputs, channel) {
|
|
26
|
+
if (channel !== 'body')
|
|
27
|
+
return Object.keys(inputs[channel]);
|
|
28
|
+
const { body } = inputs;
|
|
29
|
+
if (body.kind !== 'json')
|
|
30
|
+
return [];
|
|
31
|
+
const { value } = body;
|
|
32
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
33
|
+
return [];
|
|
34
|
+
return Object.keys(value);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Every channel of one set of witness inputs against the operation that will
|
|
38
|
+
* receive them. All four, not only the differential channel: `planPreflight`
|
|
39
|
+
* copies all four onto the `ProbeRequest` and the port sends them, so a value
|
|
40
|
+
* on an unselected channel is as much an outbound value as one on the selected
|
|
41
|
+
* channel.
|
|
42
|
+
*
|
|
43
|
+
* The two directions have different standing, and the difference matters
|
|
44
|
+
* enough to name, because the next reader will diff this against
|
|
45
|
+
* `checkUndeclaredMandatoryInput` and find the two disagreeing.
|
|
46
|
+
*
|
|
47
|
+
* The permitted direction is that function's rule, carried over: it loops the
|
|
48
|
+
* same four channels and rejects a key the operation declares in neither list.
|
|
49
|
+
* That is what makes `WitnessInputs`'s AD-18 promise about `header` more than a
|
|
50
|
+
* comment.
|
|
51
|
+
*
|
|
52
|
+
* The required direction is new here, because the two shapes differ. An
|
|
53
|
+
* `InputBindingChannel` is nullable, and `null` means "this step binds nothing in
|
|
54
|
+
* this channel", so `checkUndeclaredMandatoryInput` skips an unbound channel and
|
|
55
|
+
* asks nothing about required keys. `WitnessInputs` has all four channels as
|
|
56
|
+
* concrete values, so a leg omitting a required key is a request the port cannot
|
|
57
|
+
* issue. The cost is that a required header forces a literal value into the
|
|
58
|
+
* contract artifact, the surface AD-18 governs; a placeholder satisfies this
|
|
59
|
+
* check, and what pre-flight probes is a fixture.
|
|
60
|
+
*/
|
|
61
|
+
export function checkInputsAgainstShape(inputs, operation, owner, artifactPath) {
|
|
62
|
+
for (const channel of TRANSPORT_CHANNELS) {
|
|
63
|
+
const shape = operation.requestShape[channel];
|
|
64
|
+
const supplied = suppliedKeys(inputs, channel);
|
|
65
|
+
for (const key of shape.requiredKeys) {
|
|
66
|
+
if (supplied.includes(key))
|
|
67
|
+
continue;
|
|
68
|
+
throw new StructuralFailure('undeclared-mandatory-input', artifactPath, `${owner} omits "${key}", which operation "${operation.operationId}" declares required in its ${channel} channel (AD-10)`);
|
|
69
|
+
}
|
|
70
|
+
const permitted = new Set([...shape.requiredKeys, ...shape.permittedKeys]);
|
|
71
|
+
for (const key of supplied) {
|
|
72
|
+
if (permitted.has(key))
|
|
73
|
+
continue;
|
|
74
|
+
throw new StructuralFailure('undeclared-mandatory-input', artifactPath, `${owner} supplies "${key}", which operation "${operation.operationId}" declares in neither requiredKeys nor permittedKeys of its ${channel} channel (AD-10, AD-18)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const operationPath = (interfaceIndex, operationIndex) => `EvalContract.permittedInterfaces[${interfaceIndex}].operations[${operationIndex}]`;
|
|
79
|
+
function forEachWitness(contract, visit) {
|
|
80
|
+
contract.permittedInterfaces.forEach((iface, interfaceIndex) => {
|
|
81
|
+
iface.operations.forEach((operation, operationIndex) => {
|
|
82
|
+
const witness = operation.sensitivityWitness;
|
|
83
|
+
if (witness === null)
|
|
84
|
+
return;
|
|
85
|
+
visit(witness, operation, operationPath(interfaceIndex, operationIndex));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Strict-gated, alongside `checkUndeclaredMandatoryInput`, whose code this
|
|
91
|
+
* shares: `compile.ts` already gates that code behind `options.strict`, and one
|
|
92
|
+
* code with two gating regimes would be worse than one code with two
|
|
93
|
+
* conditions.
|
|
94
|
+
*/
|
|
95
|
+
export function checkSensitivityWitnessDeclared(contract) {
|
|
96
|
+
contract.permittedInterfaces.forEach((iface, interfaceIndex) => {
|
|
97
|
+
iface.operations.forEach((operation, operationIndex) => {
|
|
98
|
+
const path = operationPath(interfaceIndex, operationIndex);
|
|
99
|
+
const witness = operation.sensitivityWitness;
|
|
100
|
+
if (witness === null) {
|
|
101
|
+
if (declaresNoRequestKeys(operation))
|
|
102
|
+
return;
|
|
103
|
+
throw new StructuralFailure('undeclared-mandatory-input', path, `operation "${operation.operationId}" declares request keys but no sensitivity witness; only an operation declaring no keys in any channel is exempt (AD-10)`);
|
|
104
|
+
}
|
|
105
|
+
witness.legs.forEach((leg, legIndex) => {
|
|
106
|
+
checkInputsAgainstShape(leg.inputs, operation, `leg "${leg.legId}"`, `${path}.sensitivityWitness.legs[${legIndex}]`);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** every interaction-rooted step id an expression addresses, in walk order. */
|
|
112
|
+
function addressedStepIds(expression) {
|
|
113
|
+
const found = new Set();
|
|
114
|
+
const visitOperand = (operand) => {
|
|
115
|
+
const { pointer } = operand;
|
|
116
|
+
if (pointer === undefined || pointer.startsWith('@'))
|
|
117
|
+
return;
|
|
118
|
+
found.add(parseEvidenceTarget(pointer).stepId);
|
|
119
|
+
};
|
|
120
|
+
const walk = (node) => {
|
|
121
|
+
switch (node.op) {
|
|
122
|
+
case 'not':
|
|
123
|
+
case 'all':
|
|
124
|
+
case 'any':
|
|
125
|
+
node.operands.forEach(walk);
|
|
126
|
+
return;
|
|
127
|
+
case 'for-all':
|
|
128
|
+
case 'for-any':
|
|
129
|
+
visitOperand(node.collection);
|
|
130
|
+
walk(node.predicate);
|
|
131
|
+
return;
|
|
132
|
+
default:
|
|
133
|
+
for (const operand of node.operands)
|
|
134
|
+
visitOperand(operand);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
walk(expression);
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
/** the channel AD-10 selects for an operation, by its state-change marker. */
|
|
141
|
+
const legalChannels = (operation) => operation.stateChangeMarker ? ['body'] : ['path', 'query'];
|
|
142
|
+
/**
|
|
143
|
+
* The shape rules: the differential channel agrees with the state-change
|
|
144
|
+
* marker, the relation reads both legs and nothing else, and a declared
|
|
145
|
+
* fixture reset names a mutating operation the contract declares. Unconditional,
|
|
146
|
+
* since none of these fires `undeclared-mandatory-input`.
|
|
147
|
+
*/
|
|
148
|
+
export function checkWitnessLegality(contract) {
|
|
149
|
+
forEachWitness(contract, (witness, operation, path) => {
|
|
150
|
+
const legal = legalChannels(operation);
|
|
151
|
+
if (!legal.includes(witness.channel)) {
|
|
152
|
+
throw new StructuralFailure('malformed-operator-expression', `${path}.sensitivityWitness`, `channel "${witness.channel}" contradicts stateChangeMarker ${operation.stateChangeMarker} on operation "${operation.operationId}"; AD-10 selects ${legal.map((name) => `"${name}"`).join(' or ')}`);
|
|
153
|
+
}
|
|
154
|
+
// AD-10's predicate is a differential. Two legs supplying the same values on
|
|
155
|
+
// the selected channel establish nothing, and at run time that surfaces as a
|
|
156
|
+
// `failed` check pointing at the fixture, when the defect is in the
|
|
157
|
+
// declaration.
|
|
158
|
+
const [first, second] = witness.legs;
|
|
159
|
+
if (first !== undefined &&
|
|
160
|
+
second !== undefined &&
|
|
161
|
+
JSON.stringify(first.inputs[witness.channel]) ===
|
|
162
|
+
JSON.stringify(second.inputs[witness.channel])) {
|
|
163
|
+
throw new StructuralFailure('malformed-operator-expression', `${path}.sensitivityWitness.legs`, `both legs of witness "${witness.witnessId}" supply the same ${witness.channel} value, so the pair is not a differential (AD-10)`);
|
|
164
|
+
}
|
|
165
|
+
const legIds = witness.legs.map((leg) => leg.legId);
|
|
166
|
+
const addressed = addressedStepIds(witness.relation);
|
|
167
|
+
// Reported before the coverage rules: an unknown root is the more
|
|
168
|
+
// specific diagnosis, and a relation that reads a third step is
|
|
169
|
+
// unreachable evidence whether or not it also reads both legs.
|
|
170
|
+
for (const stepId of addressed) {
|
|
171
|
+
if (legIds.includes(stepId))
|
|
172
|
+
continue;
|
|
173
|
+
throw new StructuralFailure('unreachable-check-evidence', `${path}.sensitivityWitness.relation`, `the relation addresses "${stepId}", which is neither leg of witness "${witness.witnessId}" (AD-10)`);
|
|
174
|
+
}
|
|
175
|
+
const covered = legIds.filter((legId) => addressed.has(legId));
|
|
176
|
+
if (covered.length !== legIds.length) {
|
|
177
|
+
throw new StructuralFailure('malformed-operator-expression', `${path}.sensitivityWitness.relation`, `the relation addresses ${covered.length} of the two legs of witness "${witness.witnessId}"; a differential that reads one leg establishes no sensitivity (AD-10)`);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
const reset = contract.fixtureReset;
|
|
181
|
+
if (reset === null)
|
|
182
|
+
return;
|
|
183
|
+
const iface = contract.permittedInterfaces.find((candidate) => candidate.logicalId === reset.interfaceId);
|
|
184
|
+
const operation = iface?.operations.find((candidate) => candidate.operationId === reset.operationId);
|
|
185
|
+
if (operation === undefined) {
|
|
186
|
+
throw new StructuralFailure('unreachable-check-evidence', 'EvalContract.fixtureReset', `the fixture reset names operation "${reset.operationId}" on interface "${reset.interfaceId}", which the contract does not declare (AD-10)`);
|
|
187
|
+
}
|
|
188
|
+
if (!operation.stateChangeMarker) {
|
|
189
|
+
throw new StructuralFailure('malformed-operator-expression', 'EvalContract.fixtureReset', `the fixture reset names operation "${reset.operationId}", whose stateChangeMarker is false; an operation that changes no state resets nothing (AD-10)`);
|
|
190
|
+
}
|
|
191
|
+
// The reset is one more leg through the same port, so its inputs answer to
|
|
192
|
+
// the same request shape every witness leg does.
|
|
193
|
+
checkInputsAgainstShape(reset.inputs, operation, 'the fixture reset', 'EvalContract.fixtureReset.inputs');
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Leg identifiers share one namespace with interaction-plan step identifiers,
|
|
197
|
+
* because a witness relation is an ordinary AD-4 expression and addresses a leg
|
|
198
|
+
* as `/interactions/{legId}/…`. A collision would make a relation pointer
|
|
199
|
+
* resolve against the wrong observation.
|
|
200
|
+
*/
|
|
201
|
+
export function checkWitnessLegIdentifiers(contract) {
|
|
202
|
+
const stepIds = new Set(contract.interactionPlan.map((step) => step.stepId));
|
|
203
|
+
const seen = new Map();
|
|
204
|
+
const claim = (legId, owner, path) => {
|
|
205
|
+
if (stepIds.has(legId)) {
|
|
206
|
+
throw new StructuralFailure('malformed-operator-expression', path, `leg id "${legId}" collides with an interaction-plan step id; the two share one pointer namespace (AD-10, AD-26)`);
|
|
207
|
+
}
|
|
208
|
+
const previous = seen.get(legId);
|
|
209
|
+
if (previous !== undefined) {
|
|
210
|
+
throw new StructuralFailure('malformed-operator-expression', path, `leg id "${legId}" is already claimed by ${previous}; leg ids are unique across the contract (AD-10)`);
|
|
211
|
+
}
|
|
212
|
+
seen.set(legId, owner);
|
|
213
|
+
};
|
|
214
|
+
forEachWitness(contract, (witness, _operation, path) => {
|
|
215
|
+
const [first, second] = witness.legs;
|
|
216
|
+
if (first !== undefined &&
|
|
217
|
+
second !== undefined &&
|
|
218
|
+
first.legId === second.legId) {
|
|
219
|
+
throw new StructuralFailure('malformed-operator-expression', `${path}.sensitivityWitness.legs`, `both legs of witness "${witness.witnessId}" carry the leg id "${first.legId}", so the relation cannot tell them apart (AD-10)`);
|
|
220
|
+
}
|
|
221
|
+
witness.legs.forEach((leg, legIndex) => {
|
|
222
|
+
claim(leg.legId, `witness "${witness.witnessId}"`, `${path}.sensitivityWitness.legs[${legIndex}].legId`);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
// The fixture reset is one more leg through the same port, so its id sits in
|
|
226
|
+
// the same namespace. AD-10 names no defect for the collision; the check is
|
|
227
|
+
// added here because two legs sharing a `probeId` would make the reducer read
|
|
228
|
+
// one leg's observation for the other.
|
|
229
|
+
const reset = contract.fixtureReset;
|
|
230
|
+
if (reset !== null)
|
|
231
|
+
claim(reset.legId, 'the fixture reset', 'EvalContract.fixtureReset.legId');
|
|
232
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks each waiver for its rule, rationale, approval, and expiry.
|
|
3
|
+
* A condition is optional.
|
|
4
|
+
*/
|
|
5
|
+
import { StructuralFailure } from '../failure-codes.js';
|
|
6
|
+
const REQUIRED_WAIVER_PARTS = [
|
|
7
|
+
'rule',
|
|
8
|
+
'rationale',
|
|
9
|
+
'approval',
|
|
10
|
+
'expiresAt',
|
|
11
|
+
];
|
|
12
|
+
/** Reports the first missing required field. */
|
|
13
|
+
export function checkWaiverCompleteness(contract) {
|
|
14
|
+
for (const waiver of contract.waivers) {
|
|
15
|
+
for (const part of REQUIRED_WAIVER_PARTS) {
|
|
16
|
+
if (waiver[part] === null) {
|
|
17
|
+
throw new StructuralFailure('waiver-incomplete', `EvalContract.waivers[id=${waiver.id}].${part}`, `a waiver requires ${part}; AD-5 requires the named rule, an explicit rationale, the recorded approval, and an RFC 3339 expiry`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-31 coverage gaps: relevance fired and satisfaction failed. A satisfied
|
|
3
|
+
* rule gets no record, since AD-21 reads `coverageGaps` as the gap list.
|
|
4
|
+
* Nothing throws; AD-5 emits the artifact with the gap recorded, and only a
|
|
5
|
+
* structural error blocks compilation.
|
|
6
|
+
*/
|
|
7
|
+
import { type EvalContract, type Severity } from '../schemas/eval-contract.ts';
|
|
8
|
+
import type { CoverageGap } from '../schemas/evidence-artifact.ts';
|
|
9
|
+
export type CoverageGapRecord = CoverageGap;
|
|
10
|
+
/**
|
|
11
|
+
* Highest declared behaviour severity. `behaviors` carries `.min(1)`, so this
|
|
12
|
+
* is total. The maximum, because the predicates are contract-level and
|
|
13
|
+
* oracle-blind: a lower reduction lets one trivial behaviour push every gap
|
|
14
|
+
* under AD-21's severity floor.
|
|
15
|
+
*/
|
|
16
|
+
export declare function coverageSeverity(contract: EvalContract): Severity;
|
|
17
|
+
/** One record per gap, in `DISCIPLINE_RULES` order. Both arrays share it, so the pairing is positional. */
|
|
18
|
+
export declare function evaluateCoverage(contract: EvalContract): readonly CoverageGapRecord[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-31 coverage gaps: relevance fired and satisfaction failed. A satisfied
|
|
3
|
+
* rule gets no record, since AD-21 reads `coverageGaps` as the gap list.
|
|
4
|
+
* Nothing throws; AD-5 emits the artifact with the gap recorded, and only a
|
|
5
|
+
* structural error blocks compilation.
|
|
6
|
+
*/
|
|
7
|
+
import { SEVERITY_LEVELS, } from '../schemas/eval-contract.js';
|
|
8
|
+
import { evaluateRelevance } from './relevance.js';
|
|
9
|
+
import { evaluateSatisfaction } from './satisfaction.js';
|
|
10
|
+
/**
|
|
11
|
+
* Highest declared behaviour severity. `behaviors` carries `.min(1)`, so this
|
|
12
|
+
* is total. The maximum, because the predicates are contract-level and
|
|
13
|
+
* oracle-blind: a lower reduction lets one trivial behaviour push every gap
|
|
14
|
+
* under AD-21's severity floor.
|
|
15
|
+
*/
|
|
16
|
+
export function coverageSeverity(contract) {
|
|
17
|
+
let rank = 0;
|
|
18
|
+
for (const behavior of contract.behaviors) {
|
|
19
|
+
rank = Math.max(rank, SEVERITY_LEVELS.indexOf(behavior.severity));
|
|
20
|
+
}
|
|
21
|
+
return SEVERITY_LEVELS[rank];
|
|
22
|
+
}
|
|
23
|
+
/** One record per gap, in `DISCIPLINE_RULES` order. Both arrays share it, so the pairing is positional. */
|
|
24
|
+
export function evaluateCoverage(contract) {
|
|
25
|
+
const relevance = evaluateRelevance(contract);
|
|
26
|
+
const satisfaction = evaluateSatisfaction(contract);
|
|
27
|
+
const severity = coverageSeverity(contract);
|
|
28
|
+
const gaps = [];
|
|
29
|
+
// `!relevant.relevant` is unfalsifiable today: a rule relevant for no site
|
|
30
|
+
// answers satisfied vacuously, which fixture 168 pins over 23 contracts. It
|
|
31
|
+
// stays as the conjunction AD-31 states, so a predicate pair that broke the
|
|
32
|
+
// invariant cannot record a gap for a rule that never fired.
|
|
33
|
+
for (const [index, relevant] of relevance.entries()) {
|
|
34
|
+
const satisfied = satisfaction[index];
|
|
35
|
+
if (satisfied === undefined || !relevant.relevant || satisfied.satisfied) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
gaps.push({
|
|
39
|
+
rule: relevant.rule,
|
|
40
|
+
relevancePredicate: relevant.predicate,
|
|
41
|
+
satisfactionPredicate: satisfied.predicate,
|
|
42
|
+
satisfied: false,
|
|
43
|
+
severity,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return gaps;
|
|
47
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-31's seven relevance predicates: decision procedures over AD-19's
|
|
3
|
+
* declarations. They read no run record, no probe, no outcome, no oracle.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here throws. A coverage gap is recorded and the artifact still ships
|
|
6
|
+
* (AD-5), so there is no failure code. Failing closed means answering
|
|
7
|
+
* `relevant` on an absent declaration.
|
|
8
|
+
*/
|
|
9
|
+
import type { EvalContract } from '../schemas/eval-contract.ts';
|
|
10
|
+
import { type DisciplineRule } from './rules.ts';
|
|
11
|
+
export type RelevanceVerdict = {
|
|
12
|
+
readonly rule: DisciplineRule;
|
|
13
|
+
readonly predicate: string;
|
|
14
|
+
readonly relevant: boolean;
|
|
15
|
+
/** which declaration decided it, so a gap is diagnosable. */
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
};
|
|
18
|
+
/** A contract declaring no operation leaves six of the rules nothing to read. */
|
|
19
|
+
export declare const NO_OPERATION = "the contract declares no operation, so the declaration this rule reads is absent";
|
|
20
|
+
/**
|
|
21
|
+
* Rule 1: `successIndicator` declared, and some other pointer carrying a
|
|
22
|
+
* channel role. An operation nominating no indicator is relevant, since a
|
|
23
|
+
* nullable pointer has two spellings and `null` is the absent one.
|
|
24
|
+
* `channelRoles: {}` answers the second conjunct.
|
|
25
|
+
*/
|
|
26
|
+
export declare function successIndicatorSeparationRelevance(contract: EvalContract): RelevanceVerdict;
|
|
27
|
+
/**
|
|
28
|
+
* Rule 2: the descriptor declares more than one pointer, counted over distinct
|
|
29
|
+
* `requiredKeys`. That is the set rule 2's satisfaction denominator reads, and
|
|
30
|
+
* AD-20 keeps permitted keys out of coverage. Distinct, because `requiredKeys`
|
|
31
|
+
* carries no uniqueness constraint and one key repeated is one pointer.
|
|
32
|
+
*/
|
|
33
|
+
export declare function wholeBodyRelevance(contract: EvalContract): RelevanceVerdict;
|
|
34
|
+
/**
|
|
35
|
+
* Rule 3: some operation declares a request key on any of AD-19's four
|
|
36
|
+
* transport channels. The site is the whole channel triple. A key with no
|
|
37
|
+
* `types` entry has an absent type, a key typed `null` has AD-31's
|
|
38
|
+
* indeterminate one, and both are relevant, so declaring an input and leaving
|
|
39
|
+
* it untyped buys no irrelevance.
|
|
40
|
+
*/
|
|
41
|
+
export declare function malformedInputRelevance(contract: EvalContract): RelevanceVerdict;
|
|
42
|
+
/**
|
|
43
|
+
* Rule 4: the descriptor declares at least one collection location. `null` is
|
|
44
|
+
* the absent state and is relevant; `[]` is the explicit empty answer.
|
|
45
|
+
*/
|
|
46
|
+
export declare function perRecordRelevance(contract: EvalContract): RelevanceVerdict;
|
|
47
|
+
/**
|
|
48
|
+
* Rule 5: a sibling group over operations or parameters is non-empty. The one
|
|
49
|
+
* contract-level rule of the seven. A group of one cannot exist, since
|
|
50
|
+
* `SIBLING_GROUP_MINIMUM` is two.
|
|
51
|
+
*/
|
|
52
|
+
export declare function siblingCrossCheckRelevance(contract: EvalContract): RelevanceVerdict;
|
|
53
|
+
/**
|
|
54
|
+
* Rule 6: a declared collection location names a reference set. The site is
|
|
55
|
+
* `collectionLocations` alone, so an explicitly empty list stays an answer. A
|
|
56
|
+
* location naming an identifier the contract does not declare still names one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function omissionAndCompletenessRelevance(contract: EvalContract): RelevanceVerdict;
|
|
59
|
+
/**
|
|
60
|
+
* Rule 7: some operation declares `stateChangeMarker: true`. The one rule with
|
|
61
|
+
* no absent state to grade, since the marker is a required boolean and both
|
|
62
|
+
* values are legal.
|
|
63
|
+
*/
|
|
64
|
+
export declare function stateChangeReadBackRelevance(contract: EvalContract): RelevanceVerdict;
|
|
65
|
+
/**
|
|
66
|
+
* One predicate per rule. The mapped type fails the typecheck when a member of
|
|
67
|
+
* `DISCIPLINE_RULES` has no predicate. Same idiom as `operatorHandlers` in
|
|
68
|
+
* `core/evaluate/resolution.ts`, minus its `Object.hasOwn` guard: these keys
|
|
69
|
+
* come from a compile-time tuple, so a runtime guard is unreachable.
|
|
70
|
+
*/
|
|
71
|
+
export declare const RELEVANCE_PREDICATES: {
|
|
72
|
+
readonly [Rule in DisciplineRule]: (contract: EvalContract) => RelevanceVerdict;
|
|
73
|
+
};
|
|
74
|
+
/** All seven verdicts, in `DISCIPLINE_RULES` order, over declarations alone. */
|
|
75
|
+
export declare function evaluateRelevance(contract: EvalContract): readonly RelevanceVerdict[];
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { TRANSPORT_CHANNELS } from '../schemas/pointer.js';
|
|
2
|
+
import { DISCIPLINE_RULES, relevancePredicateId, } from './rules.js';
|
|
3
|
+
const verdict = (rule, relevant, reason) => ({
|
|
4
|
+
rule,
|
|
5
|
+
predicate: relevancePredicateId(rule),
|
|
6
|
+
relevant,
|
|
7
|
+
reason,
|
|
8
|
+
});
|
|
9
|
+
/** `1 collection location`, `2 collection locations`. A reason is read by people. */
|
|
10
|
+
const plural = (count, noun) => `${count} ${noun}${count === 1 ? '' : 's'}`;
|
|
11
|
+
/** Every declared operation, flattened. Six of the seven rules range over this list. */
|
|
12
|
+
const operationsOf = (contract) => contract.permittedInterfaces.flatMap((declared) => declared.operations);
|
|
13
|
+
/** A contract declaring no operation leaves six of the rules nothing to read. */
|
|
14
|
+
export const NO_OPERATION = 'the contract declares no operation, so the declaration this rule reads is absent';
|
|
15
|
+
/**
|
|
16
|
+
* Rule 1: `successIndicator` declared, and some other pointer carrying a
|
|
17
|
+
* channel role. An operation nominating no indicator is relevant, since a
|
|
18
|
+
* nullable pointer has two spellings and `null` is the absent one.
|
|
19
|
+
* `channelRoles: {}` answers the second conjunct.
|
|
20
|
+
*/
|
|
21
|
+
export function successIndicatorSeparationRelevance(contract) {
|
|
22
|
+
const rule = 'success-indicator-separation';
|
|
23
|
+
const operations = operationsOf(contract);
|
|
24
|
+
if (operations.length === 0)
|
|
25
|
+
return verdict(rule, true, NO_OPERATION);
|
|
26
|
+
for (const operation of operations) {
|
|
27
|
+
const { successIndicator, channelRoles } = operation.responseDescriptor;
|
|
28
|
+
// `=== null`: `DescriptorPointer` admits the empty string, which
|
|
29
|
+
// nominates the whole response body.
|
|
30
|
+
if (successIndicator === null) {
|
|
31
|
+
return verdict(rule, true, `operation ${operation.operationId} nominates no success indicator`);
|
|
32
|
+
}
|
|
33
|
+
if (channelRoles === null) {
|
|
34
|
+
return verdict(rule, true, `operation ${operation.operationId} declares no channel roles`);
|
|
35
|
+
}
|
|
36
|
+
const other = Object.keys(channelRoles).find((pointer) => pointer !== successIndicator);
|
|
37
|
+
if (other !== undefined) {
|
|
38
|
+
return verdict(rule, true, `operation ${operation.operationId} gives pointer ${other} a channel role beside its success indicator`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return verdict(rule, false, 'every operation nominates a success indicator and gives no other pointer a channel role');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rule 2: the descriptor declares more than one pointer, counted over distinct
|
|
45
|
+
* `requiredKeys`. That is the set rule 2's satisfaction denominator reads, and
|
|
46
|
+
* AD-20 keeps permitted keys out of coverage. Distinct, because `requiredKeys`
|
|
47
|
+
* carries no uniqueness constraint and one key repeated is one pointer.
|
|
48
|
+
*/
|
|
49
|
+
export function wholeBodyRelevance(contract) {
|
|
50
|
+
const rule = 'whole-body';
|
|
51
|
+
const operations = operationsOf(contract);
|
|
52
|
+
if (operations.length === 0)
|
|
53
|
+
return verdict(rule, true, NO_OPERATION);
|
|
54
|
+
for (const operation of operations) {
|
|
55
|
+
const distinct = new Set(operation.responseDescriptor.requiredKeys);
|
|
56
|
+
if (distinct.size > 1) {
|
|
57
|
+
return verdict(rule, true, `operation ${operation.operationId} declares ${plural(distinct.size, 'distinct required response key')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return verdict(rule, false, 'no operation declares more than one distinct required response key');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Rule 3: some operation declares a request key on any of AD-19's four
|
|
64
|
+
* transport channels. The site is the whole channel triple. A key with no
|
|
65
|
+
* `types` entry has an absent type, a key typed `null` has AD-31's
|
|
66
|
+
* indeterminate one, and both are relevant, so declaring an input and leaving
|
|
67
|
+
* it untyped buys no irrelevance.
|
|
68
|
+
*/
|
|
69
|
+
export function malformedInputRelevance(contract) {
|
|
70
|
+
const rule = 'malformed-input';
|
|
71
|
+
const operations = operationsOf(contract);
|
|
72
|
+
if (operations.length === 0)
|
|
73
|
+
return verdict(rule, true, NO_OPERATION);
|
|
74
|
+
for (const operation of operations) {
|
|
75
|
+
for (const channel of TRANSPORT_CHANNELS) {
|
|
76
|
+
const shape = operation.requestShape[channel];
|
|
77
|
+
// `Object.keys` enumerates own keys only. `KeyName` admits
|
|
78
|
+
// `constructor`, which a keyed lookup would find on the prototype.
|
|
79
|
+
const key = shape.requiredKeys[0] ??
|
|
80
|
+
shape.permittedKeys[0] ??
|
|
81
|
+
Object.keys(shape.types)[0];
|
|
82
|
+
if (key !== undefined) {
|
|
83
|
+
return verdict(rule, true, `operation ${operation.operationId} declares ${channel} key ${key}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return verdict(rule, false, 'no operation declares a request key on any transport channel');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Rule 4: the descriptor declares at least one collection location. `null` is
|
|
91
|
+
* the absent state and is relevant; `[]` is the explicit empty answer.
|
|
92
|
+
*/
|
|
93
|
+
export function perRecordRelevance(contract) {
|
|
94
|
+
const rule = 'per-record';
|
|
95
|
+
const operations = operationsOf(contract);
|
|
96
|
+
if (operations.length === 0)
|
|
97
|
+
return verdict(rule, true, NO_OPERATION);
|
|
98
|
+
for (const operation of operations) {
|
|
99
|
+
const { collectionLocations } = operation.responseDescriptor;
|
|
100
|
+
if (collectionLocations === null) {
|
|
101
|
+
return verdict(rule, true, `operation ${operation.operationId} declares no collection-location list, so no collection is declared to range over`);
|
|
102
|
+
}
|
|
103
|
+
if (collectionLocations.length > 0) {
|
|
104
|
+
return verdict(rule, true, `operation ${operation.operationId} declares ${plural(collectionLocations.length, 'collection location')}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return verdict(rule, false, 'every operation declares an explicitly empty collection-location list');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Rule 5: a sibling group over operations or parameters is non-empty. The one
|
|
111
|
+
* contract-level rule of the seven. A group of one cannot exist, since
|
|
112
|
+
* `SIBLING_GROUP_MINIMUM` is two.
|
|
113
|
+
*/
|
|
114
|
+
export function siblingCrossCheckRelevance(contract) {
|
|
115
|
+
const rule = 'sibling-cross-check';
|
|
116
|
+
const groups = contract.siblingGroups;
|
|
117
|
+
if (groups === null) {
|
|
118
|
+
return verdict(rule, true, 'the contract declares no sibling groups');
|
|
119
|
+
}
|
|
120
|
+
if (groups.operations.length > 0) {
|
|
121
|
+
return verdict(rule, true, `the contract declares ${plural(groups.operations.length, 'operation sibling group')}`);
|
|
122
|
+
}
|
|
123
|
+
if (groups.parameters.length > 0) {
|
|
124
|
+
return verdict(rule, true, `the contract declares ${plural(groups.parameters.length, 'parameter sibling group')}`);
|
|
125
|
+
}
|
|
126
|
+
return verdict(rule, false, 'the contract declares explicitly empty operation and parameter sibling groups');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Rule 6: a declared collection location names a reference set. The site is
|
|
130
|
+
* `collectionLocations` alone, so an explicitly empty list stays an answer. A
|
|
131
|
+
* location naming an identifier the contract does not declare still names one.
|
|
132
|
+
*/
|
|
133
|
+
export function omissionAndCompletenessRelevance(contract) {
|
|
134
|
+
const rule = 'omission-and-completeness';
|
|
135
|
+
const operations = operationsOf(contract);
|
|
136
|
+
if (operations.length === 0)
|
|
137
|
+
return verdict(rule, true, NO_OPERATION);
|
|
138
|
+
for (const operation of operations) {
|
|
139
|
+
const { collectionLocations } = operation.responseDescriptor;
|
|
140
|
+
if (collectionLocations === null) {
|
|
141
|
+
return verdict(rule, true, `operation ${operation.operationId} declares no collection-location list, so no location can name a reference set`);
|
|
142
|
+
}
|
|
143
|
+
for (const location of collectionLocations) {
|
|
144
|
+
if (location.referenceSet !== null) {
|
|
145
|
+
return verdict(rule, true, `operation ${operation.operationId} names reference set ${location.referenceSet} for collection ${location.pointer}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return verdict(rule, false, 'no declared collection location names a reference set');
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Rule 7: some operation declares `stateChangeMarker: true`. The one rule with
|
|
153
|
+
* no absent state to grade, since the marker is a required boolean and both
|
|
154
|
+
* values are legal.
|
|
155
|
+
*/
|
|
156
|
+
export function stateChangeReadBackRelevance(contract) {
|
|
157
|
+
const rule = 'state-change-read-back';
|
|
158
|
+
const operations = operationsOf(contract);
|
|
159
|
+
if (operations.length === 0)
|
|
160
|
+
return verdict(rule, true, NO_OPERATION);
|
|
161
|
+
for (const operation of operations) {
|
|
162
|
+
if (operation.stateChangeMarker) {
|
|
163
|
+
return verdict(rule, true, `operation ${operation.operationId} declares stateChangeMarker: true`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return verdict(rule, false, 'no operation declares stateChangeMarker: true');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* One predicate per rule. The mapped type fails the typecheck when a member of
|
|
170
|
+
* `DISCIPLINE_RULES` has no predicate. Same idiom as `operatorHandlers` in
|
|
171
|
+
* `core/evaluate/resolution.ts`, minus its `Object.hasOwn` guard: these keys
|
|
172
|
+
* come from a compile-time tuple, so a runtime guard is unreachable.
|
|
173
|
+
*/
|
|
174
|
+
export const RELEVANCE_PREDICATES = {
|
|
175
|
+
'success-indicator-separation': successIndicatorSeparationRelevance,
|
|
176
|
+
'whole-body': wholeBodyRelevance,
|
|
177
|
+
'malformed-input': malformedInputRelevance,
|
|
178
|
+
'per-record': perRecordRelevance,
|
|
179
|
+
'sibling-cross-check': siblingCrossCheckRelevance,
|
|
180
|
+
'omission-and-completeness': omissionAndCompletenessRelevance,
|
|
181
|
+
'state-change-read-back': stateChangeReadBackRelevance,
|
|
182
|
+
};
|
|
183
|
+
/** All seven verdicts, in `DISCIPLINE_RULES` order, over declarations alone. */
|
|
184
|
+
export function evaluateRelevance(contract) {
|
|
185
|
+
return DISCIPLINE_RULES.map((rule) => RELEVANCE_PREDICATES[rule](contract));
|
|
186
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** AD-20's seven discipline rules as identifiers, in AD-20's enumeration order. */
|
|
2
|
+
export declare const DISCIPLINE_RULES: readonly ['success-indicator-separation', 'whole-body', 'malformed-input', 'per-record', 'sibling-cross-check', 'omission-and-completeness', 'state-change-read-back'];
|
|
3
|
+
export type DisciplineRule = (typeof DISCIPLINE_RULES)[number];
|
|
4
|
+
/** `relevancePredicate` on a coverage-gap record. Derived, so a new rule arrives with one. */
|
|
5
|
+
export declare const relevancePredicateId: (rule: DisciplineRule) => string;
|
|
6
|
+
/** `satisfactionPredicate` on a coverage-gap record. Derived, like its relevance twin. */
|
|
7
|
+
export declare const satisfactionPredicateId: (rule: DisciplineRule) => string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** AD-20's seven discipline rules as identifiers, in AD-20's enumeration order. */
|
|
2
|
+
// AD-20 names its seven rules in prose and gives them no identifiers, which is
|
|
3
|
+
// why `Waiver.rule` and `CoverageGap.rule` are opaque strings. These spellings
|
|
4
|
+
// are the Gate C contract's own `oracles[].rule` values
|
|
5
|
+
// (reviews/gate-c/eval-contract.json:267-446), so a gap joins a waiver.
|
|
6
|
+
export const DISCIPLINE_RULES = [
|
|
7
|
+
'success-indicator-separation',
|
|
8
|
+
'whole-body',
|
|
9
|
+
'malformed-input',
|
|
10
|
+
'per-record',
|
|
11
|
+
'sibling-cross-check',
|
|
12
|
+
'omission-and-completeness',
|
|
13
|
+
'state-change-read-back',
|
|
14
|
+
];
|
|
15
|
+
/** `relevancePredicate` on a coverage-gap record. Derived, so a new rule arrives with one. */
|
|
16
|
+
export const relevancePredicateId = (rule) => `${rule}-relevance`;
|
|
17
|
+
/** `satisfactionPredicate` on a coverage-gap record. Derived, like its relevance twin. */
|
|
18
|
+
export const satisfactionPredicateId = (rule) => `${rule}-satisfaction`;
|