eval-quality 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -67
- package/corpus/dev/README.md +17 -11
- package/corpus/dev/compile-seal-example/brief.json +1 -1
- package/corpus/dev/compile-seal-example/contract.json +1 -1
- package/corpus/dev/contracts/absent-collection-locations.json +1 -1
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
- package/corpus/dev/contracts/absent-success-indicator.json +1 -1
- package/corpus/dev/contracts/empty-channel-roles.json +1 -1
- package/corpus/dev/contracts/empty-collection-locations.json +1 -1
- package/corpus/dev/contracts/empty-request-shapes.json +1 -1
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
- package/corpus/dev/contracts/no-operation-inventory.json +1 -1
- package/corpus/dev/contracts/no-read-back-relation.json +1 -1
- package/corpus/dev/contracts/no-state-change-marker.json +1 -1
- package/corpus/dev/contracts/no-type-violating-step.json +1 -1
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
- package/corpus/dev/contracts/satisfied-declarations.json +1 -1
- package/corpus/dev/contracts/single-required-response-key.json +1 -1
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
- package/corpus/dev/index.json +1 -1
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.js +1 -0
- package/dist/application/score.d.ts +48 -0
- package/dist/application/score.js +186 -0
- package/dist/cli/arguments.d.ts +7 -3
- package/dist/cli/arguments.js +73 -5
- package/dist/cli/exit-codes.d.ts +6 -2
- package/dist/cli/exit-codes.js +8 -9
- package/dist/cli/main.js +9 -0
- package/dist/cli/render.d.ts +1 -1
- package/dist/cli/render.js +6 -4
- package/dist/cli/run.d.ts +13 -3
- package/dist/cli/run.js +136 -16
- package/dist/core/canonical/canonicalize.d.ts +12 -0
- package/dist/core/canonical/canonicalize.js +18 -4
- package/dist/core/compile/bindings.d.ts +70 -0
- package/dist/core/compile/bindings.js +317 -0
- package/dist/core/compile/compile.d.ts +5 -0
- package/dist/core/compile/compile.js +9 -0
- package/dist/core/compile/expression-legality.d.ts +49 -0
- package/dist/core/compile/expression-legality.js +105 -31
- package/dist/core/compile/forbidden-inputs.d.ts +8 -1
- package/dist/core/compile/forbidden-inputs.js +16 -3
- package/dist/core/compile/interface-inventory.d.ts +23 -1
- package/dist/core/compile/interface-inventory.js +32 -3
- package/dist/core/compile/reachability.d.ts +18 -1
- package/dist/core/compile/reachability.js +46 -1
- package/dist/core/compile/sensitivity-witness.d.ts +1 -1
- package/dist/core/compile/sensitivity-witness.js +1 -1
- package/dist/core/emit/emit.d.ts +9 -0
- package/dist/core/emit/emit.js +214 -0
- package/dist/core/emit/private-artifact-digest.d.ts +11 -0
- package/dist/core/emit/private-artifact-digest.js +31 -0
- package/dist/core/evaluate/evidence-resolution.d.ts +11 -1
- package/dist/core/evaluate/evidence-resolution.js +4 -1
- package/dist/core/failure-codes.d.ts +2 -2
- package/dist/core/failure-codes.js +4 -2
- package/dist/core/ingest/conditions.d.ts +201 -0
- package/dist/core/ingest/conditions.js +48 -0
- package/dist/core/ingest/index.d.ts +13 -0
- package/dist/core/ingest/index.js +11 -0
- package/dist/core/ingest/ingest.d.ts +68 -0
- package/dist/core/ingest/ingest.js +310 -0
- package/dist/core/lineage/stage-table.d.ts +21 -4
- package/dist/core/lineage/stage-table.js +35 -3
- package/dist/core/preflight/witness-evidence.js +10 -0
- package/dist/core/schemas/artifact.d.ts +385 -14
- package/dist/core/schemas/constraint-ledger.js +40 -0
- package/dist/core/schemas/defect-signature.d.ts +273 -0
- package/dist/core/schemas/defect-signature.js +127 -0
- package/dist/core/schemas/eval-contract.d.ts +33 -0
- package/dist/core/schemas/eval-contract.js +14 -0
- package/dist/core/schemas/evidence-artifact.d.ts +99 -19
- package/dist/core/schemas/evidence-artifact.js +64 -8
- package/dist/core/schemas/plan.d.ts +112 -0
- package/dist/core/schemas/plan.js +67 -2
- package/dist/core/schemas/probe-qualification.d.ts +168 -0
- package/dist/core/schemas/probe-qualification.js +106 -0
- package/dist/core/schemas/probe.d.ts +312 -0
- package/dist/core/schemas/probe.js +6 -2
- package/dist/core/schemas/scoring-policy.d.ts +1 -0
- package/dist/core/schemas/scoring-policy.js +6 -1
- package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
- package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
- package/dist/core/schemas/sealed-run-record.d.ts +30 -3
- package/dist/core/schemas/sealed-run-record.js +37 -5
- package/dist/core/score/binding-order.d.ts +31 -0
- package/dist/core/score/binding-order.js +88 -0
- package/dist/core/score/bindings.d.ts +93 -0
- package/dist/core/score/bindings.js +297 -0
- package/dist/core/score/ladder-table.d.ts +23 -0
- package/dist/core/score/ladder-table.js +109 -0
- package/dist/core/score/ladder.d.ts +229 -0
- package/dist/core/score/ladder.js +464 -0
- package/dist/core/score/mode-agreement.d.ts +30 -0
- package/dist/core/score/mode-agreement.js +16 -0
- package/dist/core/score/outcome-table.d.ts +17 -0
- package/dist/core/score/outcome-table.js +172 -0
- package/dist/core/score/outcome.d.ts +466 -0
- package/dist/core/score/outcome.js +562 -0
- package/dist/core/score/qualification.d.ts +83 -0
- package/dist/core/score/qualification.js +615 -0
- package/dist/core/score/quotation.d.ts +79 -0
- package/dist/core/score/quotation.js +201 -0
- package/dist/core/score/reduce-trials.d.ts +80 -0
- package/dist/core/score/reduce-trials.js +90 -0
- package/dist/core/score/score.d.ts +72 -0
- package/dist/core/score/score.js +581 -0
- package/dist/core/score/selection.d.ts +80 -0
- package/dist/core/score/selection.js +98 -0
- package/dist/core/score/strength.d.ts +49 -0
- package/dist/core/score/strength.js +193 -0
- package/dist/core/score/witness.d.ts +117 -0
- package/dist/core/score/witness.js +315 -0
- package/dist/core/seal/derived-reference.d.ts +1 -1
- package/dist/core/seal/derived-reference.js +236 -28
- package/dist/core/seal/seal.js +8 -3
- package/dist/core/stage-contracts.d.ts +62 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/package.json +12 -3
- package/schemas/eval-contract.schema.json +100 -2
- package/schemas/evidence-artifact.schema.json +96 -9
- package/schemas/probe.schema.json +508 -3
- package/schemas/scoring-policy.schema.json +8 -1
- package/schemas/sealed-evaluator-brief.schema.json +11 -1
- package/schemas/sealed-run-record.schema.json +19 -2
|
@@ -83,22 +83,170 @@ function canonicalizeForDisplay(value) {
|
|
|
83
83
|
function formatLiteral(literal) {
|
|
84
84
|
return JSON.stringify(canonicalizeForDisplay(literal));
|
|
85
85
|
}
|
|
86
|
-
// Base rendering
|
|
87
|
-
// `any` and, at the 'generic' level,
|
|
88
|
-
// on purpose; the escalation ladder below tells them
|
|
89
|
-
// would otherwise collide.
|
|
86
|
+
// Base rendering, one arm per tagged form. `type-violating` always names the
|
|
87
|
+
// input malformed. `any` and, at the 'generic' level, every other form share
|
|
88
|
+
// the same generic wording on purpose; the escalation ladder below tells them
|
|
89
|
+
// apart once two steps would otherwise collide.
|
|
90
|
+
//
|
|
91
|
+
// `captured` escalates to a real derived reference at the 'literal' rung: the
|
|
92
|
+
// local phrase for the pointer's channel and tail, then the referenced step
|
|
93
|
+
// rendered through `stepReferenceAtLevel`, which is the same phrase
|
|
94
|
+
// `fullTargetPhrase` already prints for an evidence target. Naming the
|
|
95
|
+
// referenced OPERATION alone was tried and dropped: two predecessors sharing
|
|
96
|
+
// one operation and one body key then render identically, so two siblings
|
|
97
|
+
// capturing from them collide at every rung and `renderStepReference` throws
|
|
98
|
+
// out of `seal()` on a contract that compiles clean. Recursing into the
|
|
99
|
+
// predecessor separates them by its own binding clause. Two predecessors that
|
|
100
|
+
// are themselves irreducible still tie, and the throw there is correct: the
|
|
101
|
+
// declared structure does not distinguish them, which is the same answer
|
|
102
|
+
// `irreducibleCollisionPair` already gets. That throw is a bare `TypeError`
|
|
103
|
+
// rather than a coded failure, which is a pre-existing gap this widens; the
|
|
104
|
+
// deferred-work entry names hoisting the collision check to compile time.
|
|
105
|
+
//
|
|
106
|
+
// It does NOT call `renderStepReference`, so none of that function's
|
|
107
|
+
// constraints apply: no direction-scoped sibling list is consulted, so AD-16's
|
|
108
|
+
// scoping stays intact, and there is no tie to throw on. A step identifier
|
|
109
|
+
// never appears, which is the property AD-16 actually requires.
|
|
110
|
+
//
|
|
111
|
+
// `rendering` holds the steps already on the render path, and a capture back
|
|
112
|
+
// into one of them falls back to the level-independent phrase. A fixed depth
|
|
113
|
+
// bound was tried and dropped: at depth one, a two-link chain whose every link
|
|
114
|
+
// is genuinely distinguishable still collides, because only the immediate
|
|
115
|
+
// predecessor's binding shows and that is the half the two siblings share.
|
|
116
|
+
// Following the chain as far as it goes is what separates them, and the
|
|
117
|
+
// on-path set is what makes that terminate. Each recursion adds one step id, so
|
|
118
|
+
// the depth is bounded by the plan; `plan-exceeds-scripting-bound` bounds that
|
|
119
|
+
// too. Same guard the score module uses for an `after` chain, for the same
|
|
120
|
+
// reason: `binding-cycle` rejects a capture cycle at compile time, so this only
|
|
121
|
+
// covers a plan driven straight into the renderer.
|
|
122
|
+
//
|
|
123
|
+
// A pointer whose step or operation the index cannot resolve falls back to the
|
|
124
|
+
// 'kind' phrase. `seal()` runs after `compile`, which rejects such a pointer
|
|
125
|
+
// under `unreachable-check-evidence`, so this is a fallback for a
|
|
126
|
+
// directly-driven caller and adds no throw path.
|
|
127
|
+
//
|
|
128
|
+
// One thing a grep for step identifiers will find and should not be surprised
|
|
129
|
+
// by: `localTargetPhrase` prints the pointer's tail, and a response-body key
|
|
130
|
+
// the author happened to name the same as a step prints with it. That is the
|
|
131
|
+
// author's own declared key rather than a plan identifier, and it is what every
|
|
132
|
+
// ordinary evidence target has always printed.
|
|
133
|
+
//
|
|
134
|
+
// `principal` tops out at the declared name, which AD-18 keeps an opaque label
|
|
135
|
+
// and is therefore safe on the brief.
|
|
136
|
+
// One or more entries of a step's clause that capture from the same earlier
|
|
137
|
+
// step, rendered together so that step's phrase is expanded once. Expanding it
|
|
138
|
+
// per entry made the clause's size multiply down a capture chain: a step with k
|
|
139
|
+
// entries pointing at its predecessor expanded that predecessor k times, and the
|
|
140
|
+
// predecessor did the same to its own, so a sixteen-step chain with four keys a
|
|
141
|
+
// link produced a phrase past V8's maximum string length. Grouping makes the
|
|
142
|
+
// clause linear in keys and reads better besides.
|
|
143
|
+
//
|
|
144
|
+
// Grouping alone bounds one axis of three, and the size is their product. The
|
|
145
|
+
// other two are the number of DISTINCT predecessors a step references, which
|
|
146
|
+
// doubles per level on a complete capture DAG, and the length of one step's own
|
|
147
|
+
// clause text, which nothing bounds: `formatLiteral` prints a `JsonValue` with
|
|
148
|
+
// no length cap and a binding channel is a record with a minimum of one entry
|
|
149
|
+
// and no maximum. Measured on a sixteen-step complete DAG that passes every
|
|
150
|
+
// plan-side check, the multiplier is 8,192, so a single 65,536-character
|
|
151
|
+
// literal on the seed step, or two hundred ordinary forty-character ones,
|
|
152
|
+
// reaches `RangeError: Invalid string length`.
|
|
153
|
+
//
|
|
154
|
+
// `budget` is what bounds all three at once. Each expansion spends the
|
|
155
|
+
// characters it produced, and once the budget is gone a capture renders as the
|
|
156
|
+
// level-independent phrase it already had. Every `stepReferenceAtLevel` call
|
|
157
|
+
// from `renderStepReference` starts a fresh budget, so one sibling's expansion
|
|
158
|
+
// never degrades another's and the distinctness test stays order-independent.
|
|
159
|
+
//
|
|
160
|
+
// The budget is charged after an expansion rather than before, so the bound is
|
|
161
|
+
// the budget plus whatever one step's own clause text costs; a step binding a
|
|
162
|
+
// single 200,000-character literal renders about 204,000. That is the point of
|
|
163
|
+
// charging late: an expansion cannot be priced before its nested expansions are
|
|
164
|
+
// known, and stopping the NEXT one is what keeps the product finite. Measured
|
|
165
|
+
// on the sixteen-step complete DAG that reached `RangeError` before: 36 KB with
|
|
166
|
+
// a minimal seed, 75 KB with two hundred forty-character literals, 135 KB with
|
|
167
|
+
// a 65,536-character one, and, with a one-million-character literal on every one
|
|
168
|
+
// of its fourteen nodes, 1,000,564. All in single-digit milliseconds. That last
|
|
169
|
+
// row is the one that pins the bound: one literal's worth of overshoot rather
|
|
170
|
+
// than fourteen, which is why the exhaustion test sits in the render pass below
|
|
171
|
+
// rather than where the slots are built.
|
|
172
|
+
//
|
|
173
|
+
// A budget alone would couple things that have nothing to do with each other:
|
|
174
|
+
// growing a literal on a step neither sibling references can starve the
|
|
175
|
+
// expansion that separates them, so an unrelated field decides whether a
|
|
176
|
+
// contract seals. `renderStepReference`'s retry at the ceiling is what removes
|
|
177
|
+
// that, and it fires only when a collision came of a suppressed expansion. What
|
|
178
|
+
// remains is the honest case: siblings the declared structure does not
|
|
179
|
+
// distinguish still throw the precondition `TypeError`, which is the
|
|
180
|
+
// pre-existing failure mode `deferred-work.md` already carries.
|
|
181
|
+
// The ordinary limit, then the ceiling a starved render retries at. No
|
|
182
|
+
// realistic contract reaches the first; only one that would otherwise fail to
|
|
183
|
+
// render at all pays for the second.
|
|
184
|
+
const RENDER_BUDGET_LIMITS = [100_000, 4_000_000];
|
|
185
|
+
const newBudget = (limit) => ({
|
|
186
|
+
remaining: limit,
|
|
187
|
+
spent: false,
|
|
188
|
+
});
|
|
189
|
+
const unexpandedGroup = (group) => `the ${joinWithAnd(group.entries.map(entryName))} you obtained earlier`;
|
|
190
|
+
function renderCaptureGroup(group, level, index, rendering, budget) {
|
|
191
|
+
const names = joinWithAnd(group.entries.map(entryName));
|
|
192
|
+
const locals = joinWithAnd(group.targets.map(localTargetPhrase));
|
|
193
|
+
const step = index.stepOf(group.stepId);
|
|
194
|
+
const operation = step === undefined ? undefined : index.operationOf(step.operationId);
|
|
195
|
+
if (step === undefined || operation === undefined) {
|
|
196
|
+
return unexpandedGroup(group);
|
|
197
|
+
}
|
|
198
|
+
const source = stepReferenceAtLevel(step, operation, level, index, rendering, budget);
|
|
199
|
+
// Charged after the fact, so a nested expansion pays before its parent does
|
|
200
|
+
// and an inner blowup is what stops the outer one.
|
|
201
|
+
budget.remaining -= source.length;
|
|
202
|
+
return `the ${names} you obtained as ${locals} from ${source}`;
|
|
203
|
+
}
|
|
204
|
+
// Records that an expansion was suppressed, which is what `renderStepReference`
|
|
205
|
+
// reads before deciding whether a collision is worth retrying at the ceiling.
|
|
206
|
+
function exhausted(budget) {
|
|
207
|
+
if (budget.remaining > 0)
|
|
208
|
+
return false;
|
|
209
|
+
budget.spent = true;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
// Whether this entry expands into a group, or renders as the level-independent
|
|
213
|
+
// phrase on its own.
|
|
214
|
+
function expandableCapture(entry, level, index, rendering, budget) {
|
|
215
|
+
if (level !== 'literal' || !('captured' in entry.value))
|
|
216
|
+
return null;
|
|
217
|
+
if (exhausted(budget))
|
|
218
|
+
return null;
|
|
219
|
+
const target = parseEvidenceTarget(entry.value.captured);
|
|
220
|
+
if (rendering.has(target.stepId))
|
|
221
|
+
return null;
|
|
222
|
+
const step = index.stepOf(target.stepId);
|
|
223
|
+
if (step === undefined)
|
|
224
|
+
return null;
|
|
225
|
+
return index.operationOf(step.operationId) === undefined ? null : target;
|
|
226
|
+
}
|
|
227
|
+
// Renders one entry on its own. Every captured entry that expands lands in a
|
|
228
|
+
// group instead, so the arm here is the level-independent fallback.
|
|
90
229
|
function renderBindingValue(entry, level) {
|
|
91
230
|
const name = entryName(entry);
|
|
92
|
-
|
|
93
|
-
|
|
231
|
+
const { value } = entry;
|
|
232
|
+
if ('matcher' in value) {
|
|
233
|
+
return value.matcher === 'type-violating'
|
|
234
|
+
? `a malformed ${name} value`
|
|
235
|
+
: `the supplied ${name}`;
|
|
94
236
|
}
|
|
95
|
-
if (level === 'generic'
|
|
237
|
+
if (level === 'generic') {
|
|
96
238
|
return `the supplied ${name}`;
|
|
97
239
|
}
|
|
240
|
+
if ('principal' in value) {
|
|
241
|
+
return `the ${name} of the ${value.principal} account`;
|
|
242
|
+
}
|
|
243
|
+
if ('captured' in value) {
|
|
244
|
+
return `the ${name} you obtained earlier`;
|
|
245
|
+
}
|
|
98
246
|
if (level === 'kind') {
|
|
99
247
|
return `the stated ${name}`;
|
|
100
248
|
}
|
|
101
|
-
return `the ${name} ${formatLiteral(
|
|
249
|
+
return `the ${name} ${formatLiteral(value.literal)}`;
|
|
102
250
|
}
|
|
103
251
|
// Every declared binding key escalates together: narrowing to only the
|
|
104
252
|
// discriminating key would make one step's shown detail depend on which
|
|
@@ -108,13 +256,49 @@ function renderBindingValue(entry, level) {
|
|
|
108
256
|
//
|
|
109
257
|
// When a type-violating binding is present, the clause names only that one:
|
|
110
258
|
// it is the semantically salient binding, matching AD-16's worked example.
|
|
111
|
-
function bindingClause(step, level) {
|
|
259
|
+
function bindingClause(step, level, index, rendering, budget) {
|
|
112
260
|
const entries = bindingEntries(step);
|
|
113
261
|
if (entries.length === 0)
|
|
114
262
|
return null;
|
|
115
263
|
const malformed = entries.filter((entry) => isTypeViolating(entry.value));
|
|
116
264
|
const chosen = malformed.length > 0 ? malformed : entries;
|
|
117
|
-
|
|
265
|
+
// Each expandable capture joins the group for the step it references, held
|
|
266
|
+
// at the position that step was first referenced from, so the clause's order
|
|
267
|
+
// still comes from `bindingEntries`'s sort rather than from a map's keys.
|
|
268
|
+
const slots = [];
|
|
269
|
+
const groups = new Map();
|
|
270
|
+
for (const entry of chosen) {
|
|
271
|
+
const target = expandableCapture(entry, level, index, rendering, budget);
|
|
272
|
+
if (target === null) {
|
|
273
|
+
slots.push(entry);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const existing = groups.get(target.stepId);
|
|
277
|
+
if (existing !== undefined) {
|
|
278
|
+
existing.entries.push(entry);
|
|
279
|
+
existing.targets.push(target);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const group = {
|
|
283
|
+
stepId: target.stepId,
|
|
284
|
+
entries: [entry],
|
|
285
|
+
targets: [target],
|
|
286
|
+
};
|
|
287
|
+
groups.set(target.stepId, group);
|
|
288
|
+
slots.push(group);
|
|
289
|
+
}
|
|
290
|
+
// Tested here rather than while the slots are built: a clause commits to its
|
|
291
|
+
// groups in one pass and renders them in the next, so an entry-time test let
|
|
292
|
+
// every group of an already-committed clause render in full, and the bound
|
|
293
|
+
// grew by the group count rather than by one expansion.
|
|
294
|
+
const phrases = slots.map((slot) => {
|
|
295
|
+
if (!('stepId' in slot))
|
|
296
|
+
return renderBindingValue(slot, level);
|
|
297
|
+
return exhausted(budget)
|
|
298
|
+
? unexpandedGroup(slot)
|
|
299
|
+
: renderCaptureGroup(slot, level, index, rendering, budget);
|
|
300
|
+
});
|
|
301
|
+
return `with ${joinWithAnd(phrases)}`;
|
|
118
302
|
}
|
|
119
303
|
// Parenthesized rather than comma-joined onto the operation reference: a
|
|
120
304
|
// step reference can itself be joined with other phrases at a higher level
|
|
@@ -122,9 +306,11 @@ function bindingClause(step, level) {
|
|
|
122
306
|
// already carries its own internal ", and" separators. A second comma layer
|
|
123
307
|
// on top would produce indistinguishable comma soup once two or more
|
|
124
308
|
// phrases are joined.
|
|
125
|
-
function stepReferenceAtLevel(step, operation, level) {
|
|
309
|
+
function stepReferenceAtLevel(step, operation, level, index, rendering = new Set(), budget = newBudget(RENDER_BUDGET_LIMITS[0])) {
|
|
126
310
|
const base = operationReference(operation);
|
|
127
|
-
|
|
311
|
+
// The step being rendered joins the path before its own clause is built, so
|
|
312
|
+
// a self-capture falls back on the first hop.
|
|
313
|
+
const clause = bindingClause(step, level, index, new Set([...rendering, step.stepId]), budget);
|
|
128
314
|
return clause === null ? base : `${base} (${clause})`;
|
|
129
315
|
}
|
|
130
316
|
/**
|
|
@@ -140,12 +326,34 @@ function stepReferenceAtLevel(step, operation, level) {
|
|
|
140
326
|
* duplicate `stepId` check if two siblings still collide after full
|
|
141
327
|
* escalation.
|
|
142
328
|
*/
|
|
143
|
-
export function renderStepReference(step, operation, siblings) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
329
|
+
export function renderStepReference(step, operation, siblings, index) {
|
|
330
|
+
// Two passes over the ladder, at the ordinary budget and then at the ceiling.
|
|
331
|
+
// The retry fires only when every level collided AND some render had an
|
|
332
|
+
// expansion suppressed, which is what breaks the coupling the budget would
|
|
333
|
+
// otherwise introduce: without it, growing a literal on a step unrelated to
|
|
334
|
+
// either sibling can starve the expansion that separates them and make an
|
|
335
|
+
// otherwise-sealable contract throw. A collision with nothing suppressed is
|
|
336
|
+
// the declared structure's own tie, and a larger budget renders the same
|
|
337
|
+
// text, so that case leaves the loop rather than paying for a second pass.
|
|
338
|
+
for (const limit of RENDER_BUDGET_LIMITS) {
|
|
339
|
+
let starved = false;
|
|
340
|
+
for (const level of ESCALATION_LEVELS) {
|
|
341
|
+
const rendered = siblings.map((sibling) => {
|
|
342
|
+
const budget = newBudget(limit);
|
|
343
|
+
return {
|
|
344
|
+
phrase: stepReferenceAtLevel(sibling, operation, level, index, new Set(), budget),
|
|
345
|
+
spent: budget.spent,
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
const phrases = rendered.map((entry) => entry.phrase);
|
|
349
|
+
if (new Set(phrases).size === phrases.length) {
|
|
350
|
+
return stepReferenceAtLevel(step, operation, level, index, new Set(), newBudget(limit));
|
|
351
|
+
}
|
|
352
|
+
if (rendered.some((entry) => entry.spent))
|
|
353
|
+
starved = true;
|
|
148
354
|
}
|
|
355
|
+
if (!starved)
|
|
356
|
+
break;
|
|
149
357
|
}
|
|
150
358
|
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
359
|
}
|
|
@@ -216,9 +424,9 @@ function localTargetPhrase(target) {
|
|
|
216
424
|
: 'the standard error you obtained';
|
|
217
425
|
}
|
|
218
426
|
}
|
|
219
|
-
function fullTargetPhrase(resolved, siblingsOf) {
|
|
427
|
+
function fullTargetPhrase(resolved, siblingsOf, index) {
|
|
220
428
|
const local = localTargetPhrase(resolved.target);
|
|
221
|
-
const stepRef = renderStepReference(resolved.step, resolved.operation, siblingsOf(resolved.operation.operationId));
|
|
429
|
+
const stepRef = renderStepReference(resolved.step, resolved.operation, siblingsOf(resolved.operation.operationId), index);
|
|
222
430
|
const preposition = resolved.target.channel === 'call-inputs' ? 'to' : 'from';
|
|
223
431
|
return `${local} ${preposition} ${stepRef}`;
|
|
224
432
|
}
|
|
@@ -236,7 +444,7 @@ function channelSignature(target) {
|
|
|
236
444
|
// never degenerates to argument order, since colliding same-operation
|
|
237
445
|
// siblings already throw in `renderStepReference`, and different operations
|
|
238
446
|
// always humanize to different names.
|
|
239
|
-
function sentFirstOrder(a, b, siblingsOf) {
|
|
447
|
+
function sentFirstOrder(a, b, siblingsOf, index) {
|
|
240
448
|
const rank = (r) => r.target.channel === 'call-inputs' ? 0 : 1;
|
|
241
449
|
const rankA = rank(a);
|
|
242
450
|
const rankB = rank(b);
|
|
@@ -246,8 +454,8 @@ function sentFirstOrder(a, b, siblingsOf) {
|
|
|
246
454
|
const signatureB = channelSignature(b.target);
|
|
247
455
|
if (signatureA !== signatureB)
|
|
248
456
|
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));
|
|
457
|
+
const referenceA = renderStepReference(a.step, a.operation, siblingsOf(a.operation.operationId), index);
|
|
458
|
+
const referenceB = renderStepReference(b.step, b.operation, siblingsOf(b.operation.operationId), index);
|
|
251
459
|
return referenceA <= referenceB ? [a, b] : [b, a];
|
|
252
460
|
}
|
|
253
461
|
// Two evidence targets whose steps are declared in an `after` relationship
|
|
@@ -340,19 +548,19 @@ function groupResolvedTargets(resolved) {
|
|
|
340
548
|
}
|
|
341
549
|
return groups;
|
|
342
550
|
}
|
|
343
|
-
function renderPhraseGroup(group, siblingsOf) {
|
|
551
|
+
function renderPhraseGroup(group, siblingsOf, index) {
|
|
344
552
|
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)}`;
|
|
553
|
+
const [first, second] = sentFirstOrder(group.a, group.b, siblingsOf, index);
|
|
554
|
+
return `${fullTargetPhrase(first, siblingsOf, index)}, compared with ${fullTargetPhrase(second, siblingsOf, index)}`;
|
|
347
555
|
}
|
|
348
556
|
const first = group.resolved[0];
|
|
349
557
|
if (first === undefined) {
|
|
350
558
|
throw new TypeError('evidence-target group is empty');
|
|
351
559
|
}
|
|
352
560
|
if (group.resolved.length === 1) {
|
|
353
|
-
return fullTargetPhrase(first, siblingsOf);
|
|
561
|
+
return fullTargetPhrase(first, siblingsOf, index);
|
|
354
562
|
}
|
|
355
|
-
const stepRef = renderStepReference(first.step, first.operation, siblingsOf(first.operation.operationId));
|
|
563
|
+
const stepRef = renderStepReference(first.step, first.operation, siblingsOf(first.operation.operationId), index);
|
|
356
564
|
// Sorted, so a same-step group's field order is permutation-invariant like
|
|
357
565
|
// the top-level join below; in declaration order, permuting
|
|
358
566
|
// `evidenceTargets` would change which field is named first here.
|
|
@@ -378,6 +586,6 @@ export function renderEvidenceReferences(pointers, index) {
|
|
|
378
586
|
const siblings = siblingsByOperation(resolved);
|
|
379
587
|
const siblingsOf = (operationId) => siblings.get(operationId) ?? [];
|
|
380
588
|
const groups = groupResolvedTargets(resolved);
|
|
381
|
-
const phrases = groups.map((group) => renderPhraseGroup(group, siblingsOf));
|
|
589
|
+
const phrases = groups.map((group) => renderPhraseGroup(group, siblingsOf, index));
|
|
382
590
|
return joinWithAnd([...phrases].sort());
|
|
383
591
|
}
|
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
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AD-34's stage-shape vocabulary. Two conformance types pin the exact shapes
|
|
3
|
-
* of `compile` and `seal`;
|
|
4
|
-
* plan/reduce pair, which `preflight` implements
|
|
3
|
+
* of `compile` and `seal`; four generic types cover the rest -- the
|
|
4
|
+
* conditional plan/reduce pair, which `preflight` implements, `IngestStage`,
|
|
5
|
+
* generic over the one product it owns, and `ScoreStage`, generic over both
|
|
6
|
+
* the trial set's element type and the product it owns.
|
|
5
7
|
*
|
|
6
8
|
* AD-24's six-stage input/output/owner/lineage table lives in
|
|
7
9
|
* `core/lineage/stage-table.ts`. This file holds the stage *shapes* TypeScript
|
|
@@ -9,7 +11,15 @@
|
|
|
9
11
|
* Neither imports the other.
|
|
10
12
|
*/
|
|
11
13
|
import type { EvalContract } from './schemas/eval-contract.ts';
|
|
14
|
+
import type { EvaluatorConfiguration } from './schemas/evaluator-configuration.ts';
|
|
15
|
+
import type { EvidenceArtifact } from './schemas/evidence-artifact.ts';
|
|
16
|
+
import type { IsolationManifest } from './schemas/isolation-manifest.ts';
|
|
17
|
+
import type { PreflightVerdict } from './schemas/preflight-verdict.ts';
|
|
18
|
+
import type { Probe } from './schemas/probe.ts';
|
|
19
|
+
import type { ScoringPolicy } from './schemas/scoring-policy.ts';
|
|
12
20
|
import type { SealedEvaluatorBrief } from './schemas/sealed-evaluator-brief.ts';
|
|
21
|
+
import type { SealedRunRecord } from './schemas/sealed-run-record.ts';
|
|
22
|
+
import type { WaiverStateValue } from './score/outcome.ts';
|
|
13
23
|
/** The core compile stage's one runtime option. Core behavior never depends on an implicit configuration source (AD-1), so this is required; `application/compile.ts` is the only caller that supplies a default. */
|
|
14
24
|
export type CompileOptions = {
|
|
15
25
|
readonly strict: boolean;
|
|
@@ -28,3 +38,53 @@ export type SealStage = (contract: EvalContract) => SealedEvaluatorBrief;
|
|
|
28
38
|
export type PlanStage<InputArtifact, RequestDescription> = (input: InputArtifact) => RequestDescription;
|
|
29
39
|
/** A stage's pure reducing half: the plan plus the observations a port produced, reduced to the next artifact. */
|
|
30
40
|
export type ReduceStage<RequestDescription, Observation, OutputArtifact> = (plan: RequestDescription, observations: Observation) => OutputArtifact;
|
|
41
|
+
/**
|
|
42
|
+
* The ingest stage: the three artifacts AD-24's table declares as its inputs,
|
|
43
|
+
* validated into the internal product it owns. `core/ingest/ingest.ts`
|
|
44
|
+
* implements it.
|
|
45
|
+
*
|
|
46
|
+
* Three parameters and no fourth. AD-24 names `mode` as a value input and the
|
|
47
|
+
* sealed run record is where mode is read from, so restating it as an argument
|
|
48
|
+
* would create a disagreement case the design has no rule for.
|
|
49
|
+
*
|
|
50
|
+
* The manifest and the evaluator configuration both admit `null` because AD-16
|
|
51
|
+
* and AD-24 each make an absent artifact a condition this stage records rather
|
|
52
|
+
* than a shape a caller may not present. Their other shared clause, incomplete,
|
|
53
|
+
* is a schema rejection raised before this stage sees anything.
|
|
54
|
+
*
|
|
55
|
+
* Generic over the product for the same reason `ReduceStage` is: a concrete
|
|
56
|
+
* return type would import from `core/ingest/`, which imports this file back.
|
|
57
|
+
*/
|
|
58
|
+
export type IngestStage<Product> = (record: SealedRunRecord, manifest: IsolationManifest | null, configuration: EvaluatorConfiguration | null) => Product;
|
|
59
|
+
/**
|
|
60
|
+
* The score stage: `STAGE_SIGNATURES.score`'s five declared artifact inputs,
|
|
61
|
+
* plus the two caller-supplied value parameters neither the trial set nor
|
|
62
|
+
* any declared input carries a source for (`score.ts`'s own Boundaries:
|
|
63
|
+
* `waiver` mirrors `outcome.ts:58-61`'s AD-5 expiry citation, and
|
|
64
|
+
* `evaluationFault` has no citation anywhere and is recorded as a genuine
|
|
65
|
+
* gap this parameter closes).
|
|
66
|
+
*
|
|
67
|
+
* Generic over both the trial set's element type and the owned product, for
|
|
68
|
+
* the same reason `IngestStage` is generic over its product: a concrete
|
|
69
|
+
* `ValidatedObservations` here would import `core/ingest/ingest.ts`, and a
|
|
70
|
+
* concrete `ScoredOutcomesAndVerdict` would import `core/score/score.ts`,
|
|
71
|
+
* either of which imports this file back to type itself.
|
|
72
|
+
*/
|
|
73
|
+
export type ScoreStage<Trials, Product> = (contract: EvalContract, trials: readonly Trials[], probe: Probe, preflightVerdict: PreflightVerdict, policy: ScoringPolicy, waiver: WaiverStateValue, evaluationFault: boolean) => Product;
|
|
74
|
+
/**
|
|
75
|
+
* The emit stage: `STAGE_SIGNATURES.emit`'s one declared artifact input plus
|
|
76
|
+
* the three caller-attested AD-11 digests, minting the `EvidenceArtifact`
|
|
77
|
+
* neither `score` nor any earlier stage can construct alone. One generic
|
|
78
|
+
* parameter, not two: `EvidenceArtifact` is imported concretely here, the
|
|
79
|
+
* same way this file already imports several other concrete schema types
|
|
80
|
+
* above, so importing one more from the same directory opens no cycle;
|
|
81
|
+
* only the input (`ScoredOutcomesAndVerdict`, defined in
|
|
82
|
+
* `core/score/score.ts`) needs genericity, for the same import-cycle reason
|
|
83
|
+
* `IngestStage`/`ScoreStage` are generic over theirs.
|
|
84
|
+
*
|
|
85
|
+
* The three digests are plain `string` rather than a `Digest` type: no
|
|
86
|
+
* `core/schemas` module exports a bare TypeScript `Digest` type, only the
|
|
87
|
+
* Zod schema, and `EvidenceArtifact`'s own parse is where their AD-27 shape
|
|
88
|
+
* is actually enforced.
|
|
89
|
+
*/
|
|
90
|
+
export type EmitStage<Input> = (scored: Input, corpusDigest: string, fixtureDigest: string, evaluatorConfigurationDigest: string) => EvidenceArtifact;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export type { ScoringPolicy } from './core/schemas/scoring-policy.ts';
|
|
|
12
12
|
export type { SealedEvaluatorBrief } from './core/schemas/sealed-evaluator-brief.ts';
|
|
13
13
|
export type { SealedRunRecord } from './core/schemas/sealed-run-record.ts';
|
|
14
14
|
export type { FixtureReset, ManifestationWitness, SensitivityWitness, SensitivityWitnessLeg, WitnessChannel, WitnessInputs, } from './core/schemas/sensitivity-witness.ts';
|
|
15
|
-
export declare const VERSION = "0.
|
|
15
|
+
export declare const VERSION = "0.2.0";
|
package/dist/index.js
CHANGED
package/dist/testing/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* Type re-exports use `export type`; `biome.json` sets
|
|
11
11
|
* `style.useExportType: "error"`.
|
|
12
12
|
*/
|
|
13
|
+
export type { RuntimeFaultCode } from '../core/schemas/faults.ts';
|
|
14
|
+
export { RUNTIME_FAULT_CODES, RuntimeFault } from '../core/schemas/faults.ts';
|
|
13
15
|
export type { ClockReadRequest, ClockReadResponse, CorpusResolveRequest, CorpusResolveResponse, FileReadRequest, FileReadResponse, FileWriteRequest, FileWriteResponse, ProbeObservation, ProbeObservedBody, ProbeRequest, ProbeRequestBody, } from '../core/schemas/port-messages.ts';
|
|
14
16
|
export type { ProbeTargetAuthorization, ProbeTargetPolicy, } from '../core/schemas/probe-policy.ts';
|
|
15
17
|
export type { ClockPort } from '../ports/clock-port.ts';
|
package/dist/testing/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Type re-exports use `export type`; `biome.json` sets
|
|
11
11
|
* `style.useExportType: "error"`.
|
|
12
12
|
*/
|
|
13
|
+
export { RUNTIME_FAULT_CODES, RuntimeFault } from '../core/schemas/faults.js';
|
|
13
14
|
export { clockReadParsers } from '../ports/clock-port.js';
|
|
14
15
|
export { corpusResolveParsers } from '../ports/corpus-port.js';
|
|
15
16
|
export { probeParsers } from '../ports/environment-probe-port.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eval-quality",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Compile disciplined Behavioral Evaluation Contracts and score their ability to catch known defects.",
|
|
5
5
|
"author": "Murat Ozcan",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -87,11 +87,17 @@
|
|
|
87
87
|
"check:ad28-registry": "node scripts/check-ad28-registry.ts",
|
|
88
88
|
"generate:ad31-table": "node scripts/generate-ad31-table.ts",
|
|
89
89
|
"check:ad31-table": "node scripts/check-ad31-table.ts",
|
|
90
|
+
"generate:ad33-table": "node scripts/generate-ad33-table.ts",
|
|
91
|
+
"check:ad33-table": "node scripts/check-ad33-table.ts",
|
|
92
|
+
"generate:ad21-table": "node scripts/generate-ad21-table.ts",
|
|
93
|
+
"check:ad21-table": "node scripts/check-ad21-table.ts",
|
|
90
94
|
"check:layers": "node scripts/check-dependency-direction.ts",
|
|
91
95
|
"check:lineage": "node scripts/check-lineage-ownership.ts",
|
|
92
96
|
"check:boundary": "node scripts/check-package-boundary.ts",
|
|
93
97
|
"generate:dev-corpus": "node scripts/generate-dev-corpus.ts",
|
|
94
98
|
"check:corpus": "node scripts/check-dev-corpus.ts",
|
|
99
|
+
"generate:worked-example": "node scripts/generate-worked-example.ts",
|
|
100
|
+
"check:worked-example": "node scripts/check-worked-example.ts",
|
|
95
101
|
"check:website-deps": "node scripts/audit-lockfile-age.mjs --lockfile website/package-lock.json && node scripts/check-licenses.mjs --lockfile website/package-lock.json",
|
|
96
102
|
"check:shareable": "node scripts/check-shareable.mjs",
|
|
97
103
|
"bench:digest": "node scripts/bench-digest.ts",
|
|
@@ -99,9 +105,12 @@
|
|
|
99
105
|
"docs:build": "node tools/build-docs.mjs",
|
|
100
106
|
"docs:preview": "npm --prefix website run preview",
|
|
101
107
|
"docs:validate-links": "node tools/validate-doc-links.js",
|
|
108
|
+
"release:patch": "gh workflow run publish.yml --ref main -f bump=patch",
|
|
109
|
+
"release:minor": "gh workflow run publish.yml --ref main -f bump=minor",
|
|
110
|
+
"release:major": "gh workflow run publish.yml --ref main -f bump=major",
|
|
102
111
|
"release:prepare": "node scripts/release-prepare.mjs",
|
|
103
|
-
"release:publish": "gh workflow run publish.yml --ref main",
|
|
104
|
-
"validate": "npm run build && npm run typecheck && npm run lint && npm run check:docs && npm run check:doc-invocations && npm run check:shareable && npm run lint:spine && npm run check:vectors && npm run check:schemas && npm run check:ad5-registry && npm run check:ad28-registry && npm run check:ad31-table && npm run check:layers && npm run check:lineage && npm run check:boundary && npm run check:corpus && npm run check:website-deps && npm run test:coverage",
|
|
112
|
+
"release:publish": "gh workflow run publish.yml --ref main -f bump=none",
|
|
113
|
+
"validate": "npm run build && npm run typecheck && npm run lint && npm run check:docs && npm run check:doc-invocations && npm run check:shareable && npm run lint:spine && npm run check:vectors && npm run check:schemas && npm run check:ad5-registry && npm run check:ad28-registry && npm run check:ad31-table && npm run check:ad33-table && npm run check:ad21-table && npm run check:layers && npm run check:lineage && npm run check:boundary && npm run check:corpus && npm run check:worked-example && npm run check:website-deps && npm run test:coverage",
|
|
105
114
|
"prepack": "npm run clean && npm run build",
|
|
106
115
|
"prepublishOnly": "node scripts/assert-publish-authorized.mjs"
|
|
107
116
|
},
|