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,89 @@
|
|
|
1
|
+
import type { EvalContract } from '../schemas/eval-contract.ts';
|
|
2
|
+
import type { InteractionStep } from '../schemas/plan.ts';
|
|
3
|
+
import type { InputChannelName } from '../schemas/pointer.ts';
|
|
4
|
+
import { type EvidenceTarget } from '../seal/plan-index.ts';
|
|
5
|
+
/**
|
|
6
|
+
* The one channel a captured pointer may name: whichever channel the
|
|
7
|
+
* referenced operation's own response descriptor describes.
|
|
8
|
+
*
|
|
9
|
+
* `ResponseDescriptor` declares `requiredKeys`, `permittedKeys`, `types`,
|
|
10
|
+
* `successIndicator`, `channelRoles`, and `collectionLocations`, and every one
|
|
11
|
+
* of them is about the channel the operation nominates. Off an interface that
|
|
12
|
+
* speaks HTTP that is the response body; off a command it is the stream the
|
|
13
|
+
* operation names. `response-headers` and `response-status` are never it,
|
|
14
|
+
* because `Observation.responseHeaders` admits objects, arrays, numbers, and
|
|
15
|
+
* `null`, so a header capture compiled as a `string` could resolve to an
|
|
16
|
+
* object at score time.
|
|
17
|
+
*/
|
|
18
|
+
/** One `{ captured }` binding, resolved to the pointer target it addresses. */
|
|
19
|
+
export type CapturedBinding = {
|
|
20
|
+
readonly transportChannel: InputChannelName;
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly pointer: string;
|
|
23
|
+
readonly target: EvidenceTarget;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Every captured binding one step declares, in fixed transport-channel order
|
|
27
|
+
* then by key name, so which binding a check reports never depends on a
|
|
28
|
+
* caller-keyed map's insertion order. Exported because `score/binding-order.ts`
|
|
29
|
+
* and `score/bindings.ts` need the same reading of which bindings are captures.
|
|
30
|
+
*/
|
|
31
|
+
export declare function capturedBindings(step: InteractionStep): readonly CapturedBinding[];
|
|
32
|
+
/**
|
|
33
|
+
* `binding-cycle`: a cycle over the union of the capture edges and AD-39's
|
|
34
|
+
* `after` edges that contains at least one capture edge.
|
|
35
|
+
*
|
|
36
|
+
* Both edge kinds assert that this step's observation comes after that one, so
|
|
37
|
+
* a capture B to A together with `A.after = B` is an unsatisfiable ordering
|
|
38
|
+
* while each graph alone stays acyclic. A cycle made only of `after` edges is
|
|
39
|
+
* left to `checkNestedTemporalClause`, which already catches every one of them,
|
|
40
|
+
* so requiring a capture edge in the cycle keeps the two codes disjoint.
|
|
41
|
+
*
|
|
42
|
+
* Decided by strongly connected components rather than by a depth-first walk
|
|
43
|
+
* that inspects each back edge. A cycle lies entirely inside one component, and
|
|
44
|
+
* a capture edge whose endpoints share a component is closed by a path back
|
|
45
|
+
* through that component, so "some cycle contains a capture edge" is exactly
|
|
46
|
+
* "some capture edge has both endpoints in one component". A self-capture is
|
|
47
|
+
* the singleton case and needs no rule of its own. The depth-first form was
|
|
48
|
+
* written first and rejected: settling a node after rejecting a pure-`after`
|
|
49
|
+
* back edge can hide a mixed cycle a later root would reach, and whether it
|
|
50
|
+
* does depends on the order edges happen to be enumerated in, which is an
|
|
51
|
+
* invariant nothing states.
|
|
52
|
+
*
|
|
53
|
+
* Nodes are the plan's declared step ids; an edge whose target no step declares
|
|
54
|
+
* is dropped, since a dangling reference is not a cycle and AD-39 makes a
|
|
55
|
+
* dangling `after` permissive besides. The binding reported is the first, in
|
|
56
|
+
* the plan's own declaration order, whose endpoints share a component.
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkBindingCycle(contract: EvalContract): void;
|
|
59
|
+
/**
|
|
60
|
+
* `captured-channel-undeclared`: a captured pointer naming any AD-26 channel
|
|
61
|
+
* but the one the referenced operation's response descriptor describes. That
|
|
62
|
+
* is `response-body` off an interface that speaks HTTP and whichever channel
|
|
63
|
+
* `descriptorChannel` nominates off one that runs behind a command, which is
|
|
64
|
+
* the same rule read against the declaration rather than assumed.
|
|
65
|
+
*
|
|
66
|
+
* Every other channel is refused for a reason that does not depend on the
|
|
67
|
+
* kind: `call-inputs` addresses a step's own request, and `response-headers`,
|
|
68
|
+
* `response-status`, and `exit-code` have no declared structure to give a
|
|
69
|
+
* captured value a type. On the `artifact` channel the identifier is compared
|
|
70
|
+
* too, so a capture from a file the operation writes but does not describe is
|
|
71
|
+
* refused alongside one from a file it never writes.
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkCapturedChannel(contract: EvalContract): void;
|
|
74
|
+
/**
|
|
75
|
+
* `unreachable-check-evidence` over the captured-pointer residue: an
|
|
76
|
+
* unresolvable step or operation, an undeclared body key, a tail that is not
|
|
77
|
+
* exactly one segment, an array index, an absent, indeterminate, or non-scalar
|
|
78
|
+
* declared type, and a declared type that does not equal the bound
|
|
79
|
+
* parameter's.
|
|
80
|
+
*
|
|
81
|
+
* A capture naming another channel is reported here only for the two conditions
|
|
82
|
+
* that are decidable without a response descriptor: an unresolvable step and an
|
|
83
|
+
* unresolvable operation. Everything else about such a pointer is
|
|
84
|
+
* `checkCapturedChannel`'s. That split is what lets this check run at the
|
|
85
|
+
* registry's third rung without shadowing the fifteenth, and it keeps the
|
|
86
|
+
* higher-ranked code first on a pointer that is unresolvable and off-body at
|
|
87
|
+
* once.
|
|
88
|
+
*/
|
|
89
|
+
export declare function checkCapturedReachability(contract: EvalContract): void;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owed item 3's compile-time checks over captured input bindings:
|
|
3
|
+
* `binding-cycle` over the ordering graph, `captured-channel-undeclared` on a
|
|
4
|
+
* pointer naming any channel but `response-body`, and the typed residue that
|
|
5
|
+
* fires the shipped `unreachable-check-evidence`.
|
|
6
|
+
*
|
|
7
|
+
* The three run at two registry positions. `checkCapturedReachability` fires
|
|
8
|
+
* `unreachable-check-evidence`, whose rung is third, so it runs beside
|
|
9
|
+
* `checkEvidenceReachability`; the two new codes run at their own inserted
|
|
10
|
+
* position. Running captured reachability late would let a lower-ranked code
|
|
11
|
+
* win on a contract carrying both defects, which `compile.ts`'s own documented
|
|
12
|
+
* priority forbids.
|
|
13
|
+
*
|
|
14
|
+
* `evaluatePointerReachability` decides the step, operation, body-key, and
|
|
15
|
+
* scalar-descent half. The scalar determination, the one-segment tail rule, and
|
|
16
|
+
* type equality are this module's own.
|
|
17
|
+
*/
|
|
18
|
+
import { boundChannelsOf, descriptorChannelOf, requestShapeOf, targetsDescribedChannel, } from '../declared-inputs.js';
|
|
19
|
+
import { ARRAY_INDEX_PATTERN } from '../evaluate/evidence-resolution.js';
|
|
20
|
+
import { StructuralFailure } from '../failure-codes.js';
|
|
21
|
+
import { JsonTypeName } from '../schemas/primitives.js';
|
|
22
|
+
import { anyOperationOf, buildPlanIndex, parseEvidenceTarget, resolveStep, } from '../seal/plan-index.js';
|
|
23
|
+
import { evaluatePointerReachability } from './reachability.js';
|
|
24
|
+
/**
|
|
25
|
+
* Every captured binding one step declares, in fixed transport-channel order
|
|
26
|
+
* then by key name, so which binding a check reports never depends on a
|
|
27
|
+
* caller-keyed map's insertion order. Exported because `score/binding-order.ts`
|
|
28
|
+
* and `score/bindings.ts` need the same reading of which bindings are captures.
|
|
29
|
+
*/
|
|
30
|
+
export function capturedBindings(step) {
|
|
31
|
+
const captures = [];
|
|
32
|
+
for (const { channel: transportChannel, bound: map } of boundChannelsOf(step.inputBinding)) {
|
|
33
|
+
if (map === null)
|
|
34
|
+
continue;
|
|
35
|
+
for (const key of Object.keys(map).sort()) {
|
|
36
|
+
const value = map[key];
|
|
37
|
+
if (value === undefined || !('captured' in value))
|
|
38
|
+
continue;
|
|
39
|
+
captures.push({
|
|
40
|
+
transportChannel,
|
|
41
|
+
key,
|
|
42
|
+
pointer: value.captured,
|
|
43
|
+
target: parseEvidenceTarget(value.captured),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return captures;
|
|
48
|
+
}
|
|
49
|
+
// The one shipped precedent for addressing a caller-keyed binding entry
|
|
50
|
+
// (`interface-inventory.ts`'s undeclared-key throw), reused so the two new
|
|
51
|
+
// codes and the widened `undeclared-mandatory-input` agree on one spelling.
|
|
52
|
+
function bindingPath(step, capture) {
|
|
53
|
+
return `EvalContract.interactionPlan[stepId=${step.stepId}].inputBinding.${capture.transportChannel}[${JSON.stringify(capture.key)}]`;
|
|
54
|
+
}
|
|
55
|
+
// ---- binding-cycle -------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* `binding-cycle`: a cycle over the union of the capture edges and AD-39's
|
|
58
|
+
* `after` edges that contains at least one capture edge.
|
|
59
|
+
*
|
|
60
|
+
* Both edge kinds assert that this step's observation comes after that one, so
|
|
61
|
+
* a capture B to A together with `A.after = B` is an unsatisfiable ordering
|
|
62
|
+
* while each graph alone stays acyclic. A cycle made only of `after` edges is
|
|
63
|
+
* left to `checkNestedTemporalClause`, which already catches every one of them,
|
|
64
|
+
* so requiring a capture edge in the cycle keeps the two codes disjoint.
|
|
65
|
+
*
|
|
66
|
+
* Decided by strongly connected components rather than by a depth-first walk
|
|
67
|
+
* that inspects each back edge. A cycle lies entirely inside one component, and
|
|
68
|
+
* a capture edge whose endpoints share a component is closed by a path back
|
|
69
|
+
* through that component, so "some cycle contains a capture edge" is exactly
|
|
70
|
+
* "some capture edge has both endpoints in one component". A self-capture is
|
|
71
|
+
* the singleton case and needs no rule of its own. The depth-first form was
|
|
72
|
+
* written first and rejected: settling a node after rejecting a pure-`after`
|
|
73
|
+
* back edge can hide a mixed cycle a later root would reach, and whether it
|
|
74
|
+
* does depends on the order edges happen to be enumerated in, which is an
|
|
75
|
+
* invariant nothing states.
|
|
76
|
+
*
|
|
77
|
+
* Nodes are the plan's declared step ids; an edge whose target no step declares
|
|
78
|
+
* is dropped, since a dangling reference is not a cycle and AD-39 makes a
|
|
79
|
+
* dangling `after` permissive besides. The binding reported is the first, in
|
|
80
|
+
* the plan's own declaration order, whose endpoints share a component.
|
|
81
|
+
*/
|
|
82
|
+
export function checkBindingCycle(contract) {
|
|
83
|
+
const declaredStepIds = new Set(contract.interactionPlan.map((step) => step.stepId));
|
|
84
|
+
const edgesFrom = new Map();
|
|
85
|
+
const addEdge = (from, to) => {
|
|
86
|
+
const list = edgesFrom.get(from);
|
|
87
|
+
if (list === undefined)
|
|
88
|
+
edgesFrom.set(from, [to]);
|
|
89
|
+
else
|
|
90
|
+
list.push(to);
|
|
91
|
+
};
|
|
92
|
+
for (const step of contract.interactionPlan) {
|
|
93
|
+
for (const capture of capturedBindings(step)) {
|
|
94
|
+
if (declaredStepIds.has(capture.target.stepId)) {
|
|
95
|
+
addEdge(step.stepId, capture.target.stepId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (step.after !== null && declaredStepIds.has(step.after)) {
|
|
99
|
+
addEdge(step.stepId, step.after);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const component = stronglyConnectedComponents(declaredStepIds, edgesFrom);
|
|
103
|
+
for (const step of contract.interactionPlan) {
|
|
104
|
+
for (const capture of capturedBindings(step)) {
|
|
105
|
+
const target = capture.target.stepId;
|
|
106
|
+
if (!declaredStepIds.has(target))
|
|
107
|
+
continue;
|
|
108
|
+
if (component.get(step.stepId) !== component.get(target))
|
|
109
|
+
continue;
|
|
110
|
+
throw new StructuralFailure('binding-cycle', bindingPath(step, capture), `captured pointer "${capture.pointer}" closes a cycle over the capture and temporal-clause edges; a captured value has no earlier step to resolve from (AD-39)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Tarjan's algorithm, returning each node's component identifier. */
|
|
115
|
+
function stronglyConnectedComponents(nodes, edgesFrom) {
|
|
116
|
+
const order = new Map();
|
|
117
|
+
const lowLink = new Map();
|
|
118
|
+
const onStack = new Set();
|
|
119
|
+
const stack = [];
|
|
120
|
+
const component = new Map();
|
|
121
|
+
let nextOrder = 0;
|
|
122
|
+
let nextComponent = 0;
|
|
123
|
+
const connect = (node) => {
|
|
124
|
+
order.set(node, nextOrder);
|
|
125
|
+
lowLink.set(node, nextOrder);
|
|
126
|
+
nextOrder += 1;
|
|
127
|
+
stack.push(node);
|
|
128
|
+
onStack.add(node);
|
|
129
|
+
for (const next of edgesFrom.get(node) ?? []) {
|
|
130
|
+
if (!order.has(next)) {
|
|
131
|
+
connect(next);
|
|
132
|
+
lowLink.set(node, Math.min(lowLink.get(node) ?? 0, lowLink.get(next) ?? 0));
|
|
133
|
+
}
|
|
134
|
+
else if (onStack.has(next)) {
|
|
135
|
+
lowLink.set(node, Math.min(lowLink.get(node) ?? 0, order.get(next) ?? 0));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (lowLink.get(node) !== order.get(node))
|
|
139
|
+
return;
|
|
140
|
+
const id = nextComponent;
|
|
141
|
+
nextComponent += 1;
|
|
142
|
+
for (;;) {
|
|
143
|
+
const member = stack.pop();
|
|
144
|
+
if (member === undefined)
|
|
145
|
+
break;
|
|
146
|
+
onStack.delete(member);
|
|
147
|
+
component.set(member, id);
|
|
148
|
+
if (member === node)
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
for (const node of nodes) {
|
|
153
|
+
if (!order.has(node))
|
|
154
|
+
connect(node);
|
|
155
|
+
}
|
|
156
|
+
return component;
|
|
157
|
+
}
|
|
158
|
+
// ---- captured-channel-undeclared -----------------------------------------
|
|
159
|
+
/**
|
|
160
|
+
* `captured-channel-undeclared`: a captured pointer naming any AD-26 channel
|
|
161
|
+
* but the one the referenced operation's response descriptor describes. That
|
|
162
|
+
* is `response-body` off an interface that speaks HTTP and whichever channel
|
|
163
|
+
* `descriptorChannel` nominates off one that runs behind a command, which is
|
|
164
|
+
* the same rule read against the declaration rather than assumed.
|
|
165
|
+
*
|
|
166
|
+
* Every other channel is refused for a reason that does not depend on the
|
|
167
|
+
* kind: `call-inputs` addresses a step's own request, and `response-headers`,
|
|
168
|
+
* `response-status`, and `exit-code` have no declared structure to give a
|
|
169
|
+
* captured value a type. On the `artifact` channel the identifier is compared
|
|
170
|
+
* too, so a capture from a file the operation writes but does not describe is
|
|
171
|
+
* refused alongside one from a file it never writes.
|
|
172
|
+
*/
|
|
173
|
+
export function checkCapturedChannel(contract) {
|
|
174
|
+
let index;
|
|
175
|
+
for (const step of contract.interactionPlan) {
|
|
176
|
+
for (const capture of capturedBindings(step)) {
|
|
177
|
+
index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
|
|
178
|
+
const referenced = index.stepOf(capture.target.stepId);
|
|
179
|
+
const operation = referenced === undefined
|
|
180
|
+
? undefined
|
|
181
|
+
: anyOperationOf(index, referenced.operationId);
|
|
182
|
+
// An unresolvable reference is `checkCapturedReachability`'s, at a
|
|
183
|
+
// higher rung. With nothing to ask, this check falls back to the
|
|
184
|
+
// api answer so an unresolvable off-body capture still reports.
|
|
185
|
+
const capturable = operation === undefined
|
|
186
|
+
? 'response-body'
|
|
187
|
+
: descriptorChannelOf(operation);
|
|
188
|
+
if (operation === undefined
|
|
189
|
+
? capture.target.channel === capturable
|
|
190
|
+
: targetsDescribedChannel(operation, capture.target)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
throw new StructuralFailure('captured-channel-undeclared', bindingPath(step, capture), `captured pointer "${capture.pointer}" names the ${capture.target.channel} channel, which the referenced operation's response descriptor does not declare (AD-26)`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ---- the typed residue: unreachable-check-evidence -----------------------
|
|
198
|
+
const SCALAR_TYPES = new Set(JsonTypeName.options.filter((name) => name !== 'object' && name !== 'array'));
|
|
199
|
+
/**
|
|
200
|
+
* The type a captured pointer resolves to, or why nothing scalar is declared
|
|
201
|
+
* there. The tail must be exactly one segment: `ResponseDescriptor.types` is
|
|
202
|
+
* keyed by plain key name, so a nested segment has no declared type to compare
|
|
203
|
+
* against, and an array element has none either even where
|
|
204
|
+
* `evaluatePointerReachability` admits the index against a root collection.
|
|
205
|
+
*/
|
|
206
|
+
function capturedType(target, index) {
|
|
207
|
+
const channel = target.channel;
|
|
208
|
+
const segments = target.tail.length;
|
|
209
|
+
if (segments !== 1) {
|
|
210
|
+
return {
|
|
211
|
+
reason: `addresses ${segments === 0 ? `the whole ${channel}` : `a ${channel} path ${segments} segments deep`}, which declares no scalar to capture`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const key = target.tail[0];
|
|
215
|
+
if (key === undefined) {
|
|
216
|
+
// Unreachable: the length check above guarantees one segment.
|
|
217
|
+
throw new TypeError('captured pointer tail is one segment but is empty');
|
|
218
|
+
}
|
|
219
|
+
if (ARRAY_INDEX_PATTERN.test(key)) {
|
|
220
|
+
return {
|
|
221
|
+
reason: `addresses ${channel} element ${key}, which no declaration gives a type`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const step = resolveStep(index, target.stepId);
|
|
225
|
+
const operation = anyOperationOf(index, step.operationId);
|
|
226
|
+
if (operation === undefined) {
|
|
227
|
+
throw new TypeError(`step names an operation the permitted interfaces do not declare: ${step.operationId}`);
|
|
228
|
+
}
|
|
229
|
+
const declared = operation.responseDescriptor.types[key];
|
|
230
|
+
if (declared === undefined || declared === null) {
|
|
231
|
+
return {
|
|
232
|
+
reason: `addresses ${channel} field "${key}", whose type operation "${operation.operationId}" ${declared === undefined ? 'does not declare' : 'declares indeterminate'}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (!SCALAR_TYPES.has(declared)) {
|
|
236
|
+
return {
|
|
237
|
+
reason: `addresses ${channel} field "${key}", which operation "${operation.operationId}" declares "${declared}" rather than a scalar`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { type: declared };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The type the bound parameter itself is declared as. An indeterminate
|
|
244
|
+
* declaration on either side is not equal to anything and fails closed, which
|
|
245
|
+
* is AD-31's disposition for an indeterminate descriptor. A step whose own
|
|
246
|
+
* operation does not resolve is skipped, matching
|
|
247
|
+
* `checkUndeclaredMandatoryInput`: that defect belongs to a separate
|
|
248
|
+
* cross-field rule.
|
|
249
|
+
*/
|
|
250
|
+
function boundParameterType(step, capture, index) {
|
|
251
|
+
const operation = anyOperationOf(index, step.operationId);
|
|
252
|
+
if (operation === undefined)
|
|
253
|
+
return null;
|
|
254
|
+
const shape = requestShapeOf(operation, capture.transportChannel);
|
|
255
|
+
// A channel the operation does not accept input on declares no type for
|
|
256
|
+
// the key either, so there is nothing to compare and the check abstains
|
|
257
|
+
// for the same reason the undeclared-key branch below does.
|
|
258
|
+
if (shape === undefined)
|
|
259
|
+
return null;
|
|
260
|
+
// A key the operation declares in neither list is an input the contract did
|
|
261
|
+
// not declare, which is `undeclared-mandatory-input`'s and strict-only under
|
|
262
|
+
// AD-4. This check runs unconditionally, so claiming it here would reject a
|
|
263
|
+
// non-strict contract under a code that names a different defect, and would
|
|
264
|
+
// treat a captured binding more harshly than the same key bound to a literal.
|
|
265
|
+
if (!shape.requiredKeys.includes(capture.key) &&
|
|
266
|
+
!shape.permittedKeys.includes(capture.key)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const declared = shape.types[capture.key];
|
|
270
|
+
if (declared === undefined || declared === null) {
|
|
271
|
+
return {
|
|
272
|
+
reason: `binds ${capture.transportChannel} parameter "${capture.key}", whose type operation "${operation.operationId}" ${declared === undefined ? 'does not declare' : 'declares indeterminate'}, so no type equality is decidable`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { type: declared };
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* `unreachable-check-evidence` over the captured-pointer residue: an
|
|
279
|
+
* unresolvable step or operation, an undeclared body key, a tail that is not
|
|
280
|
+
* exactly one segment, an array index, an absent, indeterminate, or non-scalar
|
|
281
|
+
* declared type, and a declared type that does not equal the bound
|
|
282
|
+
* parameter's.
|
|
283
|
+
*
|
|
284
|
+
* A capture naming another channel is reported here only for the two conditions
|
|
285
|
+
* that are decidable without a response descriptor: an unresolvable step and an
|
|
286
|
+
* unresolvable operation. Everything else about such a pointer is
|
|
287
|
+
* `checkCapturedChannel`'s. That split is what lets this check run at the
|
|
288
|
+
* registry's third rung without shadowing the fifteenth, and it keeps the
|
|
289
|
+
* higher-ranked code first on a pointer that is unresolvable and off-body at
|
|
290
|
+
* once.
|
|
291
|
+
*/
|
|
292
|
+
export function checkCapturedReachability(contract) {
|
|
293
|
+
let index;
|
|
294
|
+
for (const step of contract.interactionPlan) {
|
|
295
|
+
for (const capture of capturedBindings(step)) {
|
|
296
|
+
const path = bindingPath(step, capture);
|
|
297
|
+
index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
|
|
298
|
+
// Resolved before the channel test, so a pointer that is both
|
|
299
|
+
// unresolvable and on a non-body channel reports the higher-ranked
|
|
300
|
+
// code. Deferring this to `checkCapturedChannel` would fire index 15
|
|
301
|
+
// where AD-5's order gives index 2, and would assert something about
|
|
302
|
+
// a response descriptor that no declared operation supplies. The
|
|
303
|
+
// wording matches `evaluatePointerReachability`'s own two reasons, so
|
|
304
|
+
// one defect reads the same however it is reached.
|
|
305
|
+
const referenced = index.stepOf(capture.target.stepId);
|
|
306
|
+
if (referenced === undefined) {
|
|
307
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" names a step the interaction plan does not declare`);
|
|
308
|
+
}
|
|
309
|
+
const operation = anyOperationOf(index, referenced.operationId);
|
|
310
|
+
if (operation === undefined) {
|
|
311
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" names step "${capture.target.stepId}", which names operation "${referenced.operationId}", not declared by any permitted interface`);
|
|
312
|
+
}
|
|
313
|
+
if (!targetsDescribedChannel(operation, capture.target))
|
|
314
|
+
continue;
|
|
315
|
+
const reachability = evaluatePointerReachability(capture.pointer, index);
|
|
316
|
+
if (!reachability.reachable) {
|
|
317
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" ${reachability.reason}`);
|
|
318
|
+
}
|
|
319
|
+
const captured = capturedType(capture.target, index);
|
|
320
|
+
if (captured.type === undefined) {
|
|
321
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" ${captured.reason}`);
|
|
322
|
+
}
|
|
323
|
+
const bound = boundParameterType(step, capture, index);
|
|
324
|
+
if (bound === null)
|
|
325
|
+
continue;
|
|
326
|
+
if (bound.type === undefined) {
|
|
327
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" resolves to a declared "${captured.type}", but the step ${bound.reason}`);
|
|
328
|
+
}
|
|
329
|
+
if (bound.type !== captured.type) {
|
|
330
|
+
throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" resolves to a declared "${captured.type}", which is not the "${bound.type}" the bound ${capture.transportChannel} parameter "${capture.key}" is declared as`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -13,6 +13,23 @@
|
|
|
13
13
|
* code; the other two witness checks fire different codes and run
|
|
14
14
|
* unconditionally, after the registry order, since AD-5 names no rung for them.
|
|
15
15
|
*
|
|
16
|
+
* `checkCapturedReachability` fires the shipped `unreachable-check-evidence`,
|
|
17
|
+
* so it runs at that code's own third rung, beside `checkEvidenceReachability`.
|
|
18
|
+
* Grouping it with the two codes owed item 3 added, at the inserted position
|
|
19
|
+
* below, would let a lower-ranked code win on a contract carrying both defects.
|
|
20
|
+
*
|
|
21
|
+
* `checkArtifactReferences` fires `unresolved-artifact-reference`, the
|
|
22
|
+
* registry's last code, but runs near the front for the reason its own call
|
|
23
|
+
* site records: it names a fault that reachability can only describe the
|
|
24
|
+
* consequence of, and reachability abstains on the same pointer so the two
|
|
25
|
+
* never race.
|
|
26
|
+
*
|
|
27
|
+
* `checkStepReferenceReducibility` runs last, after every other check has had
|
|
28
|
+
* its say. It is the only check that reports what a LATER stage cannot do
|
|
29
|
+
* rather than what this contract declares wrongly, so a contract with an
|
|
30
|
+
* ordinary declaration fault should hear about that fault rather than about
|
|
31
|
+
* the sealing consequence of it.
|
|
32
|
+
*
|
|
16
33
|
* `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
|
|
17
34
|
* `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
|
|
18
35
|
* rubric or criterion id makes every `rubrics[id=...]` path the other three
|
|
@@ -13,26 +13,58 @@
|
|
|
13
13
|
* code; the other two witness checks fire different codes and run
|
|
14
14
|
* unconditionally, after the registry order, since AD-5 names no rung for them.
|
|
15
15
|
*
|
|
16
|
+
* `checkCapturedReachability` fires the shipped `unreachable-check-evidence`,
|
|
17
|
+
* so it runs at that code's own third rung, beside `checkEvidenceReachability`.
|
|
18
|
+
* Grouping it with the two codes owed item 3 added, at the inserted position
|
|
19
|
+
* below, would let a lower-ranked code win on a contract carrying both defects.
|
|
20
|
+
*
|
|
21
|
+
* `checkArtifactReferences` fires `unresolved-artifact-reference`, the
|
|
22
|
+
* registry's last code, but runs near the front for the reason its own call
|
|
23
|
+
* site records: it names a fault that reachability can only describe the
|
|
24
|
+
* consequence of, and reachability abstains on the same pointer so the two
|
|
25
|
+
* never race.
|
|
26
|
+
*
|
|
27
|
+
* `checkStepReferenceReducibility` runs last, after every other check has had
|
|
28
|
+
* its say. It is the only check that reports what a LATER stage cannot do
|
|
29
|
+
* rather than what this contract declares wrongly, so a contract with an
|
|
30
|
+
* ordinary declaration fault should hear about that fault rather than about
|
|
31
|
+
* the sealing consequence of it.
|
|
32
|
+
*
|
|
16
33
|
* `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
|
|
17
34
|
* `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
|
|
18
35
|
* rubric or criterion id makes every `rubrics[id=...]` path the other three
|
|
19
36
|
* rubric checks emit address two things, so identifiers are settled before any
|
|
20
37
|
* of them reports.
|
|
21
38
|
*/
|
|
39
|
+
import { EVAL_CONTRACT_SCHEMA_VERSION } from '../schemas/eval-contract.js';
|
|
40
|
+
import { checkBindingCycle, checkCapturedChannel, checkCapturedReachability, } from './bindings.js';
|
|
22
41
|
import { checkObservableSuccessCriterion, checkRequirementLinkage, } from './declarations.js';
|
|
42
|
+
import { checkExcludedContent } from './excluded-content.js';
|
|
23
43
|
import { checkOperandLegality, checkQuantifierNesting, checkQuantifierOverNonCollection, checkReferenceSetResolution, checkRegexConstructs, } from './expression-legality.js';
|
|
24
44
|
import { checkForbiddenInputFloor, checkScopedResourceReferences, } from './forbidden-inputs.js';
|
|
25
|
-
import { checkDuplicateOperationSignature, checkInterfaceKind, checkUndeclaredMandatoryInput, } from './interface-inventory.js';
|
|
45
|
+
import { checkArtifactReferences, checkDuplicateOperationSignature, checkInterfaceKind, checkUndeclaredMandatoryInput, } from './interface-inventory.js';
|
|
26
46
|
import { checkOracleAlignment, checkOracleChannel } from './oracle-alignment.js';
|
|
27
47
|
import { checkBoundElementScope, checkEvidenceReachability, } from './reachability.js';
|
|
28
48
|
import { checkRubricAnchoring, checkRubricEvidenceReachability, checkRubricIdentifiers, checkRubricReasoningProse, } from './rubrics.js';
|
|
49
|
+
import { checkSchemaVersion } from './schema-version.js';
|
|
29
50
|
import { checkNestedTemporalClause, checkScriptingBound, } from './scripting-bound.js';
|
|
30
51
|
import { checkSensitivityWitnessDeclared, checkWitnessLegality, checkWitnessLegIdentifiers, } from './sensitivity-witness.js';
|
|
52
|
+
import { checkStepReferenceReducibility } from './step-reference.js';
|
|
31
53
|
import { checkWaiverCompleteness } from './waivers.js';
|
|
32
54
|
export function compile(contract, options) {
|
|
55
|
+
// First, and before any check reads a declaration. AD-11 makes an unequal
|
|
56
|
+
// stamp a rejection rather than a degraded read, and every check below is
|
|
57
|
+
// written against this version's field shapes.
|
|
58
|
+
checkSchemaVersion(contract.schemaVersion, EVAL_CONTRACT_SCHEMA_VERSION, 'EvalContract.schemaVersion');
|
|
33
59
|
checkRequirementLinkage(contract);
|
|
34
60
|
checkObservableSuccessCriterion(contract);
|
|
61
|
+
// Ahead of reachability, because a pointer naming an artifact nothing
|
|
62
|
+
// declares has no operation shape to be reachable against, and the code
|
|
63
|
+
// that names the fault directly should not lose the race to the code that
|
|
64
|
+
// describes its consequence.
|
|
65
|
+
checkArtifactReferences(contract);
|
|
35
66
|
checkEvidenceReachability(contract);
|
|
67
|
+
checkCapturedReachability(contract);
|
|
36
68
|
checkBoundElementScope(contract);
|
|
37
69
|
checkOperandLegality(contract);
|
|
38
70
|
checkRegexConstructs(contract);
|
|
@@ -49,13 +81,17 @@ export function compile(contract, options) {
|
|
|
49
81
|
checkInterfaceKind(contract);
|
|
50
82
|
checkNestedTemporalClause(contract);
|
|
51
83
|
checkScriptingBound(contract);
|
|
84
|
+
checkBindingCycle(contract);
|
|
85
|
+
checkCapturedChannel(contract);
|
|
52
86
|
checkRubricIdentifiers(contract);
|
|
53
87
|
checkRubricReasoningProse(contract);
|
|
54
88
|
checkRubricAnchoring(contract);
|
|
55
89
|
checkRubricEvidenceReachability(contract);
|
|
56
90
|
checkForbiddenInputFloor(contract);
|
|
91
|
+
checkExcludedContent(contract);
|
|
57
92
|
checkScopedResourceReferences(contract);
|
|
58
93
|
checkWaiverCompleteness(contract);
|
|
94
|
+
checkStepReferenceReducibility(contract);
|
|
59
95
|
// Identifiers before legality. A duplicated or plan-colliding leg id makes
|
|
60
96
|
// the legality check's question ("does the relation address both legs?")
|
|
61
97
|
// unanswerable, so legality-first reports an unreachable-evidence failure on
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EvalContract } from '../schemas/eval-contract.ts';
|
|
2
|
+
/**
|
|
3
|
+
* `excluded-content-in-declaration`: the contract carries a value-shaped secret.
|
|
4
|
+
*
|
|
5
|
+
* The first hit is reported and the rest are dropped. A contract carrying one
|
|
6
|
+
* has to be re-authored before it compiles at all, so enumerating the others
|
|
7
|
+
* would print more of the secret material into a failure message that gets
|
|
8
|
+
* pasted into a terminal, an issue, and a log. The message names the category
|
|
9
|
+
* and the path and quotes nothing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkExcludedContent(contract: EvalContract): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-18's exclusion, read against the contract rather than against a directory.
|
|
3
|
+
*
|
|
4
|
+
* AD-18 excludes credentials, tokens, real names, email addresses, account
|
|
5
|
+
* identifiers, and transaction content from every artifact this package produces
|
|
6
|
+
* or publishes, and says so binds "published examples and test fixtures as
|
|
7
|
+
* strictly as real runs". The only mechanism enforcing it was a test that greps
|
|
8
|
+
* `corpus/dev`, which reaches the corpus this repository ships and no contract an
|
|
9
|
+
* adopter authors. A token written into a contract therefore compiled clean and
|
|
10
|
+
* travelled onward: `seal` copies permitted interfaces onto the sealed brief, so
|
|
11
|
+
* the value reaches the one artifact AD-16 hands to an evaluator, and `emit`
|
|
12
|
+
* copies quoted evidence onto the evidence artifact.
|
|
13
|
+
*
|
|
14
|
+
* The whole contract is scanned, not a chosen subtree. Choosing one would have to
|
|
15
|
+
* be right about where an author pastes a secret, and the answer is wherever the
|
|
16
|
+
* field they were filling in happened to be: a witness input value, a fixture
|
|
17
|
+
* reset payload, an oracle's expected operand, a behaviour description. The
|
|
18
|
+
* scan's cost is one walk of an already-parsed object.
|
|
19
|
+
*
|
|
20
|
+
* Unconditional rather than strict-gated, unlike `undeclared-mandatory-input`
|
|
21
|
+
* next to it. That code is strict-only because AD-4 leaves a contract two
|
|
22
|
+
* legitimate readings of how completely it declares its inputs. AD-18 has one
|
|
23
|
+
* reading and no lenient mode: a credential that reaches a published artifact is
|
|
24
|
+
* unrecoverable, which is the harm the decision names.
|
|
25
|
+
*/
|
|
26
|
+
import { scanExcludedContent } from '../excluded-content.js';
|
|
27
|
+
import { StructuralFailure } from '../failure-codes.js';
|
|
28
|
+
/**
|
|
29
|
+
* `excluded-content-in-declaration`: the contract carries a value-shaped secret.
|
|
30
|
+
*
|
|
31
|
+
* The first hit is reported and the rest are dropped. A contract carrying one
|
|
32
|
+
* has to be re-authored before it compiles at all, so enumerating the others
|
|
33
|
+
* would print more of the secret material into a failure message that gets
|
|
34
|
+
* pasted into a terminal, an issue, and a log. The message names the category
|
|
35
|
+
* and the path and quotes nothing.
|
|
36
|
+
*/
|
|
37
|
+
export function checkExcludedContent(contract) {
|
|
38
|
+
const hit = scanExcludedContent(contract, 'EvalContract')[0];
|
|
39
|
+
if (hit === undefined)
|
|
40
|
+
return;
|
|
41
|
+
throw new StructuralFailure('excluded-content-in-declaration', hit.path, `carries a value shaped like ${hit.category}, which AD-18 excludes from every artifact this package produces; store a digest or an AD-8 opaque reference instead. The matched text is not quoted here, since a failure message travels further than the contract does`);
|
|
42
|
+
}
|
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import type { EvalContract } from '../schemas/eval-contract.ts';
|
|
2
|
+
import type { Expression, Operand, SetOperand } from '../schemas/expression.ts';
|
|
3
|
+
import type { AnyOperation } from '../schemas/interface.ts';
|
|
4
|
+
type OperandPosition = 0 | 1 | 'collection';
|
|
5
|
+
type OperandBearingOp = Exclude<Expression['op'], 'not' | 'all' | 'any'>;
|
|
6
|
+
type Visitor = {
|
|
7
|
+
onOperand?: (operand: Operand, op: OperandBearingOp, position: OperandPosition, path: string, quantifierDepth: number) => void;
|
|
8
|
+
onSetOperand?: (setOperand: SetOperand, path: string) => void;
|
|
9
|
+
onQuantifier?: (expr: {
|
|
10
|
+
op: 'for-all' | 'for-any';
|
|
11
|
+
collection: Operand;
|
|
12
|
+
predicate: Expression;
|
|
13
|
+
}, path: string, quantifierDepth: number) => void;
|
|
14
|
+
onCoversByKey?: (expr: Extract<Expression, {
|
|
15
|
+
op: 'covers-by-key';
|
|
16
|
+
}>, path: string, quantifierDepth: number) => void;
|
|
17
|
+
onRegex?: (pattern: string, path: string) => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The one expression walker in this package. Contract-free and a superset of
|
|
21
|
+
* the narrower walks the checks below need, so a probe-side legality pass
|
|
22
|
+
* reuses it rather than adding a fifth traversal that can drift from it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function walkExpression(expr: Expression, quantifierDepth: number, path: string, visitor: Visitor): void;
|
|
25
|
+
/**
|
|
26
|
+
* `checkOperandLegality` over one bare `Expression`, minus the `covers-by-key`
|
|
27
|
+
* `expectedKey` uniqueness rule, which reads the contract's declared reference
|
|
28
|
+
* sets and has no probe-side form: a probe-side condition may carry no
|
|
29
|
+
* `{ referenceSet }` operand at all.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkExpressionOperandLegality(expression: Expression, artifactPath: string): void;
|
|
32
|
+
/** `checkRegexConstructs` over one bare `Expression`. */
|
|
33
|
+
export declare function checkExpressionRegexConstructs(expression: Expression, artifactPath: string): void;
|
|
34
|
+
/** `checkQuantifierNesting` over one bare `Expression`. */
|
|
35
|
+
export declare function checkExpressionQuantifierNesting(expression: Expression, artifactPath: string): void;
|
|
2
36
|
/** Checks each operator's operands against its position-specific constraints. */
|
|
3
37
|
export declare function checkOperandLegality(contract: EvalContract): void;
|
|
4
38
|
/** Rejects invalid regexes, partial anchors, backreferences, and lookbehind. */
|
|
5
39
|
export declare function checkRegexConstructs(contract: EvalContract): void;
|
|
6
40
|
/** Rejects nested quantifiers and covers-by-key inside a quantifier. */
|
|
7
41
|
export declare function checkQuantifierNesting(contract: EvalContract): void;
|
|
42
|
+
/**
|
|
43
|
+
* `checkQuantifierOverNonCollection` over one bare `Expression` rooted at a
|
|
44
|
+
* known set of step identifiers, all of which resolve to one operation. The
|
|
45
|
+
* `legIds` argument is load-bearing: omit it and the check silently no-ops on
|
|
46
|
+
* every expression, which is the trap the witness branch below already warns
|
|
47
|
+
* about. It reads the operation's declared response types, never its
|
|
48
|
+
* `collectionLocations`; a declared non-array type beats a contradictory
|
|
49
|
+
* collection location, and a re-derivation that read the other field would
|
|
50
|
+
* disagree with the compiler on a committed fixture.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkExpressionQuantifierOverNonCollection(expression: Expression, artifactPath: string, scope: {
|
|
53
|
+
readonly operation: AnyOperation;
|
|
54
|
+
readonly legIds: readonly string[];
|
|
55
|
+
}): void;
|
|
8
56
|
/** Checks response-body collection pointers, after bound-element substitution. */
|
|
9
57
|
export declare function checkQuantifierOverNonCollection(contract: EvalContract): void;
|
|
10
58
|
/**
|
|
@@ -12,3 +60,4 @@ export declare function checkQuantifierOverNonCollection(contract: EvalContract)
|
|
|
12
60
|
* This preserves legal identifiers such as `constructor`.
|
|
13
61
|
*/
|
|
14
62
|
export declare function checkReferenceSetResolution(contract: EvalContract): void;
|
|
63
|
+
export {};
|