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.
Files changed (133) hide show
  1. package/README.md +142 -67
  2. package/corpus/dev/README.md +17 -11
  3. package/corpus/dev/compile-seal-example/brief.json +1 -1
  4. package/corpus/dev/compile-seal-example/contract.json +1 -1
  5. package/corpus/dev/contracts/absent-collection-locations.json +1 -1
  6. package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
  7. package/corpus/dev/contracts/absent-success-indicator.json +1 -1
  8. package/corpus/dev/contracts/empty-channel-roles.json +1 -1
  9. package/corpus/dev/contracts/empty-collection-locations.json +1 -1
  10. package/corpus/dev/contracts/empty-request-shapes.json +1 -1
  11. package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
  12. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  13. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  14. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  15. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  16. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  17. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  18. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  19. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  20. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  21. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  22. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  23. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  24. package/corpus/dev/index.json +1 -1
  25. package/dist/application/index.d.ts +2 -0
  26. package/dist/application/index.js +1 -0
  27. package/dist/application/score.d.ts +48 -0
  28. package/dist/application/score.js +186 -0
  29. package/dist/cli/arguments.d.ts +7 -3
  30. package/dist/cli/arguments.js +73 -5
  31. package/dist/cli/exit-codes.d.ts +6 -2
  32. package/dist/cli/exit-codes.js +8 -9
  33. package/dist/cli/main.js +9 -0
  34. package/dist/cli/render.d.ts +1 -1
  35. package/dist/cli/render.js +6 -4
  36. package/dist/cli/run.d.ts +13 -3
  37. package/dist/cli/run.js +136 -16
  38. package/dist/core/canonical/canonicalize.d.ts +12 -0
  39. package/dist/core/canonical/canonicalize.js +18 -4
  40. package/dist/core/compile/bindings.d.ts +70 -0
  41. package/dist/core/compile/bindings.js +317 -0
  42. package/dist/core/compile/compile.d.ts +5 -0
  43. package/dist/core/compile/compile.js +9 -0
  44. package/dist/core/compile/expression-legality.d.ts +49 -0
  45. package/dist/core/compile/expression-legality.js +105 -31
  46. package/dist/core/compile/forbidden-inputs.d.ts +8 -1
  47. package/dist/core/compile/forbidden-inputs.js +16 -3
  48. package/dist/core/compile/interface-inventory.d.ts +23 -1
  49. package/dist/core/compile/interface-inventory.js +32 -3
  50. package/dist/core/compile/reachability.d.ts +18 -1
  51. package/dist/core/compile/reachability.js +46 -1
  52. package/dist/core/compile/sensitivity-witness.d.ts +1 -1
  53. package/dist/core/compile/sensitivity-witness.js +1 -1
  54. package/dist/core/emit/emit.d.ts +9 -0
  55. package/dist/core/emit/emit.js +214 -0
  56. package/dist/core/emit/private-artifact-digest.d.ts +11 -0
  57. package/dist/core/emit/private-artifact-digest.js +31 -0
  58. package/dist/core/evaluate/evidence-resolution.d.ts +11 -1
  59. package/dist/core/evaluate/evidence-resolution.js +4 -1
  60. package/dist/core/failure-codes.d.ts +2 -2
  61. package/dist/core/failure-codes.js +4 -2
  62. package/dist/core/ingest/conditions.d.ts +201 -0
  63. package/dist/core/ingest/conditions.js +48 -0
  64. package/dist/core/ingest/index.d.ts +13 -0
  65. package/dist/core/ingest/index.js +11 -0
  66. package/dist/core/ingest/ingest.d.ts +68 -0
  67. package/dist/core/ingest/ingest.js +310 -0
  68. package/dist/core/lineage/stage-table.d.ts +21 -4
  69. package/dist/core/lineage/stage-table.js +35 -3
  70. package/dist/core/preflight/witness-evidence.js +10 -0
  71. package/dist/core/schemas/artifact.d.ts +385 -14
  72. package/dist/core/schemas/constraint-ledger.js +40 -0
  73. package/dist/core/schemas/defect-signature.d.ts +273 -0
  74. package/dist/core/schemas/defect-signature.js +127 -0
  75. package/dist/core/schemas/eval-contract.d.ts +33 -0
  76. package/dist/core/schemas/eval-contract.js +14 -0
  77. package/dist/core/schemas/evidence-artifact.d.ts +99 -19
  78. package/dist/core/schemas/evidence-artifact.js +64 -8
  79. package/dist/core/schemas/plan.d.ts +112 -0
  80. package/dist/core/schemas/plan.js +67 -2
  81. package/dist/core/schemas/probe-qualification.d.ts +168 -0
  82. package/dist/core/schemas/probe-qualification.js +106 -0
  83. package/dist/core/schemas/probe.d.ts +312 -0
  84. package/dist/core/schemas/probe.js +6 -2
  85. package/dist/core/schemas/scoring-policy.d.ts +1 -0
  86. package/dist/core/schemas/scoring-policy.js +6 -1
  87. package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
  88. package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
  89. package/dist/core/schemas/sealed-run-record.d.ts +30 -3
  90. package/dist/core/schemas/sealed-run-record.js +37 -5
  91. package/dist/core/score/binding-order.d.ts +31 -0
  92. package/dist/core/score/binding-order.js +88 -0
  93. package/dist/core/score/bindings.d.ts +93 -0
  94. package/dist/core/score/bindings.js +297 -0
  95. package/dist/core/score/ladder-table.d.ts +23 -0
  96. package/dist/core/score/ladder-table.js +109 -0
  97. package/dist/core/score/ladder.d.ts +229 -0
  98. package/dist/core/score/ladder.js +464 -0
  99. package/dist/core/score/mode-agreement.d.ts +30 -0
  100. package/dist/core/score/mode-agreement.js +16 -0
  101. package/dist/core/score/outcome-table.d.ts +17 -0
  102. package/dist/core/score/outcome-table.js +172 -0
  103. package/dist/core/score/outcome.d.ts +466 -0
  104. package/dist/core/score/outcome.js +562 -0
  105. package/dist/core/score/qualification.d.ts +83 -0
  106. package/dist/core/score/qualification.js +615 -0
  107. package/dist/core/score/quotation.d.ts +79 -0
  108. package/dist/core/score/quotation.js +201 -0
  109. package/dist/core/score/reduce-trials.d.ts +80 -0
  110. package/dist/core/score/reduce-trials.js +90 -0
  111. package/dist/core/score/score.d.ts +72 -0
  112. package/dist/core/score/score.js +581 -0
  113. package/dist/core/score/selection.d.ts +80 -0
  114. package/dist/core/score/selection.js +98 -0
  115. package/dist/core/score/strength.d.ts +49 -0
  116. package/dist/core/score/strength.js +193 -0
  117. package/dist/core/score/witness.d.ts +117 -0
  118. package/dist/core/score/witness.js +315 -0
  119. package/dist/core/seal/derived-reference.d.ts +1 -1
  120. package/dist/core/seal/derived-reference.js +236 -28
  121. package/dist/core/seal/seal.js +8 -3
  122. package/dist/core/stage-contracts.d.ts +62 -2
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/testing/index.d.ts +2 -0
  126. package/dist/testing/index.js +1 -0
  127. package/package.json +12 -3
  128. package/schemas/eval-contract.schema.json +100 -2
  129. package/schemas/evidence-artifact.schema.json +96 -9
  130. package/schemas/probe.schema.json +508 -3
  131. package/schemas/scoring-policy.schema.json +8 -1
  132. package/schemas/sealed-evaluator-brief.schema.json +11 -1
  133. 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: `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.
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
- if (isTypeViolating(entry.value)) {
93
- return `a malformed ${name} value`;
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' || 'matcher' in entry.value) {
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(entry.value.literal)}`;
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
- return `with ${joinWithAnd(chosen.map((entry) => renderBindingValue(entry, level)))}`;
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
- const clause = bindingClause(step, level);
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
- 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);
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
  }
@@ -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
- schemaVersion: 1,
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`; two generic types record the conditional
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.1.0";
15
+ export declare const VERSION = "0.2.0";
package/dist/index.js CHANGED
@@ -19,4 +19,4 @@
19
19
  // subpath, where AD-37 puts the conformance definition an adapter author
20
20
  // reads; the reference adapters stay at `eval-quality/adapters`.
21
21
  export * from './application/index.js';
22
- export const VERSION = '0.1.0';
22
+ export const VERSION = '0.2.0';
@@ -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';
@@ -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.1.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
  },