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,383 @@
|
|
|
1
|
+
import { parseEvidenceTarget, resolveOperation, resolveStep, } from './plan-index.js';
|
|
2
|
+
function resolveEvidenceTarget(pointer, index) {
|
|
3
|
+
const target = parseEvidenceTarget(pointer);
|
|
4
|
+
const step = resolveStep(index, target.stepId);
|
|
5
|
+
const operation = resolveOperation(index, step.operationId);
|
|
6
|
+
return { target, step, operation };
|
|
7
|
+
}
|
|
8
|
+
// ---- joining ---------------------------------------------------------
|
|
9
|
+
function joinWithAnd(items) {
|
|
10
|
+
const [first, ...rest] = items;
|
|
11
|
+
if (first === undefined)
|
|
12
|
+
return '';
|
|
13
|
+
if (rest.length === 0)
|
|
14
|
+
return first;
|
|
15
|
+
const last = rest[rest.length - 1];
|
|
16
|
+
if (rest.length === 1)
|
|
17
|
+
return `${first} and ${last}`;
|
|
18
|
+
const middle = rest.slice(0, -1);
|
|
19
|
+
return `${[first, ...middle].join(', ')}, and ${last}`;
|
|
20
|
+
}
|
|
21
|
+
// ---- operation identity -----------------------------------------------
|
|
22
|
+
// Humanizing a kebab-case operationId is injective on distinct ids, and
|
|
23
|
+
// `buildPlanIndex` already rejects a duplicate `operationId` across
|
|
24
|
+
// interfaces, so two resolved operations never share this phrase. `method`
|
|
25
|
+
// and `pathTemplate` are never printed here: AD-16 withholds the operation
|
|
26
|
+
// inventory from the brief.
|
|
27
|
+
function operationReference(operation) {
|
|
28
|
+
return `the ${operation.operationId.split('-').join(' ')} endpoint`;
|
|
29
|
+
}
|
|
30
|
+
const ESCALATION_LEVELS = [
|
|
31
|
+
'generic',
|
|
32
|
+
'kind',
|
|
33
|
+
'literal',
|
|
34
|
+
];
|
|
35
|
+
const TRANSPORT_ORDER = [
|
|
36
|
+
'path',
|
|
37
|
+
'query',
|
|
38
|
+
'header',
|
|
39
|
+
'body',
|
|
40
|
+
];
|
|
41
|
+
// Sorted by transport channel in fixed order, then by key name, so this
|
|
42
|
+
// never depends on a binding map's insertion order and the rendered prose
|
|
43
|
+
// stays permutation-invariant.
|
|
44
|
+
function bindingEntries(step) {
|
|
45
|
+
const entries = [];
|
|
46
|
+
for (const transportChannel of TRANSPORT_ORDER) {
|
|
47
|
+
const map = step.inputBinding[transportChannel];
|
|
48
|
+
if (map === null)
|
|
49
|
+
continue;
|
|
50
|
+
for (const key of Object.keys(map).sort()) {
|
|
51
|
+
const value = map[key];
|
|
52
|
+
if (value !== undefined)
|
|
53
|
+
entries.push({ transportChannel, key, value });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
// The transport channel is part of the rendered name as well as the sort
|
|
59
|
+
// key: two bindings can share a parameter name across channels (path.id and
|
|
60
|
+
// query.id), and without the qualifier both would render as "the supplied
|
|
61
|
+
// id", hiding two different bindings behind identical text.
|
|
62
|
+
function entryName(entry) {
|
|
63
|
+
return `${entry.transportChannel} ${entry.key}`;
|
|
64
|
+
}
|
|
65
|
+
function isTypeViolating(value) {
|
|
66
|
+
return 'matcher' in value && value.matcher === 'type-violating';
|
|
67
|
+
}
|
|
68
|
+
// Matches `canonicalize.ts`'s key-sorting rule (AD-27) so two contracts
|
|
69
|
+
// differing only in one literal binding's declared key order still render
|
|
70
|
+
// identical prose from that one canonical digest.
|
|
71
|
+
function canonicalizeForDisplay(value) {
|
|
72
|
+
if (Array.isArray(value))
|
|
73
|
+
return value.map(canonicalizeForDisplay);
|
|
74
|
+
if (value !== null && typeof value === 'object') {
|
|
75
|
+
const sorted = {};
|
|
76
|
+
for (const key of Object.keys(value).sort()) {
|
|
77
|
+
sorted[key] = canonicalizeForDisplay(value[key]);
|
|
78
|
+
}
|
|
79
|
+
return sorted;
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function formatLiteral(literal) {
|
|
84
|
+
return JSON.stringify(canonicalizeForDisplay(literal));
|
|
85
|
+
}
|
|
86
|
+
// Base rendering: `type-violating` always names the input malformed.
|
|
87
|
+
// `any` and, at the 'generic' level, `literal` share the same generic wording
|
|
88
|
+
// on purpose; the escalation ladder below tells them apart once two steps
|
|
89
|
+
// would otherwise collide.
|
|
90
|
+
function renderBindingValue(entry, level) {
|
|
91
|
+
const name = entryName(entry);
|
|
92
|
+
if (isTypeViolating(entry.value)) {
|
|
93
|
+
return `a malformed ${name} value`;
|
|
94
|
+
}
|
|
95
|
+
if (level === 'generic' || 'matcher' in entry.value) {
|
|
96
|
+
return `the supplied ${name}`;
|
|
97
|
+
}
|
|
98
|
+
if (level === 'kind') {
|
|
99
|
+
return `the stated ${name}`;
|
|
100
|
+
}
|
|
101
|
+
return `the ${name} ${formatLiteral(entry.value.literal)}`;
|
|
102
|
+
}
|
|
103
|
+
// Every declared binding key escalates together: narrowing to only the
|
|
104
|
+
// discriminating key would make one step's shown detail depend on which
|
|
105
|
+
// other steps happen to collide with it elsewhere. Naming a step's own
|
|
106
|
+
// bound keys is not the operation inventory AD-16 withholds; that scope is
|
|
107
|
+
// which other calls exist, not this call's own fields.
|
|
108
|
+
//
|
|
109
|
+
// When a type-violating binding is present, the clause names only that one:
|
|
110
|
+
// it is the semantically salient binding, matching AD-16's worked example.
|
|
111
|
+
function bindingClause(step, level) {
|
|
112
|
+
const entries = bindingEntries(step);
|
|
113
|
+
if (entries.length === 0)
|
|
114
|
+
return null;
|
|
115
|
+
const malformed = entries.filter((entry) => isTypeViolating(entry.value));
|
|
116
|
+
const chosen = malformed.length > 0 ? malformed : entries;
|
|
117
|
+
return `with ${joinWithAnd(chosen.map((entry) => renderBindingValue(entry, level)))}`;
|
|
118
|
+
}
|
|
119
|
+
// Parenthesized rather than comma-joined onto the operation reference: a
|
|
120
|
+
// step reference can itself be joined with other phrases at a higher level
|
|
121
|
+
// (`joinWithAnd`, or a temporal pair's "compared with"), and `bindingClause`
|
|
122
|
+
// already carries its own internal ", and" separators. A second comma layer
|
|
123
|
+
// on top would produce indistinguishable comma soup once two or more
|
|
124
|
+
// phrases are joined.
|
|
125
|
+
function stepReferenceAtLevel(step, operation, level) {
|
|
126
|
+
const base = operationReference(operation);
|
|
127
|
+
const clause = bindingClause(step, level);
|
|
128
|
+
return clause === null ? base : `${base} (${clause})`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Escalates through `ESCALATION_LEVELS` in order until every step in
|
|
132
|
+
* `siblings` renders distinctly.
|
|
133
|
+
*
|
|
134
|
+
* `siblings` is direction-scoped: the caller passes only the steps the
|
|
135
|
+
* current direction's own resolved evidence targets share this operation
|
|
136
|
+
* with, not every step in the plan (AD-16), so an unrelated collision
|
|
137
|
+
* elsewhere never blocks an otherwise renderable direction.
|
|
138
|
+
*
|
|
139
|
+
* Throws the same precondition-violation `TypeError` as `buildPlanIndex`'s
|
|
140
|
+
* duplicate `stepId` check if two siblings still collide after full
|
|
141
|
+
* escalation.
|
|
142
|
+
*/
|
|
143
|
+
export function renderStepReference(step, operation, siblings) {
|
|
144
|
+
for (const level of ESCALATION_LEVELS) {
|
|
145
|
+
const phrases = siblings.map((sibling) => stepReferenceAtLevel(sibling, operation, level));
|
|
146
|
+
if (new Set(phrases).size === phrases.length) {
|
|
147
|
+
return stepReferenceAtLevel(step, operation, level);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw new TypeError(`two or more steps invoking operation "${operation.operationId}" that this direction references render to the same derived reference even fully escalated; the declared structure does not distinguish them`);
|
|
151
|
+
}
|
|
152
|
+
function siblingsByOperation(resolved) {
|
|
153
|
+
const map = new Map();
|
|
154
|
+
const seenSteps = new Set();
|
|
155
|
+
for (const entry of resolved) {
|
|
156
|
+
if (seenSteps.has(entry.step.stepId))
|
|
157
|
+
continue;
|
|
158
|
+
seenSteps.add(entry.step.stepId);
|
|
159
|
+
const list = map.get(entry.operation.operationId);
|
|
160
|
+
if (list === undefined)
|
|
161
|
+
map.set(entry.operation.operationId, [entry.step]);
|
|
162
|
+
else
|
|
163
|
+
list.push(entry.step);
|
|
164
|
+
}
|
|
165
|
+
return map;
|
|
166
|
+
}
|
|
167
|
+
// ---- the channel framing ----------------------------------------------
|
|
168
|
+
// An empty token is RFC 6901's legal spelling for a zero-length key but
|
|
169
|
+
// names no field worth naming, so it is filtered out rather than spliced in
|
|
170
|
+
// verbatim (which would produce a blank "its field" or a stray "."). The
|
|
171
|
+
// remaining segments join by "." rather than keeping only the last, so two
|
|
172
|
+
// nested pointers sharing one leaf field name (`.../a/id` and `.../b/id`)
|
|
173
|
+
// never collapse to the same phrase.
|
|
174
|
+
function fieldPath(tail) {
|
|
175
|
+
const named = tail.filter((token) => token !== '');
|
|
176
|
+
return named.length === 0 ? null : named.join('.');
|
|
177
|
+
}
|
|
178
|
+
function localTargetPhrase(target) {
|
|
179
|
+
const field = fieldPath(target.tail);
|
|
180
|
+
switch (target.channel) {
|
|
181
|
+
case 'response-status':
|
|
182
|
+
return 'its transport status';
|
|
183
|
+
case 'exit-code':
|
|
184
|
+
return 'its exit code';
|
|
185
|
+
case 'call-inputs': {
|
|
186
|
+
if (target.transportChannel === null) {
|
|
187
|
+
// Unreachable: `parseEvidenceTarget` sets this exactly when the
|
|
188
|
+
// channel is 'call-inputs'.
|
|
189
|
+
throw new TypeError('call-inputs evidence target carries no transport channel');
|
|
190
|
+
}
|
|
191
|
+
// The transport channel is named here too, not only in the no-tail
|
|
192
|
+
// fallback below: same path.id / query.id collision `entryName`
|
|
193
|
+
// above guards against.
|
|
194
|
+
return field !== null
|
|
195
|
+
? `the ${target.transportChannel} ${field} value you sent`
|
|
196
|
+
: `the ${target.transportChannel} you sent`;
|
|
197
|
+
}
|
|
198
|
+
case 'response-body':
|
|
199
|
+
return field !== null
|
|
200
|
+
? `its ${field} field`
|
|
201
|
+
: 'the response body you obtained';
|
|
202
|
+
case 'response-headers':
|
|
203
|
+
return field !== null
|
|
204
|
+
? `its ${field} header`
|
|
205
|
+
: 'the response headers you obtained';
|
|
206
|
+
case 'stdout':
|
|
207
|
+
// Distinguished from response-body's "field" wording and from
|
|
208
|
+
// stderr below, so two channels naming the same field never render
|
|
209
|
+
// identical text.
|
|
210
|
+
return field !== null
|
|
211
|
+
? `the ${field} field of its standard output`
|
|
212
|
+
: 'the standard output you obtained';
|
|
213
|
+
case 'stderr':
|
|
214
|
+
return field !== null
|
|
215
|
+
? `the ${field} field of its standard error`
|
|
216
|
+
: 'the standard error you obtained';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function fullTargetPhrase(resolved, siblingsOf) {
|
|
220
|
+
const local = localTargetPhrase(resolved.target);
|
|
221
|
+
const stepRef = renderStepReference(resolved.step, resolved.operation, siblingsOf(resolved.operation.operationId));
|
|
222
|
+
const preposition = resolved.target.channel === 'call-inputs' ? 'to' : 'from';
|
|
223
|
+
return `${local} ${preposition} ${stepRef}`;
|
|
224
|
+
}
|
|
225
|
+
// Encoded as a JSON array rather than a `/`-joined string: a `/`-join would
|
|
226
|
+
// conflate a tail token containing a literal `/` (reachable via the RFC 6901
|
|
227
|
+
// `~1` escape, e.g. `['a/b']`) with an equivalent multi-segment tail (`['a',
|
|
228
|
+
// 'b']`). Both would join to the same `".../a/b"` string, producing a false
|
|
229
|
+
// tie in `sentFirstOrder` below.
|
|
230
|
+
function channelSignature(target) {
|
|
231
|
+
return JSON.stringify([target.channel, target.transportChannel, target.tail]);
|
|
232
|
+
}
|
|
233
|
+
// A total order over one pair, independent of argument order. The final
|
|
234
|
+
// reference-comparison rung fires only on a genuine tie, such as the same
|
|
235
|
+
// field read from two different `after`-linked steps' response bodies; it
|
|
236
|
+
// never degenerates to argument order, since colliding same-operation
|
|
237
|
+
// siblings already throw in `renderStepReference`, and different operations
|
|
238
|
+
// always humanize to different names.
|
|
239
|
+
function sentFirstOrder(a, b, siblingsOf) {
|
|
240
|
+
const rank = (r) => r.target.channel === 'call-inputs' ? 0 : 1;
|
|
241
|
+
const rankA = rank(a);
|
|
242
|
+
const rankB = rank(b);
|
|
243
|
+
if (rankA !== rankB)
|
|
244
|
+
return rankA < rankB ? [a, b] : [b, a];
|
|
245
|
+
const signatureA = channelSignature(a.target);
|
|
246
|
+
const signatureB = channelSignature(b.target);
|
|
247
|
+
if (signatureA !== signatureB)
|
|
248
|
+
return signatureA < signatureB ? [a, b] : [b, a];
|
|
249
|
+
const referenceA = renderStepReference(a.step, a.operation, siblingsOf(a.operation.operationId));
|
|
250
|
+
const referenceB = renderStepReference(b.step, b.operation, siblingsOf(b.operation.operationId));
|
|
251
|
+
return referenceA <= referenceB ? [a, b] : [b, a];
|
|
252
|
+
}
|
|
253
|
+
// Two evidence targets whose steps are declared in an `after` relationship
|
|
254
|
+
// render as one relational phrase, so concatenation never discloses which
|
|
255
|
+
// step came first. A same-step multi-target group stays `single` regardless
|
|
256
|
+
// of `after`; forcing a pair there would guess at which target on each side
|
|
257
|
+
// pairs with which.
|
|
258
|
+
//
|
|
259
|
+
// Pairing is resolved from sorted step ids, never from discovery order over
|
|
260
|
+
// `resolved`, so it stays permutation-invariant: when a predecessor is named
|
|
261
|
+
// by more than one successor (AD-39 admits this), the winner is the successor
|
|
262
|
+
// whose own stepId sorts first, and the losing successor renders as its own
|
|
263
|
+
// `single` group.
|
|
264
|
+
function groupResolvedTargets(resolved) {
|
|
265
|
+
const byStep = new Map();
|
|
266
|
+
for (const entry of resolved) {
|
|
267
|
+
const list = byStep.get(entry.step.stepId);
|
|
268
|
+
if (list === undefined)
|
|
269
|
+
byStep.set(entry.step.stepId, [entry]);
|
|
270
|
+
else
|
|
271
|
+
list.push(entry);
|
|
272
|
+
}
|
|
273
|
+
const stepIds = [...byStep.keys()].sort();
|
|
274
|
+
const singleTargetStepIds = new Set(stepIds.filter((id) => byStep.get(id)?.length === 1));
|
|
275
|
+
// Every eligible successor, grouped by the predecessor its `after` names.
|
|
276
|
+
const successorsOf = new Map();
|
|
277
|
+
for (const stepId of singleTargetStepIds) {
|
|
278
|
+
const step = byStep.get(stepId)?.[0]?.step;
|
|
279
|
+
const after = step?.after ?? null;
|
|
280
|
+
if (after === null || !singleTargetStepIds.has(after))
|
|
281
|
+
continue;
|
|
282
|
+
const successors = successorsOf.get(after);
|
|
283
|
+
if (successors === undefined)
|
|
284
|
+
successorsOf.set(after, [stepId]);
|
|
285
|
+
else
|
|
286
|
+
successors.push(stepId);
|
|
287
|
+
}
|
|
288
|
+
// One winner per predecessor: the successor whose own stepId sorts first.
|
|
289
|
+
const chosenSuccessorOf = new Map();
|
|
290
|
+
for (const predecessorId of stepIds) {
|
|
291
|
+
const successors = successorsOf.get(predecessorId);
|
|
292
|
+
const winner = successors === undefined ? undefined : [...successors].sort()[0];
|
|
293
|
+
if (winner !== undefined)
|
|
294
|
+
chosenSuccessorOf.set(predecessorId, winner);
|
|
295
|
+
}
|
|
296
|
+
// `chosenSuccessorOf` maps each predecessor to at most one successor, and
|
|
297
|
+
// each successor wins at most one predecessor, so it forms a disjoint
|
|
298
|
+
// union of simple chains (or cycles, on a malformed `after` graph).
|
|
299
|
+
// Walking only from steps absent from `chosenAsSuccessor` starts at true
|
|
300
|
+
// chain roots and can never enter a cycle, since every cycle node is by
|
|
301
|
+
// construction someone's chosen successor.
|
|
302
|
+
//
|
|
303
|
+
// Each root is walked and paired every other link: (1st, 2nd), (3rd,
|
|
304
|
+
// 4th), with a leftover final step left to render alone. This replaced an
|
|
305
|
+
// earlier single-link collapse that silently dropped a chain's trailing
|
|
306
|
+
// links. `pairedAway` marks each pair's second half so the final loop
|
|
307
|
+
// below skips rendering it standalone.
|
|
308
|
+
const chosenAsSuccessor = new Set(chosenSuccessorOf.values());
|
|
309
|
+
const pairPartnerOf = new Map();
|
|
310
|
+
const pairedAway = new Set();
|
|
311
|
+
for (const stepId of stepIds) {
|
|
312
|
+
if (chosenAsSuccessor.has(stepId))
|
|
313
|
+
continue; // reached by walking its root below
|
|
314
|
+
let predecessorId = stepId;
|
|
315
|
+
while (predecessorId !== undefined) {
|
|
316
|
+
const successorId = chosenSuccessorOf.get(predecessorId);
|
|
317
|
+
if (successorId === undefined)
|
|
318
|
+
break;
|
|
319
|
+
pairPartnerOf.set(predecessorId, successorId);
|
|
320
|
+
pairedAway.add(successorId);
|
|
321
|
+
predecessorId = chosenSuccessorOf.get(successorId);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const groups = [];
|
|
325
|
+
for (const stepId of stepIds) {
|
|
326
|
+
// Rendered as part of its predecessor's pair elsewhere in this loop.
|
|
327
|
+
if (pairedAway.has(stepId))
|
|
328
|
+
continue;
|
|
329
|
+
const list = byStep.get(stepId);
|
|
330
|
+
if (list === undefined)
|
|
331
|
+
continue;
|
|
332
|
+
const successorId = pairPartnerOf.get(stepId);
|
|
333
|
+
const first = list[0];
|
|
334
|
+
const partnerFirst = successorId === undefined ? undefined : byStep.get(successorId)?.[0];
|
|
335
|
+
if (first !== undefined && partnerFirst !== undefined) {
|
|
336
|
+
groups.push({ kind: 'temporal-pair', a: first, b: partnerFirst });
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
groups.push({ kind: 'single', resolved: list });
|
|
340
|
+
}
|
|
341
|
+
return groups;
|
|
342
|
+
}
|
|
343
|
+
function renderPhraseGroup(group, siblingsOf) {
|
|
344
|
+
if (group.kind === 'temporal-pair') {
|
|
345
|
+
const [first, second] = sentFirstOrder(group.a, group.b, siblingsOf);
|
|
346
|
+
return `${fullTargetPhrase(first, siblingsOf)}, compared with ${fullTargetPhrase(second, siblingsOf)}`;
|
|
347
|
+
}
|
|
348
|
+
const first = group.resolved[0];
|
|
349
|
+
if (first === undefined) {
|
|
350
|
+
throw new TypeError('evidence-target group is empty');
|
|
351
|
+
}
|
|
352
|
+
if (group.resolved.length === 1) {
|
|
353
|
+
return fullTargetPhrase(first, siblingsOf);
|
|
354
|
+
}
|
|
355
|
+
const stepRef = renderStepReference(first.step, first.operation, siblingsOf(first.operation.operationId));
|
|
356
|
+
// Sorted, so a same-step group's field order is permutation-invariant like
|
|
357
|
+
// the top-level join below; in declaration order, permuting
|
|
358
|
+
// `evidenceTargets` would change which field is named first here.
|
|
359
|
+
const locals = group.resolved
|
|
360
|
+
.map((entry) => localTargetPhrase(entry.target))
|
|
361
|
+
.sort();
|
|
362
|
+
return `${stepRef}: ${joinWithAnd(locals)}`;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Renders every evidence target a direction declares as one canonically
|
|
366
|
+
* ordered, order-independent clause: byte-identical under any permutation of
|
|
367
|
+
* `evidenceTargets`, `interactionPlan`, or `permittedInterfaces`, since every
|
|
368
|
+
* phrase comes from Map-keyed lookups and structural properties, never array
|
|
369
|
+
* position.
|
|
370
|
+
*
|
|
371
|
+
* A repeated pointer is deduplicated first: left in place it would push its
|
|
372
|
+
* step out of the "exactly one target" shape temporal pairing requires,
|
|
373
|
+
* silently disabling the relational phrase for an otherwise-valid pair.
|
|
374
|
+
*/
|
|
375
|
+
export function renderEvidenceReferences(pointers, index) {
|
|
376
|
+
const uniquePointers = [...new Set(pointers)];
|
|
377
|
+
const resolved = uniquePointers.map((pointer) => resolveEvidenceTarget(pointer, index));
|
|
378
|
+
const siblings = siblingsByOperation(resolved);
|
|
379
|
+
const siblingsOf = (operationId) => siblings.get(operationId) ?? [];
|
|
380
|
+
const groups = groupResolvedTargets(resolved);
|
|
381
|
+
const phrases = groups.map((group) => renderPhraseGroup(group, siblingsOf));
|
|
382
|
+
return joinWithAnd([...phrases].sort());
|
|
383
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Direction } from '../schemas/oracle.ts';
|
|
2
|
+
import type { PlanIndex } from './plan-index.ts';
|
|
3
|
+
/**
|
|
4
|
+
* An empty `evidenceTargets` array reaches here on a contract that compiles
|
|
5
|
+
* clean: `core/schemas/oracle.ts` puts no `.min(1)` on the array, and AD-3's
|
|
6
|
+
* alignment predicate (`core/compile/oracle-alignment.ts`) is a universal
|
|
7
|
+
* over it, so an empty list is contained in any `check` vacuously. Rendering
|
|
8
|
+
* has nothing to name, so this throws the same precondition-violation
|
|
9
|
+
* `TypeError` as the other should-never-happen shapes in `core/seal/`, and
|
|
10
|
+
* `application/seal.ts` converts it to `RuntimeFault('schema-parse-failure')`
|
|
11
|
+
* at the boundary.
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderDirectionText(direction: Direction, index: PlanIndex): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { renderEvidenceReferences } from './derived-reference.js';
|
|
2
|
+
// Uppercases only the first letter found, so an author-written `scope` or
|
|
3
|
+
// `negativeDomain` string that starts lowercase still reads as its own
|
|
4
|
+
// sentence once joined after another.
|
|
5
|
+
function capitalizeFirst(text) {
|
|
6
|
+
const match = /[a-zA-Z]/.exec(text);
|
|
7
|
+
if (match === null)
|
|
8
|
+
return text;
|
|
9
|
+
const index = match.index;
|
|
10
|
+
const char = text[index];
|
|
11
|
+
if (char === undefined)
|
|
12
|
+
return text;
|
|
13
|
+
return `${text.slice(0, index)}${char.toUpperCase()}${text.slice(index + 1)}`;
|
|
14
|
+
}
|
|
15
|
+
const ensureSentence = (text) => {
|
|
16
|
+
const capitalized = capitalizeFirst(text.trim());
|
|
17
|
+
return /[.!?]$/.test(capitalized) ? capitalized : `${capitalized}.`;
|
|
18
|
+
};
|
|
19
|
+
// `for-all`/`for-any` carry no nested predicate on `Direction`; that lives on
|
|
20
|
+
// `check`, which this module does not read. So the claim names the
|
|
21
|
+
// quantification itself, saying nothing about a condition it cannot see.
|
|
22
|
+
function quantifierClaim(relation, evidence) {
|
|
23
|
+
return relation === 'for-all'
|
|
24
|
+
? `Every element reachable through ${evidence} is asserted to meet the declared condition.`
|
|
25
|
+
: `At least one element reachable through ${evidence} is asserted to meet the declared condition.`;
|
|
26
|
+
}
|
|
27
|
+
// A shared affirmative skeleton over `not` would tell the sealed evaluator
|
|
28
|
+
// the opposite of the declared claim.
|
|
29
|
+
function connectiveClaim(relation, evidence) {
|
|
30
|
+
switch (relation) {
|
|
31
|
+
case 'all':
|
|
32
|
+
return `${evidence} is asserted to satisfy every declared condition together.`;
|
|
33
|
+
case 'any':
|
|
34
|
+
return `${evidence} is asserted to satisfy at least one declared condition.`;
|
|
35
|
+
case 'not':
|
|
36
|
+
return `${evidence} is asserted to fail the declared condition, not to satisfy it.`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function presenceClaim(relation, evidence) {
|
|
40
|
+
return relation === 'existence'
|
|
41
|
+
? `${evidence} is asserted to be present.`
|
|
42
|
+
: `${evidence} is asserted to be absent.`;
|
|
43
|
+
}
|
|
44
|
+
function comparisonClaim(relation, evidence) {
|
|
45
|
+
switch (relation) {
|
|
46
|
+
case 'equality':
|
|
47
|
+
return `${evidence} is asserted to be equal.`;
|
|
48
|
+
case 'deep-equality':
|
|
49
|
+
return `${evidence} is asserted to be deeply, structurally equal.`;
|
|
50
|
+
case 'containment':
|
|
51
|
+
return `${evidence} is asserted to contain the declared member.`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function structuralClaim(relation, evidence) {
|
|
55
|
+
return `${evidence} is asserted to satisfy the declared "${relation}" condition.`;
|
|
56
|
+
}
|
|
57
|
+
function renderRelationClaim(relation, evidence) {
|
|
58
|
+
if (relation === 'for-all' || relation === 'for-any') {
|
|
59
|
+
return quantifierClaim(relation, evidence);
|
|
60
|
+
}
|
|
61
|
+
if (relation === 'all' || relation === 'any' || relation === 'not') {
|
|
62
|
+
return connectiveClaim(relation, evidence);
|
|
63
|
+
}
|
|
64
|
+
if (relation === 'existence' || relation === 'absence') {
|
|
65
|
+
return presenceClaim(relation, evidence);
|
|
66
|
+
}
|
|
67
|
+
if (relation === 'equality' ||
|
|
68
|
+
relation === 'deep-equality' ||
|
|
69
|
+
relation === 'containment') {
|
|
70
|
+
return comparisonClaim(relation, evidence);
|
|
71
|
+
}
|
|
72
|
+
return structuralClaim(relation, evidence);
|
|
73
|
+
}
|
|
74
|
+
function renderPolarityClause(polarity) {
|
|
75
|
+
return polarity === 'expects-hold'
|
|
76
|
+
? 'The declared polarity expects this relation to hold.'
|
|
77
|
+
: 'The declared polarity expects this relation to be a violation.';
|
|
78
|
+
}
|
|
79
|
+
// `negativeDomain` is author-written, evaluator-facing free text.
|
|
80
|
+
// Any trailing terminator the author already wrote is stripped first, since
|
|
81
|
+
// the text becomes the subject of a larger sentence rather than standing
|
|
82
|
+
// alone: an author-terminated string would otherwise read "...did not
|
|
83
|
+
// return. is treated as a defect." (a mid-sentence period followed by a
|
|
84
|
+
// lowercase clause).
|
|
85
|
+
function renderNegativeDomainClause(negativeDomain) {
|
|
86
|
+
const trimmed = negativeDomain.trim().replace(/[.!?]+$/, '');
|
|
87
|
+
return `${trimmed} is treated as a defect.`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* An empty `evidenceTargets` array reaches here on a contract that compiles
|
|
91
|
+
* clean: `core/schemas/oracle.ts` puts no `.min(1)` on the array, and AD-3's
|
|
92
|
+
* alignment predicate (`core/compile/oracle-alignment.ts`) is a universal
|
|
93
|
+
* over it, so an empty list is contained in any `check` vacuously. Rendering
|
|
94
|
+
* has nothing to name, so this throws the same precondition-violation
|
|
95
|
+
* `TypeError` as the other should-never-happen shapes in `core/seal/`, and
|
|
96
|
+
* `application/seal.ts` converts it to `RuntimeFault('schema-parse-failure')`
|
|
97
|
+
* at the boundary.
|
|
98
|
+
*/
|
|
99
|
+
export function renderDirectionText(direction, index) {
|
|
100
|
+
if (direction.evidenceTargets.length === 0) {
|
|
101
|
+
throw new TypeError('direction carries no evidenceTargets; there is no observation for the generated prose to name');
|
|
102
|
+
}
|
|
103
|
+
const evidence = renderEvidenceReferences(direction.evidenceTargets, index);
|
|
104
|
+
const parts = [
|
|
105
|
+
ensureSentence(renderRelationClaim(direction.relation, evidence)),
|
|
106
|
+
ensureSentence(renderPolarityClause(direction.polarity)),
|
|
107
|
+
];
|
|
108
|
+
// `null` and `''` both drop the clause: the schema allows `''` as distinct
|
|
109
|
+
// from `null` with no minimum length, and an absent clause is omitted
|
|
110
|
+
// rather than rendered as an empty sentence. Checking `!== null` alone
|
|
111
|
+
// would still render that empty sentence for `''`.
|
|
112
|
+
if (direction.scope !== null && direction.scope.trim() !== '') {
|
|
113
|
+
parts.push(ensureSentence(direction.scope));
|
|
114
|
+
}
|
|
115
|
+
if (direction.negativeDomain !== null &&
|
|
116
|
+
direction.negativeDomain.trim() !== '') {
|
|
117
|
+
parts.push(ensureSentence(renderNegativeDomainClause(direction.negativeDomain)));
|
|
118
|
+
}
|
|
119
|
+
return parts.join(' ');
|
|
120
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** resolves a pointer to its step and operation; nothing about reachability. */
|
|
2
|
+
import type { Operation, PermittedInterface } from '../schemas/interface.ts';
|
|
3
|
+
import type { InteractionStep } from '../schemas/plan.ts';
|
|
4
|
+
import { type EvidenceChannelName, type TransportChannelName } from '../schemas/pointer.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Exported so `core/evaluate/evidence-resolution.ts` decodes pointer tails
|
|
7
|
+
* with these same two functions, avoiding a second private copy (the drift
|
|
8
|
+
* `IDENTIFIER_CHARSET_SOURCE`'s own precedent warns against).
|
|
9
|
+
*/
|
|
10
|
+
export declare const decodeToken: (token: string) => string;
|
|
11
|
+
export declare const decodeTail: (tailSource: string) => readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* One evidence target, resolved locally to its step id and channel. The
|
|
14
|
+
* channel decides whether the rendered phrase says "the response you
|
|
15
|
+
* obtained" or "the value you sent".
|
|
16
|
+
*/
|
|
17
|
+
export type EvidenceTarget = {
|
|
18
|
+
stepId: string;
|
|
19
|
+
channel: EvidenceChannelName;
|
|
20
|
+
transportChannel: TransportChannelName | null;
|
|
21
|
+
tail: readonly string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Parses one `InteractionPointer` string (`INTERACTION_POINTER_PATTERN` in
|
|
25
|
+
* `pointer.ts`) into its step id, channel, transport channel, and tail, using
|
|
26
|
+
* the schema's own channel partition so this accepts exactly what
|
|
27
|
+
* `InteractionPointer.safeParse` accepts. A should-never-happen precondition
|
|
28
|
+
* violation throws `TypeError`, per `digest.ts`'s precedent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseEvidenceTarget(pointer: string): EvidenceTarget;
|
|
31
|
+
/**
|
|
32
|
+
* What a pointer names: step, operation, and every step naming a given
|
|
33
|
+
* operation, which `derived-reference.ts`'s escalation needs. Says nothing
|
|
34
|
+
* about reachability or channel typing; the general addressing-grammar
|
|
35
|
+
* resolver lives in `core/evaluate/evidence-resolution.ts` and reachability
|
|
36
|
+
* in `core/compile/reachability.ts`.
|
|
37
|
+
*/
|
|
38
|
+
export type PlanIndex = {
|
|
39
|
+
stepOf: (stepId: string) => InteractionStep | undefined;
|
|
40
|
+
operationOf: (operationId: string) => Operation | undefined;
|
|
41
|
+
stepsUsing: (operationId: string) => readonly InteractionStep[];
|
|
42
|
+
};
|
|
43
|
+
export type PlanIndexOptions = {
|
|
44
|
+
duplicateIds?: 'throw' | 'unresolved';
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Builds the index once over the whole plan and interface set. Neither
|
|
48
|
+
* schema enforces `stepId`/`operationId` uniqueness. Strict callers keep the
|
|
49
|
+
* default throw instead of resolving by array order. Standalone structural
|
|
50
|
+
* checks can select `unresolved`, which removes every ambiguous identifier
|
|
51
|
+
* from lookup while preserving all unambiguous entries.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildPlanIndex(interactionPlan: readonly InteractionStep[], permittedInterfaces: readonly PermittedInterface[], options?: PlanIndexOptions): PlanIndex;
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a step id through the index or throws. Split from `stepOf` so the
|
|
56
|
+
* index itself stays a plain lookup (`| undefined`, per
|
|
57
|
+
* `noUncheckedIndexedAccess`) while callers get one function instead of
|
|
58
|
+
* repeating the `undefined` check. A precondition violation, not a
|
|
59
|
+
* `RuntimeFault`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveStep(index: PlanIndex, stepId: string): InteractionStep;
|
|
62
|
+
/** Resolves an operation id through the index or throws. See `resolveStep`. */
|
|
63
|
+
export declare function resolveOperation(index: PlanIndex, operationId: string): Operation;
|