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
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The derived-reference vocabulary (AD-16). Renders an evidence-target
|
|
3
|
+
* pointer, or a temporally-paired pair, as a description of the step's
|
|
4
|
+
* operation and selection predicate; the step's own identifier never appears.
|
|
5
|
+
* Resolves pointers through a `PlanIndex` into a phrase, kept apart from
|
|
6
|
+
* `plan-index.ts`'s resolving and `direction-prose.ts`'s relation templates.
|
|
7
|
+
*/
|
|
8
|
+
import { boundChannelsOf, isCommandOperation } from '../declared-inputs.js';
|
|
9
|
+
import { StructuralFailure } from '../failure-codes.js';
|
|
10
|
+
import { anyOperationOf, parseEvidenceTarget, resolveStep, } from './plan-index.js';
|
|
2
11
|
function resolveEvidenceTarget(pointer, index) {
|
|
3
12
|
const target = parseEvidenceTarget(pointer);
|
|
4
13
|
const step = resolveStep(index, target.stepId);
|
|
5
|
-
const operation =
|
|
14
|
+
const operation = anyOperationOf(index, step.operationId);
|
|
15
|
+
if (operation === undefined) {
|
|
16
|
+
throw new TypeError(`step names an operation the permitted interfaces do not declare: ${step.operationId}`);
|
|
17
|
+
}
|
|
6
18
|
return { target, step, operation };
|
|
7
19
|
}
|
|
8
20
|
// ---- joining ---------------------------------------------------------
|
|
@@ -21,30 +33,31 @@ function joinWithAnd(items) {
|
|
|
21
33
|
// ---- operation identity -----------------------------------------------
|
|
22
34
|
// Humanizing a kebab-case operationId is injective on distinct ids, and
|
|
23
35
|
// `buildPlanIndex` already rejects a duplicate `operationId` across
|
|
24
|
-
// interfaces, so two resolved operations never share this phrase.
|
|
25
|
-
//
|
|
26
|
-
// inventory from the brief
|
|
36
|
+
// interfaces, so two resolved operations never share this phrase. The
|
|
37
|
+
// transport identity is never printed here, whichever kind it is: AD-16
|
|
38
|
+
// withholds the operation inventory from the brief, so neither a method and a
|
|
39
|
+
// path template nor an executable and a subcommand path reach an evaluator.
|
|
40
|
+
//
|
|
41
|
+
// The noun follows the kind. Calling a command an endpoint told the evaluator
|
|
42
|
+
// something false about what it was reading, and the word is the only thing
|
|
43
|
+
// this phrase says beyond the operation's own name.
|
|
27
44
|
function operationReference(operation) {
|
|
28
|
-
|
|
45
|
+
const name = operation.operationId.split('-').join(' ');
|
|
46
|
+
return isCommandOperation(operation)
|
|
47
|
+
? `the ${name} command`
|
|
48
|
+
: `the ${name} endpoint`;
|
|
29
49
|
}
|
|
30
50
|
const ESCALATION_LEVELS = [
|
|
31
51
|
'generic',
|
|
32
52
|
'kind',
|
|
33
53
|
'literal',
|
|
34
54
|
];
|
|
35
|
-
|
|
36
|
-
'path',
|
|
37
|
-
'query',
|
|
38
|
-
'header',
|
|
39
|
-
'body',
|
|
40
|
-
];
|
|
41
|
-
// Sorted by transport channel in fixed order, then by key name, so this
|
|
55
|
+
// Sorted by input channel in `INPUT_CHANNELS` order, then by key name, so this
|
|
42
56
|
// never depends on a binding map's insertion order and the rendered prose
|
|
43
|
-
// stays permutation-invariant.
|
|
57
|
+
// stays permutation-invariant. `boundChannelsOf` supplies the channel order.
|
|
44
58
|
function bindingEntries(step) {
|
|
45
59
|
const entries = [];
|
|
46
|
-
for (const transportChannel of
|
|
47
|
-
const map = step.inputBinding[transportChannel];
|
|
60
|
+
for (const { channel: transportChannel, bound: map } of boundChannelsOf(step.inputBinding)) {
|
|
48
61
|
if (map === null)
|
|
49
62
|
continue;
|
|
50
63
|
for (const key of Object.keys(map).sort()) {
|
|
@@ -83,22 +96,171 @@ function canonicalizeForDisplay(value) {
|
|
|
83
96
|
function formatLiteral(literal) {
|
|
84
97
|
return JSON.stringify(canonicalizeForDisplay(literal));
|
|
85
98
|
}
|
|
86
|
-
// Base rendering
|
|
87
|
-
// `any` and, at the 'generic' level,
|
|
88
|
-
// on purpose; the escalation ladder below tells them
|
|
89
|
-
// would otherwise collide.
|
|
99
|
+
// Base rendering, one arm per tagged form. `type-violating` always names the
|
|
100
|
+
// input malformed. `any` and, at the 'generic' level, every other form share
|
|
101
|
+
// the same generic wording on purpose; the escalation ladder below tells them
|
|
102
|
+
// apart once two steps would otherwise collide.
|
|
103
|
+
//
|
|
104
|
+
// `captured` escalates to a real derived reference at the 'literal' rung: the
|
|
105
|
+
// local phrase for the pointer's channel and tail, then the referenced step
|
|
106
|
+
// rendered through `stepReferenceAtLevel`, which is the same phrase
|
|
107
|
+
// `fullTargetPhrase` already prints for an evidence target. Naming the
|
|
108
|
+
// referenced OPERATION alone was tried and dropped: two predecessors sharing
|
|
109
|
+
// one operation and one body key then render identically, so two siblings
|
|
110
|
+
// capturing from them collide at every rung and `renderStepReference` throws
|
|
111
|
+
// out of `seal()` on a contract that compiles clean. Recursing into the
|
|
112
|
+
// predecessor separates them by its own binding clause. Two predecessors that
|
|
113
|
+
// are themselves irreducible still tie, and the throw there is correct: the
|
|
114
|
+
// declared structure does not distinguish them, which is the same answer
|
|
115
|
+
// `irreducibleCollisionPair` already gets. That throw is a coded
|
|
116
|
+
// `StructuralFailure` under `irreducible-step-reference`, and it is
|
|
117
|
+
// hoisted to compile time by `checkStepReferenceReducibility`, which runs this
|
|
118
|
+
// same ladder and reports the same code before `seal` is ever called.
|
|
119
|
+
//
|
|
120
|
+
// It does NOT call `renderStepReference`, so none of that function's
|
|
121
|
+
// constraints apply: no direction-scoped sibling list is consulted, so AD-16's
|
|
122
|
+
// scoping stays intact, and there is no tie to throw on. A step identifier
|
|
123
|
+
// never appears, which is the property AD-16 actually requires.
|
|
124
|
+
//
|
|
125
|
+
// `rendering` holds the steps already on the render path, and a capture back
|
|
126
|
+
// into one of them falls back to the level-independent phrase. A fixed depth
|
|
127
|
+
// bound was tried and dropped: at depth one, a two-link chain whose every link
|
|
128
|
+
// is genuinely distinguishable still collides, because only the immediate
|
|
129
|
+
// predecessor's binding shows and that is the half the two siblings share.
|
|
130
|
+
// Following the chain as far as it goes is what separates them, and the
|
|
131
|
+
// on-path set is what makes that terminate. Each recursion adds one step id, so
|
|
132
|
+
// the depth is bounded by the plan; `plan-exceeds-scripting-bound` bounds that
|
|
133
|
+
// too. Same guard the score module uses for an `after` chain, for the same
|
|
134
|
+
// reason: `binding-cycle` rejects a capture cycle at compile time, so this only
|
|
135
|
+
// covers a plan driven straight into the renderer.
|
|
136
|
+
//
|
|
137
|
+
// A pointer whose step or operation the index cannot resolve falls back to the
|
|
138
|
+
// 'kind' phrase. `seal()` runs after `compile`, which rejects such a pointer
|
|
139
|
+
// under `unreachable-check-evidence`, so this is a fallback for a
|
|
140
|
+
// directly-driven caller and adds no throw path.
|
|
141
|
+
//
|
|
142
|
+
// One thing a grep for step identifiers will find and should not be surprised
|
|
143
|
+
// by: `localTargetPhrase` prints the pointer's tail, and a response-body key
|
|
144
|
+
// the author happened to name the same as a step prints with it. That is the
|
|
145
|
+
// author's own declared key rather than a plan identifier, and it is what every
|
|
146
|
+
// ordinary evidence target has always printed.
|
|
147
|
+
//
|
|
148
|
+
// `principal` tops out at the declared name, which AD-18 keeps an opaque label
|
|
149
|
+
// and is therefore safe on the brief.
|
|
150
|
+
// One or more entries of a step's clause that capture from the same earlier
|
|
151
|
+
// step, rendered together so that step's phrase is expanded once. Expanding it
|
|
152
|
+
// per entry made the clause's size multiply down a capture chain: a step with k
|
|
153
|
+
// entries pointing at its predecessor expanded that predecessor k times, and the
|
|
154
|
+
// predecessor did the same to its own, so a sixteen-step chain with four keys a
|
|
155
|
+
// link produced a phrase past V8's maximum string length. Grouping makes the
|
|
156
|
+
// clause linear in keys and reads better besides.
|
|
157
|
+
//
|
|
158
|
+
// Grouping alone bounds one axis of three, and the size is their product. The
|
|
159
|
+
// other two are the number of DISTINCT predecessors a step references, which
|
|
160
|
+
// doubles per level on a complete capture DAG, and the length of one step's own
|
|
161
|
+
// clause text, which nothing bounds: `formatLiteral` prints a `JsonValue` with
|
|
162
|
+
// no length cap and a binding channel is a record with a minimum of one entry
|
|
163
|
+
// and no maximum. Measured on a sixteen-step complete DAG that passes every
|
|
164
|
+
// plan-side check, the multiplier is 8,192, so a single 65,536-character
|
|
165
|
+
// literal on the seed step, or two hundred ordinary forty-character ones,
|
|
166
|
+
// reaches `RangeError: Invalid string length`.
|
|
167
|
+
//
|
|
168
|
+
// `budget` is what bounds all three at once. Each expansion spends the
|
|
169
|
+
// characters it produced, and once the budget is gone a capture renders as the
|
|
170
|
+
// level-independent phrase it already had. Every `stepReferenceAtLevel` call
|
|
171
|
+
// from `renderStepReference` starts a fresh budget, so one sibling's expansion
|
|
172
|
+
// never degrades another's and the distinctness test stays order-independent.
|
|
173
|
+
//
|
|
174
|
+
// The budget is charged after an expansion rather than before, so the bound is
|
|
175
|
+
// the budget plus whatever one step's own clause text costs; a step binding a
|
|
176
|
+
// single 200,000-character literal renders about 204,000. That is the point of
|
|
177
|
+
// charging late: an expansion cannot be priced before its nested expansions are
|
|
178
|
+
// known, and stopping the NEXT one is what keeps the product finite. Measured
|
|
179
|
+
// on the sixteen-step complete DAG that reached `RangeError` before: 36 KB with
|
|
180
|
+
// a minimal seed, 75 KB with two hundred forty-character literals, 135 KB with
|
|
181
|
+
// a 65,536-character one, and, with a one-million-character literal on every one
|
|
182
|
+
// of its fourteen nodes, 1,000,564. All in single-digit milliseconds. That last
|
|
183
|
+
// row is the one that pins the bound: one literal's worth of overshoot rather
|
|
184
|
+
// than fourteen, which is why the exhaustion test sits in the render pass below
|
|
185
|
+
// rather than where the slots are built.
|
|
186
|
+
//
|
|
187
|
+
// A budget alone would couple things that have nothing to do with each other:
|
|
188
|
+
// growing a literal on a step neither sibling references can starve the
|
|
189
|
+
// expansion that separates them, so an unrelated field decides whether a
|
|
190
|
+
// contract seals. `renderStepReference`'s retry at the ceiling is what removes
|
|
191
|
+
// that, and it fires only when a collision came of a suppressed expansion. What
|
|
192
|
+
// remains is the honest case: siblings the declared structure does not
|
|
193
|
+
// distinguish still throw the precondition `TypeError`, which is the
|
|
194
|
+
// pre-existing failure mode `deferred-work.md` already carries.
|
|
195
|
+
// The ordinary limit, then the ceiling a starved render retries at. No
|
|
196
|
+
// realistic contract reaches the first; only one that would otherwise fail to
|
|
197
|
+
// render at all pays for the second.
|
|
198
|
+
const RENDER_BUDGET_LIMITS = [100_000, 4_000_000];
|
|
199
|
+
const newBudget = (limit) => ({
|
|
200
|
+
remaining: limit,
|
|
201
|
+
spent: false,
|
|
202
|
+
});
|
|
203
|
+
const unexpandedGroup = (group) => `the ${joinWithAnd(group.entries.map(entryName))} you obtained earlier`;
|
|
204
|
+
function renderCaptureGroup(group, level, index, rendering, budget) {
|
|
205
|
+
const names = joinWithAnd(group.entries.map(entryName));
|
|
206
|
+
const locals = joinWithAnd(group.targets.map(localTargetPhrase));
|
|
207
|
+
const step = index.stepOf(group.stepId);
|
|
208
|
+
const operation = step === undefined ? undefined : anyOperationOf(index, step.operationId);
|
|
209
|
+
if (step === undefined || operation === undefined) {
|
|
210
|
+
return unexpandedGroup(group);
|
|
211
|
+
}
|
|
212
|
+
const source = stepReferenceAtLevel(step, operation, level, index, rendering, budget);
|
|
213
|
+
// Charged after the fact, so a nested expansion pays before its parent does
|
|
214
|
+
// and an inner blowup is what stops the outer one.
|
|
215
|
+
budget.remaining -= source.length;
|
|
216
|
+
return `the ${names} you obtained as ${locals} from ${source}`;
|
|
217
|
+
}
|
|
218
|
+
// Records that an expansion was suppressed, which is what `renderStepReference`
|
|
219
|
+
// reads before deciding whether a collision is worth retrying at the ceiling.
|
|
220
|
+
function exhausted(budget) {
|
|
221
|
+
if (budget.remaining > 0)
|
|
222
|
+
return false;
|
|
223
|
+
budget.spent = true;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
// Whether this entry expands into a group, or renders as the level-independent
|
|
227
|
+
// phrase on its own.
|
|
228
|
+
function expandableCapture(entry, level, index, rendering, budget) {
|
|
229
|
+
if (level !== 'literal' || !('captured' in entry.value))
|
|
230
|
+
return null;
|
|
231
|
+
if (exhausted(budget))
|
|
232
|
+
return null;
|
|
233
|
+
const target = parseEvidenceTarget(entry.value.captured);
|
|
234
|
+
if (rendering.has(target.stepId))
|
|
235
|
+
return null;
|
|
236
|
+
const step = index.stepOf(target.stepId);
|
|
237
|
+
if (step === undefined)
|
|
238
|
+
return null;
|
|
239
|
+
return anyOperationOf(index, step.operationId) === undefined ? null : target;
|
|
240
|
+
}
|
|
241
|
+
// Renders one entry on its own. Every captured entry that expands lands in a
|
|
242
|
+
// group instead, so the arm here is the level-independent fallback.
|
|
90
243
|
function renderBindingValue(entry, level) {
|
|
91
244
|
const name = entryName(entry);
|
|
92
|
-
|
|
93
|
-
|
|
245
|
+
const { value } = entry;
|
|
246
|
+
if ('matcher' in value) {
|
|
247
|
+
return value.matcher === 'type-violating'
|
|
248
|
+
? `a malformed ${name} value`
|
|
249
|
+
: `the supplied ${name}`;
|
|
94
250
|
}
|
|
95
|
-
if (level === 'generic'
|
|
251
|
+
if (level === 'generic') {
|
|
96
252
|
return `the supplied ${name}`;
|
|
97
253
|
}
|
|
254
|
+
if ('principal' in value) {
|
|
255
|
+
return `the ${name} of the ${value.principal} account`;
|
|
256
|
+
}
|
|
257
|
+
if ('captured' in value) {
|
|
258
|
+
return `the ${name} you obtained earlier`;
|
|
259
|
+
}
|
|
98
260
|
if (level === 'kind') {
|
|
99
261
|
return `the stated ${name}`;
|
|
100
262
|
}
|
|
101
|
-
return `the ${name} ${formatLiteral(
|
|
263
|
+
return `the ${name} ${formatLiteral(value.literal)}`;
|
|
102
264
|
}
|
|
103
265
|
// Every declared binding key escalates together: narrowing to only the
|
|
104
266
|
// discriminating key would make one step's shown detail depend on which
|
|
@@ -108,13 +270,49 @@ function renderBindingValue(entry, level) {
|
|
|
108
270
|
//
|
|
109
271
|
// When a type-violating binding is present, the clause names only that one:
|
|
110
272
|
// it is the semantically salient binding, matching AD-16's worked example.
|
|
111
|
-
function bindingClause(step, level) {
|
|
273
|
+
function bindingClause(step, level, index, rendering, budget) {
|
|
112
274
|
const entries = bindingEntries(step);
|
|
113
275
|
if (entries.length === 0)
|
|
114
276
|
return null;
|
|
115
277
|
const malformed = entries.filter((entry) => isTypeViolating(entry.value));
|
|
116
278
|
const chosen = malformed.length > 0 ? malformed : entries;
|
|
117
|
-
|
|
279
|
+
// Each expandable capture joins the group for the step it references, held
|
|
280
|
+
// at the position that step was first referenced from, so the clause's order
|
|
281
|
+
// still comes from `bindingEntries`'s sort rather than from a map's keys.
|
|
282
|
+
const slots = [];
|
|
283
|
+
const groups = new Map();
|
|
284
|
+
for (const entry of chosen) {
|
|
285
|
+
const target = expandableCapture(entry, level, index, rendering, budget);
|
|
286
|
+
if (target === null) {
|
|
287
|
+
slots.push(entry);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const existing = groups.get(target.stepId);
|
|
291
|
+
if (existing !== undefined) {
|
|
292
|
+
existing.entries.push(entry);
|
|
293
|
+
existing.targets.push(target);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const group = {
|
|
297
|
+
stepId: target.stepId,
|
|
298
|
+
entries: [entry],
|
|
299
|
+
targets: [target],
|
|
300
|
+
};
|
|
301
|
+
groups.set(target.stepId, group);
|
|
302
|
+
slots.push(group);
|
|
303
|
+
}
|
|
304
|
+
// Tested here rather than while the slots are built: a clause commits to its
|
|
305
|
+
// groups in one pass and renders them in the next, so an entry-time test let
|
|
306
|
+
// every group of an already-committed clause render in full, and the bound
|
|
307
|
+
// grew by the group count rather than by one expansion.
|
|
308
|
+
const phrases = slots.map((slot) => {
|
|
309
|
+
if (!('stepId' in slot))
|
|
310
|
+
return renderBindingValue(slot, level);
|
|
311
|
+
return exhausted(budget)
|
|
312
|
+
? unexpandedGroup(slot)
|
|
313
|
+
: renderCaptureGroup(slot, level, index, rendering, budget);
|
|
314
|
+
});
|
|
315
|
+
return `with ${joinWithAnd(phrases)}`;
|
|
118
316
|
}
|
|
119
317
|
// Parenthesized rather than comma-joined onto the operation reference: a
|
|
120
318
|
// step reference can itself be joined with other phrases at a higher level
|
|
@@ -122,9 +320,11 @@ function bindingClause(step, level) {
|
|
|
122
320
|
// already carries its own internal ", and" separators. A second comma layer
|
|
123
321
|
// on top would produce indistinguishable comma soup once two or more
|
|
124
322
|
// phrases are joined.
|
|
125
|
-
function stepReferenceAtLevel(step, operation, level) {
|
|
323
|
+
function stepReferenceAtLevel(step, operation, level, index, rendering = new Set(), budget = newBudget(RENDER_BUDGET_LIMITS[0])) {
|
|
126
324
|
const base = operationReference(operation);
|
|
127
|
-
|
|
325
|
+
// The step being rendered joins the path before its own clause is built, so
|
|
326
|
+
// a self-capture falls back on the first hop.
|
|
327
|
+
const clause = bindingClause(step, level, index, new Set([...rendering, step.stepId]), budget);
|
|
128
328
|
return clause === null ? base : `${base} (${clause})`;
|
|
129
329
|
}
|
|
130
330
|
/**
|
|
@@ -140,14 +340,42 @@ function stepReferenceAtLevel(step, operation, level) {
|
|
|
140
340
|
* duplicate `stepId` check if two siblings still collide after full
|
|
141
341
|
* escalation.
|
|
142
342
|
*/
|
|
143
|
-
export function renderStepReference(step, operation, siblings) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
343
|
+
export function renderStepReference(step, operation, siblings, index) {
|
|
344
|
+
// Two passes over the ladder, at the ordinary budget and then at the ceiling.
|
|
345
|
+
// The retry fires only when every level collided AND some render had an
|
|
346
|
+
// expansion suppressed, which is what breaks the coupling the budget would
|
|
347
|
+
// otherwise introduce: without it, growing a literal on a step unrelated to
|
|
348
|
+
// either sibling can starve the expansion that separates them and make an
|
|
349
|
+
// otherwise-sealable contract throw. A collision with nothing suppressed is
|
|
350
|
+
// the declared structure's own tie, and a larger budget renders the same
|
|
351
|
+
// text, so that case leaves the loop rather than paying for a second pass.
|
|
352
|
+
for (const limit of RENDER_BUDGET_LIMITS) {
|
|
353
|
+
let starved = false;
|
|
354
|
+
for (const level of ESCALATION_LEVELS) {
|
|
355
|
+
const rendered = siblings.map((sibling) => {
|
|
356
|
+
const budget = newBudget(limit);
|
|
357
|
+
return {
|
|
358
|
+
phrase: stepReferenceAtLevel(sibling, operation, level, index, new Set(), budget),
|
|
359
|
+
spent: budget.spent,
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
const phrases = rendered.map((entry) => entry.phrase);
|
|
363
|
+
if (new Set(phrases).size === phrases.length) {
|
|
364
|
+
return stepReferenceAtLevel(step, operation, level, index, new Set(), newBudget(limit));
|
|
365
|
+
}
|
|
366
|
+
if (rendered.some((entry) => entry.spent))
|
|
367
|
+
starved = true;
|
|
148
368
|
}
|
|
369
|
+
if (!starved)
|
|
370
|
+
break;
|
|
149
371
|
}
|
|
150
|
-
|
|
372
|
+
// Coded rather than a bare `TypeError`. A contract reaching here passed
|
|
373
|
+
// every compile check and then failed at seal time with a stack trace, and
|
|
374
|
+
// the defect is an ordinary authoring fault: two steps the declared
|
|
375
|
+
// structure does not tell apart. `checkStepReferenceReducibility` runs this
|
|
376
|
+
// same ladder at compile time so the fault is reported before seal, and
|
|
377
|
+
// this throw is what it catches.
|
|
378
|
+
throw new StructuralFailure('irreducible-step-reference', `EvalContract.interactionPlan[operationId=${operation.operationId}]`, `two or more steps invoking operation "${operation.operationId}" that one direction references render to the same derived reference even fully escalated; the declared structure does not distinguish them (AD-16, AD-3)`);
|
|
151
379
|
}
|
|
152
380
|
function siblingsByOperation(resolved) {
|
|
153
381
|
const map = new Map();
|
|
@@ -214,11 +442,25 @@ function localTargetPhrase(target) {
|
|
|
214
442
|
return field !== null
|
|
215
443
|
? `the ${field} field of its standard error`
|
|
216
444
|
: 'the standard error you obtained';
|
|
445
|
+
case 'artifact': {
|
|
446
|
+
if (target.artifactId === null) {
|
|
447
|
+
// Unreachable: `parseEvidenceTarget` sets this exactly when the
|
|
448
|
+
// channel is 'artifact'.
|
|
449
|
+
throw new TypeError('artifact evidence target names no artifact');
|
|
450
|
+
}
|
|
451
|
+
// The artifact is named for the same reason the transport channel
|
|
452
|
+
// is: two files can carry the same field name, and an evaluator
|
|
453
|
+
// reading "its findings field" twice cannot tell which file it
|
|
454
|
+
// means.
|
|
455
|
+
return field !== null
|
|
456
|
+
? `the ${field} field of the ${target.artifactId} it wrote`
|
|
457
|
+
: `the ${target.artifactId} it wrote`;
|
|
458
|
+
}
|
|
217
459
|
}
|
|
218
460
|
}
|
|
219
|
-
function fullTargetPhrase(resolved, siblingsOf) {
|
|
461
|
+
function fullTargetPhrase(resolved, siblingsOf, index) {
|
|
220
462
|
const local = localTargetPhrase(resolved.target);
|
|
221
|
-
const stepRef = renderStepReference(resolved.step, resolved.operation, siblingsOf(resolved.operation.operationId));
|
|
463
|
+
const stepRef = renderStepReference(resolved.step, resolved.operation, siblingsOf(resolved.operation.operationId), index);
|
|
222
464
|
const preposition = resolved.target.channel === 'call-inputs' ? 'to' : 'from';
|
|
223
465
|
return `${local} ${preposition} ${stepRef}`;
|
|
224
466
|
}
|
|
@@ -228,7 +470,12 @@ function fullTargetPhrase(resolved, siblingsOf) {
|
|
|
228
470
|
// 'b']`). Both would join to the same `".../a/b"` string, producing a false
|
|
229
471
|
// tie in `sentFirstOrder` below.
|
|
230
472
|
function channelSignature(target) {
|
|
231
|
-
return JSON.stringify([
|
|
473
|
+
return JSON.stringify([
|
|
474
|
+
target.channel,
|
|
475
|
+
target.transportChannel,
|
|
476
|
+
target.artifactId,
|
|
477
|
+
target.tail,
|
|
478
|
+
]);
|
|
232
479
|
}
|
|
233
480
|
// A total order over one pair, independent of argument order. The final
|
|
234
481
|
// reference-comparison rung fires only on a genuine tie, such as the same
|
|
@@ -236,7 +483,7 @@ function channelSignature(target) {
|
|
|
236
483
|
// never degenerates to argument order, since colliding same-operation
|
|
237
484
|
// siblings already throw in `renderStepReference`, and different operations
|
|
238
485
|
// always humanize to different names.
|
|
239
|
-
function sentFirstOrder(a, b, siblingsOf) {
|
|
486
|
+
function sentFirstOrder(a, b, siblingsOf, index) {
|
|
240
487
|
const rank = (r) => r.target.channel === 'call-inputs' ? 0 : 1;
|
|
241
488
|
const rankA = rank(a);
|
|
242
489
|
const rankB = rank(b);
|
|
@@ -246,8 +493,8 @@ function sentFirstOrder(a, b, siblingsOf) {
|
|
|
246
493
|
const signatureB = channelSignature(b.target);
|
|
247
494
|
if (signatureA !== signatureB)
|
|
248
495
|
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));
|
|
496
|
+
const referenceA = renderStepReference(a.step, a.operation, siblingsOf(a.operation.operationId), index);
|
|
497
|
+
const referenceB = renderStepReference(b.step, b.operation, siblingsOf(b.operation.operationId), index);
|
|
251
498
|
return referenceA <= referenceB ? [a, b] : [b, a];
|
|
252
499
|
}
|
|
253
500
|
// Two evidence targets whose steps are declared in an `after` relationship
|
|
@@ -340,19 +587,19 @@ function groupResolvedTargets(resolved) {
|
|
|
340
587
|
}
|
|
341
588
|
return groups;
|
|
342
589
|
}
|
|
343
|
-
function renderPhraseGroup(group, siblingsOf) {
|
|
590
|
+
function renderPhraseGroup(group, siblingsOf, index) {
|
|
344
591
|
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)}`;
|
|
592
|
+
const [first, second] = sentFirstOrder(group.a, group.b, siblingsOf, index);
|
|
593
|
+
return `${fullTargetPhrase(first, siblingsOf, index)}, compared with ${fullTargetPhrase(second, siblingsOf, index)}`;
|
|
347
594
|
}
|
|
348
595
|
const first = group.resolved[0];
|
|
349
596
|
if (first === undefined) {
|
|
350
597
|
throw new TypeError('evidence-target group is empty');
|
|
351
598
|
}
|
|
352
599
|
if (group.resolved.length === 1) {
|
|
353
|
-
return fullTargetPhrase(first, siblingsOf);
|
|
600
|
+
return fullTargetPhrase(first, siblingsOf, index);
|
|
354
601
|
}
|
|
355
|
-
const stepRef = renderStepReference(first.step, first.operation, siblingsOf(first.operation.operationId));
|
|
602
|
+
const stepRef = renderStepReference(first.step, first.operation, siblingsOf(first.operation.operationId), index);
|
|
356
603
|
// Sorted, so a same-step group's field order is permutation-invariant like
|
|
357
604
|
// the top-level join below; in declaration order, permuting
|
|
358
605
|
// `evidenceTargets` would change which field is named first here.
|
|
@@ -378,6 +625,6 @@ export function renderEvidenceReferences(pointers, index) {
|
|
|
378
625
|
const siblings = siblingsByOperation(resolved);
|
|
379
626
|
const siblingsOf = (operationId) => siblings.get(operationId) ?? [];
|
|
380
627
|
const groups = groupResolvedTargets(resolved);
|
|
381
|
-
const phrases = groups.map((group) => renderPhraseGroup(group, siblingsOf));
|
|
628
|
+
const phrases = groups.map((group) => renderPhraseGroup(group, siblingsOf, index));
|
|
382
629
|
return joinWithAnd([...phrases].sort());
|
|
383
630
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** resolves a pointer to its step and operation; nothing about reachability. */
|
|
2
|
-
import type { Operation, PermittedInterface } from '../schemas/interface.ts';
|
|
2
|
+
import type { AnyOperation, CommandOperation, InterfaceKindName, Operation, PermittedInterface } from '../schemas/interface.ts';
|
|
3
3
|
import type { InteractionStep } from '../schemas/plan.ts';
|
|
4
|
-
import { type EvidenceChannelName, type
|
|
4
|
+
import { type EvidenceChannelName, type InputChannelName } from '../schemas/pointer.ts';
|
|
5
5
|
/**
|
|
6
6
|
* Exported so `core/evaluate/evidence-resolution.ts` decodes pointer tails
|
|
7
7
|
* with these same two functions, avoiding a second private copy (the drift
|
|
@@ -17,7 +17,8 @@ export declare const decodeTail: (tailSource: string) => readonly string[];
|
|
|
17
17
|
export type EvidenceTarget = {
|
|
18
18
|
stepId: string;
|
|
19
19
|
channel: EvidenceChannelName;
|
|
20
|
-
transportChannel:
|
|
20
|
+
transportChannel: InputChannelName | null;
|
|
21
|
+
artifactId: string | null;
|
|
21
22
|
tail: readonly string[];
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
@@ -34,10 +35,18 @@ export declare function parseEvidenceTarget(pointer: string): EvidenceTarget;
|
|
|
34
35
|
* about reachability or channel typing; the general addressing-grammar
|
|
35
36
|
* resolver lives in `core/evaluate/evidence-resolution.ts` and reachability
|
|
36
37
|
* in `core/compile/reachability.ts`.
|
|
38
|
+
*
|
|
39
|
+
* `operationOf` stays narrow on purpose. It hands a resolved `Operation` to
|
|
40
|
+
* every downstream consumer, so widening its return type to the operation
|
|
41
|
+
* union would retype seventeen files at once. A command operation resolves to
|
|
42
|
+
* `undefined` from it and to a value from `commandOperationOf`, and each
|
|
43
|
+
* caller that has to branch reads the declaring kind first.
|
|
37
44
|
*/
|
|
38
45
|
export type PlanIndex = {
|
|
39
46
|
stepOf: (stepId: string) => InteractionStep | undefined;
|
|
40
47
|
operationOf: (operationId: string) => Operation | undefined;
|
|
48
|
+
commandOperationOf: (operationId: string) => CommandOperation | undefined;
|
|
49
|
+
interfaceKindOf: (operationId: string) => InterfaceKindName | undefined;
|
|
41
50
|
stepsUsing: (operationId: string) => readonly InteractionStep[];
|
|
42
51
|
};
|
|
43
52
|
export type PlanIndexOptions = {
|
|
@@ -59,5 +68,11 @@ export declare function buildPlanIndex(interactionPlan: readonly InteractionStep
|
|
|
59
68
|
* `RuntimeFault`.
|
|
60
69
|
*/
|
|
61
70
|
export declare function resolveStep(index: PlanIndex, stepId: string): InteractionStep;
|
|
71
|
+
/**
|
|
72
|
+
* The declared operation of whichever kind, for the callers that read only
|
|
73
|
+
* fields both shapes carry. Callers reading a kind-specific field ask
|
|
74
|
+
* `interfaceKindOf` first and then take the matching accessor.
|
|
75
|
+
*/
|
|
76
|
+
export declare const anyOperationOf: (index: PlanIndex, operationId: string) => AnyOperation | undefined;
|
|
62
77
|
/** Resolves an operation id through the index or throws. See `resolveStep`. */
|
|
63
78
|
export declare function resolveOperation(index: PlanIndex, operationId: string): Operation;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { operationsOf } from '../schemas/interface.js';
|
|
2
|
+
import { IDENTIFIER_ROOTED_CHANNEL, INPUT_CHANNELS, SCALAR_CHANNELS, TAIL_BEARING_CHANNELS, TRANSPORT_ROOTED_CHANNEL, } from '../schemas/pointer.js';
|
|
2
3
|
import { IDENTIFIER_CHARSET_SOURCE } from '../schemas/primitives.js';
|
|
3
4
|
// An RFC 6901 reference token, respelled here because pointer.ts keeps its
|
|
4
5
|
// own TOKEN/TAIL fragments private. IDENTIFIER_CHARSET_SOURCE and the channel
|
|
@@ -14,10 +15,10 @@ const alternation = (members) => members.join('|');
|
|
|
14
15
|
// Named capture groups replace positional indices, avoiding a fragile
|
|
15
16
|
// dependency on `IDENTIFIER_CHARSET_SOURCE` staying free of its own capturing
|
|
16
17
|
// groups.
|
|
17
|
-
const EVIDENCE_TARGET_PATTERN = new RegExp(`^/interactions/(?<stepId>${IDENTIFIER_CHARSET_SOURCE})/(?:(?<tailBearingChannel>${alternation(TAIL_BEARING_CHANNELS)})(?<tailBearingTail>${TAIL_SOURCE})|(?<scalarChannel>${alternation(SCALAR_CHANNELS)})|${TRANSPORT_ROOTED_CHANNEL}/(?<transportChannel>${alternation(
|
|
18
|
+
const EVIDENCE_TARGET_PATTERN = new RegExp(`^/interactions/(?<stepId>${IDENTIFIER_CHARSET_SOURCE})/(?:(?<tailBearingChannel>${alternation(TAIL_BEARING_CHANNELS)})(?<tailBearingTail>${TAIL_SOURCE})|(?<scalarChannel>${alternation(SCALAR_CHANNELS)})|${TRANSPORT_ROOTED_CHANNEL}/(?<transportChannel>${alternation(INPUT_CHANNELS)})(?<callInputsTail>${TAIL_SOURCE})|${IDENTIFIER_ROOTED_CHANNEL}/(?<artifactId>${IDENTIFIER_CHARSET_SOURCE})(?<artifactTail>${TAIL_SOURCE}))$`);
|
|
18
19
|
const isEvidenceChannel = (value) => TAIL_BEARING_CHANNELS.includes(value) ||
|
|
19
20
|
SCALAR_CHANNELS.includes(value);
|
|
20
|
-
const
|
|
21
|
+
const isInputChannel = (value) => INPUT_CHANNELS.includes(value);
|
|
21
22
|
/**
|
|
22
23
|
* Exported so `core/evaluate/evidence-resolution.ts` decodes pointer tails
|
|
23
24
|
* with these same two functions, avoiding a second private copy (the drift
|
|
@@ -47,6 +48,7 @@ export function parseEvidenceTarget(pointer) {
|
|
|
47
48
|
stepId,
|
|
48
49
|
channel: groups.scalarChannel,
|
|
49
50
|
transportChannel: null,
|
|
51
|
+
artifactId: null,
|
|
50
52
|
tail: [],
|
|
51
53
|
};
|
|
52
54
|
}
|
|
@@ -59,21 +61,32 @@ export function parseEvidenceTarget(pointer) {
|
|
|
59
61
|
stepId,
|
|
60
62
|
channel: groups.tailBearingChannel,
|
|
61
63
|
transportChannel: null,
|
|
64
|
+
artifactId: null,
|
|
62
65
|
tail: decodeTail(groups.tailBearingTail ?? ''),
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
if (groups.transportChannel !== undefined) {
|
|
66
|
-
if (!
|
|
67
|
-
// Unreachable:
|
|
69
|
+
if (!isInputChannel(groups.transportChannel)) {
|
|
70
|
+
// Unreachable: INPUT_CHANNELS is exactly what this group can match.
|
|
68
71
|
throw new TypeError(`call-inputs evidence target names no transport channel: ${pointer}`);
|
|
69
72
|
}
|
|
70
73
|
return {
|
|
71
74
|
stepId,
|
|
72
75
|
channel: 'call-inputs',
|
|
73
76
|
transportChannel: groups.transportChannel,
|
|
77
|
+
artifactId: null,
|
|
74
78
|
tail: decodeTail(groups.callInputsTail ?? ''),
|
|
75
79
|
};
|
|
76
80
|
}
|
|
81
|
+
if (groups.artifactId !== undefined) {
|
|
82
|
+
return {
|
|
83
|
+
stepId,
|
|
84
|
+
channel: IDENTIFIER_ROOTED_CHANNEL,
|
|
85
|
+
transportChannel: null,
|
|
86
|
+
artifactId: groups.artifactId,
|
|
87
|
+
tail: decodeTail(groups.artifactTail ?? ''),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
77
90
|
// Unreachable: the pattern's three branches are exhaustive once stepId matched.
|
|
78
91
|
throw new TypeError(`not an interaction-rooted evidence pointer of a recognized channel: ${pointer}`);
|
|
79
92
|
}
|
|
@@ -109,25 +122,42 @@ export function buildPlanIndex(interactionPlan, permittedInterfaces, options = {
|
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
const operations = new Map();
|
|
125
|
+
const commandOperations = new Map();
|
|
126
|
+
const kinds = new Map();
|
|
112
127
|
const duplicateOperationIds = new Set();
|
|
113
128
|
for (const iface of permittedInterfaces) {
|
|
114
|
-
for (const operation of iface
|
|
115
|
-
if (
|
|
129
|
+
for (const operation of operationsOf(iface)) {
|
|
130
|
+
if (kinds.has(operation.operationId) ||
|
|
116
131
|
duplicateOperationIds.has(operation.operationId)) {
|
|
117
132
|
if (duplicateIds === 'throw') {
|
|
118
133
|
throw new TypeError(`duplicate operation id across permitted interfaces: ${operation.operationId}`);
|
|
119
134
|
}
|
|
120
135
|
operations.delete(operation.operationId);
|
|
136
|
+
commandOperations.delete(operation.operationId);
|
|
137
|
+
kinds.delete(operation.operationId);
|
|
121
138
|
duplicateOperationIds.add(operation.operationId);
|
|
122
139
|
}
|
|
123
140
|
else {
|
|
124
|
-
|
|
141
|
+
kinds.set(operation.operationId, iface.kind);
|
|
142
|
+
// Sorted into the two maps by the interface's own kind rather
|
|
143
|
+
// than by probing the operation for a field: `web` and `mcp`
|
|
144
|
+
// carry the api operation shape and belong in the same map as
|
|
145
|
+
// `api`, since every consumer of a resolved operation reads
|
|
146
|
+
// the same declared fields off all three.
|
|
147
|
+
if (iface.kind === 'cli') {
|
|
148
|
+
commandOperations.set(operation.operationId, operation);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
operations.set(operation.operationId, operation);
|
|
152
|
+
}
|
|
125
153
|
}
|
|
126
154
|
}
|
|
127
155
|
}
|
|
128
156
|
return {
|
|
129
157
|
stepOf: (stepId) => steps.get(stepId),
|
|
130
158
|
operationOf: (operationId) => operations.get(operationId),
|
|
159
|
+
commandOperationOf: (operationId) => commandOperations.get(operationId),
|
|
160
|
+
interfaceKindOf: (operationId) => kinds.get(operationId),
|
|
131
161
|
stepsUsing: (operationId) => stepsByOperation.get(operationId) ?? [],
|
|
132
162
|
};
|
|
133
163
|
}
|
|
@@ -145,6 +175,12 @@ export function resolveStep(index, stepId) {
|
|
|
145
175
|
}
|
|
146
176
|
return step;
|
|
147
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* The declared operation of whichever kind, for the callers that read only
|
|
180
|
+
* fields both shapes carry. Callers reading a kind-specific field ask
|
|
181
|
+
* `interfaceKindOf` first and then take the matching accessor.
|
|
182
|
+
*/
|
|
183
|
+
export const anyOperationOf = (index, operationId) => index.operationOf(operationId) ?? index.commandOperationOf(operationId);
|
|
148
184
|
/** Resolves an operation id through the index or throws. See `resolveStep`. */
|
|
149
185
|
export function resolveOperation(index, operationId) {
|
|
150
186
|
const operation = index.operationOf(operationId);
|
package/dist/core/seal/seal.js
CHANGED
|
@@ -75,8 +75,10 @@ export function seal(contract) {
|
|
|
75
75
|
// and stateless with no "prior brief" argument (AD-12), so the only
|
|
76
76
|
// honest artifact is a lineage root: `parentDigest` null,
|
|
77
77
|
// `revisionCount` 0, independent of the contract's own lineage.
|
|
78
|
-
// `schemaVersion` is the brief schema's current version
|
|
79
|
-
|
|
78
|
+
// `schemaVersion` is the brief schema's current version; 2 since owed
|
|
79
|
+
// item 3 added `principals` as a required field, which AD-11 counts as
|
|
80
|
+
// a breaking change.
|
|
81
|
+
schemaVersion: 2,
|
|
80
82
|
parentDigest: null,
|
|
81
83
|
revisionCount: 0,
|
|
82
84
|
// A plain digest of the literal input: two differently-ordered
|
|
@@ -98,8 +100,11 @@ export function seal(contract) {
|
|
|
98
100
|
// Copied rather than aliased, for the same reason as `behaviors` above.
|
|
99
101
|
budgets: { ...contract.budgets },
|
|
100
102
|
// Sorted lexicographically; sort-key duplicates are impossible here
|
|
101
|
-
// since equal strings are interchangeable, so no duplicate guard.
|
|
103
|
+
// since equal strings are interchangeable, so no duplicate guard. The
|
|
104
|
+
// principal names are sorted the same way rather than through
|
|
105
|
+
// `sortedByKey`, for the same reason: there is no key to guard.
|
|
102
106
|
safetyLimits: [...contract.safetyLimits].sort(),
|
|
107
|
+
principals: Object.keys(contract.testData.principals ?? {}).sort(),
|
|
103
108
|
probeStepBound: contract.probeStepBound,
|
|
104
109
|
};
|
|
105
110
|
// The parse returns Zod's own deep clone, so the freeze lands on the
|