eval-quality 0.2.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.
Files changed (125) hide show
  1. package/README.md +3 -3
  2. package/corpus/dev/README.md +3 -2
  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/fragment-selection.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  19. package/corpus/dev/contracts/review-corpus.json +1 -0
  20. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  21. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  22. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  23. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  24. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  25. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  26. package/corpus/dev/index.json +1 -1
  27. package/dist/cli/render.d.ts +2 -5
  28. package/dist/cli/render.js +51 -1
  29. package/dist/core/compile/bindings.d.ts +26 -7
  30. package/dist/core/compile/bindings.js +49 -32
  31. package/dist/core/compile/compile.d.ts +12 -0
  32. package/dist/core/compile/compile.js +28 -1
  33. package/dist/core/compile/excluded-content.d.ts +11 -0
  34. package/dist/core/compile/excluded-content.js +42 -0
  35. package/dist/core/compile/expression-legality.d.ts +2 -2
  36. package/dist/core/compile/expression-legality.js +58 -12
  37. package/dist/core/compile/interface-inventory.d.ts +34 -1
  38. package/dist/core/compile/interface-inventory.js +88 -11
  39. package/dist/core/compile/reachability.d.ts +12 -2
  40. package/dist/core/compile/reachability.js +123 -36
  41. package/dist/core/compile/schema-version.d.ts +2 -0
  42. package/dist/core/compile/schema-version.js +25 -0
  43. package/dist/core/compile/sensitivity-witness.d.ts +31 -12
  44. package/dist/core/compile/sensitivity-witness.js +110 -23
  45. package/dist/core/compile/step-reference.d.ts +2 -0
  46. package/dist/core/compile/step-reference.js +49 -0
  47. package/dist/core/coverage/operations.d.ts +62 -0
  48. package/dist/core/coverage/operations.js +57 -0
  49. package/dist/core/coverage/relevance.d.ts +4 -2
  50. package/dist/core/coverage/relevance.js +22 -23
  51. package/dist/core/coverage/satisfaction.d.ts +2 -2
  52. package/dist/core/coverage/satisfaction.js +73 -48
  53. package/dist/core/declared-inputs.d.ts +83 -4
  54. package/dist/core/declared-inputs.js +105 -8
  55. package/dist/core/evaluate/evidence-resolution.d.ts +9 -12
  56. package/dist/core/evaluate/evidence-resolution.js +70 -10
  57. package/dist/core/evaluate/operators.d.ts +22 -0
  58. package/dist/core/evaluate/operators.js +17 -1
  59. package/dist/core/evaluate/resolution.d.ts +14 -3
  60. package/dist/core/evaluate/resolution.js +63 -3
  61. package/dist/core/excluded-content.d.ts +65 -0
  62. package/dist/core/excluded-content.js +113 -0
  63. package/dist/core/failure-codes.d.ts +2 -2
  64. package/dist/core/failure-codes.js +5 -2
  65. package/dist/core/ingest/conditions.d.ts +1 -1
  66. package/dist/core/ingest/ingest.js +8 -0
  67. package/dist/core/preflight/plan.d.ts +7 -5
  68. package/dist/core/preflight/plan.js +75 -32
  69. package/dist/core/preflight/projection.d.ts +6 -3
  70. package/dist/core/preflight/projection.js +22 -2
  71. package/dist/core/preflight/reduce.js +47 -7
  72. package/dist/core/preflight/witness-evidence.d.ts +5 -5
  73. package/dist/core/preflight/witness-evidence.js +63 -20
  74. package/dist/core/schemas/artifact.d.ts +683 -35
  75. package/dist/core/schemas/constraint-ledger.js +11 -0
  76. package/dist/core/schemas/defect-signature.d.ts +438 -16
  77. package/dist/core/schemas/defect-signature.js +58 -10
  78. package/dist/core/schemas/eval-contract.d.ts +517 -12
  79. package/dist/core/schemas/eval-contract.js +18 -2
  80. package/dist/core/schemas/evidence-artifact.d.ts +14 -4
  81. package/dist/core/schemas/evidence-artifact.js +1 -1
  82. package/dist/core/schemas/interface.d.ts +741 -13
  83. package/dist/core/schemas/interface.js +120 -7
  84. package/dist/core/schemas/isolation-manifest.js +16 -9
  85. package/dist/core/schemas/plan.d.ts +217 -3
  86. package/dist/core/schemas/plan.js +22 -1
  87. package/dist/core/schemas/pointer.d.ts +47 -1
  88. package/dist/core/schemas/pointer.js +89 -8
  89. package/dist/core/schemas/port-messages.d.ts +194 -10
  90. package/dist/core/schemas/port-messages.js +73 -6
  91. package/dist/core/schemas/probe-body.d.ts +18 -0
  92. package/dist/core/schemas/probe-body.js +13 -0
  93. package/dist/core/schemas/probe.d.ts +122 -3
  94. package/dist/core/schemas/probe.js +1 -1
  95. package/dist/core/schemas/sealed-run-record.d.ts +131 -9
  96. package/dist/core/schemas/sealed-run-record.js +56 -11
  97. package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
  98. package/dist/core/schemas/sensitivity-witness.js +61 -5
  99. package/dist/core/score/bindings.d.ts +2 -2
  100. package/dist/core/score/bindings.js +25 -13
  101. package/dist/core/score/qualification.d.ts +5 -5
  102. package/dist/core/score/qualification.js +67 -37
  103. package/dist/core/score/quotation.d.ts +1 -24
  104. package/dist/core/score/quotation.js +29 -4
  105. package/dist/core/score/score.js +28 -2
  106. package/dist/core/score/strength.d.ts +9 -0
  107. package/dist/core/score/strength.js +35 -1
  108. package/dist/core/score/witness.d.ts +21 -0
  109. package/dist/core/score/witness.js +9 -4
  110. package/dist/core/seal/derived-reference.d.ts +2 -9
  111. package/dist/core/seal/derived-reference.js +62 -23
  112. package/dist/core/seal/plan-index.d.ts +18 -3
  113. package/dist/core/seal/plan-index.js +44 -8
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.js +1 -1
  116. package/dist/ports/environment-probe-port.d.ts +60 -4
  117. package/dist/testing/probe-conformance.d.ts +29 -0
  118. package/dist/testing/probe-conformance.js +34 -5
  119. package/package.json +1 -1
  120. package/schemas/eval-contract.schema.json +1356 -464
  121. package/schemas/evidence-artifact.schema.json +59 -29
  122. package/schemas/isolation-manifest.schema.json +17 -10
  123. package/schemas/probe.schema.json +197 -71
  124. package/schemas/rubric.schema.json +1 -1
  125. package/schemas/sealed-run-record.schema.json +296 -39
@@ -31,6 +31,18 @@ export declare function setMembership(value: ResolvedValue, set: JsonValue[], ar
31
31
  * single element to search for: this function receives resolved values only,
32
32
  * so it cannot tell a `{ literal }` array from a resolved `{ referenceSet }`.
33
33
  * Known, accepted limitation.
34
+ *
35
+ * A separate property, and a separate limitation: a `{ referenceSet }`
36
+ * candidate matches **whole declared members** by structural equality. A
37
+ * container element carrying any field the reference set does not declare
38
+ * therefore never matches, which is the ordinary case for rows read off a
39
+ * response body. `set-membership`'s set position projects a reference set to
40
+ * its single declared key and this position does not, because projecting only
41
+ * where a container happens to hold objects would make the operand's meaning
42
+ * depend on the evidence's runtime shape. An author reconciling rows against a
43
+ * declared set wants `covers-by-key`, which compares on named keys on both
44
+ * sides, or `for-all(rows, set-membership(@/key, { referenceSet }))`, which
45
+ * reads the declared key.
34
46
  */
35
47
  export declare function containment(container: ResolvedValue, candidate: ResolvedValue | JsonValue[], artifactPath: string): boolean;
36
48
  /**
@@ -62,6 +74,16 @@ export declare function countTolerance(collection: ResolvedValue, expected: numb
62
74
  * that is required and not permitted reaches here and fails every value.
63
75
  */
64
76
  export declare function shape(value: ResolvedValue, descriptor: KeyedShapeDescriptor, _artifactPath: string): boolean;
77
+ /**
78
+ * Own-property lookup only, so a key like `__proto__` reads as missing
79
+ * rather than inherited. Returns `ABSENT`, never throws (AD-4: a missing key
80
+ * resolves `false`, not an error).
81
+ *
82
+ * Exported so `resolution.ts`'s `set-membership` set-operand projection reads
83
+ * a declared member's key through this one spelling, keeping the own-property
84
+ * guard in one place.
85
+ */
86
+ export declare function keyValueOf(element: JsonValue, key: string): JsonValue | typeof ABSENT;
65
87
  /**
66
88
  * AD-4's bijection: equal cardinality and a distinct `actual` match per
67
89
  * `expected` element on the named keys. `ABSENT` on either side resolves
@@ -90,6 +90,18 @@ export function setMembership(value, set, artifactPath) {
90
90
  * single element to search for: this function receives resolved values only,
91
91
  * so it cannot tell a `{ literal }` array from a resolved `{ referenceSet }`.
92
92
  * Known, accepted limitation.
93
+ *
94
+ * A separate property, and a separate limitation: a `{ referenceSet }`
95
+ * candidate matches **whole declared members** by structural equality. A
96
+ * container element carrying any field the reference set does not declare
97
+ * therefore never matches, which is the ordinary case for rows read off a
98
+ * response body. `set-membership`'s set position projects a reference set to
99
+ * its single declared key and this position does not, because projecting only
100
+ * where a container happens to hold objects would make the operand's meaning
101
+ * depend on the evidence's runtime shape. An author reconciling rows against a
102
+ * declared set wants `covers-by-key`, which compares on named keys on both
103
+ * sides, or `for-all(rows, set-membership(@/key, { referenceSet }))`, which
104
+ * reads the declared key.
93
105
  */
94
106
  export function containment(container, candidate, artifactPath) {
95
107
  if (container === ABSENT)
@@ -287,8 +299,12 @@ export function shape(value, descriptor, _artifactPath) {
287
299
  * Own-property lookup only, so a key like `__proto__` reads as missing
288
300
  * rather than inherited. Returns `ABSENT`, never throws (AD-4: a missing key
289
301
  * resolves `false`, not an error).
302
+ *
303
+ * Exported so `resolution.ts`'s `set-membership` set-operand projection reads
304
+ * a declared member's key through this one spelling, keeping the own-property
305
+ * guard in one place.
290
306
  */
291
- function keyValueOf(element, key) {
307
+ export function keyValueOf(element, key) {
292
308
  if (!isPlainObject(element) || !Object.hasOwn(element, key))
293
309
  return ABSENT;
294
310
  // `Object.hasOwn` above already proves the key is present; `noUncheckedIndexedAccess`
@@ -3,8 +3,9 @@
3
3
  * that turns an `Expression` into one `CheckResolutionValue`. Leaf operators,
4
4
  * `covers-by-key` included, live in `operators.ts`. Operand resolution, every
5
5
  * pointer form including the bound-element `@/` form, is injected;
6
- * `evidence-resolution.ts` supplies it, and `ResolveOperand` and
7
- * `PointerDenotesCollection` are the consumer-side contract it satisfies.
6
+ * `evidence-resolution.ts` supplies it, and `ResolveOperand`,
7
+ * `PointerDenotesCollection`, and `ReferenceSetKeys` are the consumer-side
8
+ * contract it satisfies.
8
9
  */
9
10
  import type { CheckResolutionValue } from '../schemas/evidence-artifact.ts';
10
11
  import type { Expression, Operand } from '../schemas/expression.ts';
@@ -31,10 +32,20 @@ export type ResolveOperand = (operand: Operand, boundElement: ResolvedValue, art
31
32
  * soft-delete agreement pair (AD-4's own worked example).
32
33
  */
33
34
  export type PointerDenotesCollection = (pointer: string) => boolean;
35
+ /**
36
+ * The `keys` each declared reference set names, by identifier.
37
+ * `set-membership`'s set position reads the single declared key off each
38
+ * member (`reference-set.ts`), and the members map the injected
39
+ * `ResolveOperand` closes over has already discarded the keys, so they travel
40
+ * separately. Plain data, because `PreflightPlan` carries this through to
41
+ * its reducer and is compared by value, which a closure fails.
42
+ * `evidence-resolution.ts` builds it from a contract.
43
+ */
44
+ export type ReferenceSetKeys = Readonly<Record<string, readonly string[]>>;
34
45
  /**
35
46
  * The public entry point. Walks `expression` and produces one
36
47
  * `CheckResolutionValue`, exactly what `Outcome.checkResolution` needs,
37
48
  * unmodified. `boundElement` starts `ABSENT` at the root, where no quantifier
38
49
  * has bound anything yet.
39
50
  */
40
- export declare function resolveCheck(expression: Expression, resolveOperand: ResolveOperand, pointerDenotesCollection: PointerDenotesCollection, regexMatchStepBudget: number, artifactPath: string): CheckResolutionValue;
51
+ export declare function resolveCheck(expression: Expression, resolveOperand: ResolveOperand, pointerDenotesCollection: PointerDenotesCollection, referenceSetKeys: ReferenceSetKeys, regexMatchStepBudget: number, artifactPath: string): CheckResolutionValue;
@@ -1,4 +1,4 @@
1
- import { absence, containment, countTolerance, coversByKey, deepEquality, equality, existence, ordering, regexMatch, setMembership, shape, } from './operators.js';
1
+ import { absence, containment, countTolerance, coversByKey, deepEquality, equality, existence, keyValueOf, ordering, regexMatch, setMembership, shape, } from './operators.js';
2
2
  import { ABSENT } from './resolved-value.js';
3
3
  /**
4
4
  * AD-4's one closed introduction condition, checked per operand and applied
@@ -242,10 +242,69 @@ function resolveRegexNode(expression, boundElement, ctx) {
242
242
  const { pattern } = expression;
243
243
  return resolveSingleOperand(operand, boundElement, ctx, (resolved) => regexMatch(resolved, pattern, ctx.regexMatchStepBudget, ctx.artifactPath));
244
244
  }
245
+ /**
246
+ * Reads the single declared key off each member of a `{ referenceSet }` set
247
+ * operand. `reference-set.ts` declares members as objects so one declaration
248
+ * serves both operators, and states that "a `set-membership` operand against
249
+ * the same set reads the single named key". This is where that reading
250
+ * happens, and without it AD-20 rule 6's injection form can never answer
251
+ * `true`: the value operand resolves to a scalar, and `setMembership` compares
252
+ * whole members by digest, so a scalar against an object is always `false`.
253
+ *
254
+ * Scoped to this one operator position on purpose. `covers-by-key` projects
255
+ * both sides itself on `expectedKey` and `actualKey`, and `containment`
256
+ * matches whole members, so both want the members unprojected. The injected
257
+ * `ResolveOperand` is handed one operand at a time and cannot see which
258
+ * operator position it is filling, which is why the projection lives at the
259
+ * resolution site.
260
+ *
261
+ * The option turned down: an explicit `memberKey` field on `set-membership`
262
+ * mirroring `covers-by-key`'s `expectedKey`. It is symmetric with its sibling
263
+ * and it carries a multi-key set, at the price of a grammar change to a
264
+ * published artifact, an eval-contract `schemaVersion` bump, and
265
+ * published-schema drift, for a case no shipped contract has. Revisit it the
266
+ * first time a real contract wants a multi-key set in this position.
267
+ *
268
+ * Both throws are unreachable for a compiled contract:
269
+ * `checkOperandLegality` rejects a multi-key reference set in this position,
270
+ * and a member missing the declared key, under
271
+ * `malformed-operator-expression`. They throw, because a set this function
272
+ * cannot project carries no membership answer to give.
273
+ */
274
+ function projectSetOperand(resolvedSet, setOperand, ctx) {
275
+ if (!('referenceSet' in setOperand))
276
+ return resolvedSet;
277
+ const { referenceSet } = setOperand;
278
+ // `Object.hasOwn`, the same prototype-chain guard `makeResolveOperand`
279
+ // applies to its own maps: `Identifier`'s charset admits `constructor`.
280
+ const keys = Object.hasOwn(ctx.referenceSetKeys, referenceSet)
281
+ ? ctx.referenceSetKeys[referenceSet]
282
+ : undefined;
283
+ // No declared keys means the contract declares no such reference set,
284
+ // which compilation rejects under `unresolved-reference-set`; a non-array
285
+ // is the array guard below reporting whatever the resolver returned. Both
286
+ // pass through so exactly one guard speaks for each.
287
+ if (keys === undefined || !Array.isArray(resolvedSet))
288
+ return resolvedSet;
289
+ if (keys.length !== 1) {
290
+ throw new Error(`set-membership's set-operand projection: referenceSet "${referenceSet}" declares ${keys.length} keys, and this position reads exactly one. A multi-key set here is malformed-operator-expression, which compilation rejects.`);
291
+ }
292
+ const key = keys[0];
293
+ return resolvedSet.map((member) => {
294
+ const keyValue = keyValueOf(member, key);
295
+ if (keyValue === ABSENT) {
296
+ throw new Error(`set-membership's set-operand projection: a member of referenceSet "${referenceSet}" carries no own property "${key}", the key it declares. That is malformed-operator-expression, which compilation rejects.`);
297
+ }
298
+ return keyValue;
299
+ });
300
+ }
245
301
  function resolveSetMembershipNode(expression, boundElement, ctx) {
246
302
  const [valueOperand, setOperand] = expression.operands;
247
303
  const value = ctx.resolveOperand(valueOperand, boundElement, ctx.artifactPath);
248
- const resolvedSet = ctx.resolveOperand(setOperand, boundElement, ctx.artifactPath);
304
+ // Projected before the empty-collection interception and the array guard
305
+ // below, so both read the one value the operator will see. Projection
306
+ // preserves length and array-ness, so neither answer moves.
307
+ const resolvedSet = projectSetOperand(ctx.resolveOperand(setOperand, boundElement, ctx.artifactPath), setOperand, ctx);
249
308
  if (anyOperandEmpty([
250
309
  { operand: valueOperand, resolved: value },
251
310
  { operand: setOperand, resolved: resolvedSet },
@@ -326,10 +385,11 @@ function resolveNode(expression, boundElement, ctx) {
326
385
  * unmodified. `boundElement` starts `ABSENT` at the root, where no quantifier
327
386
  * has bound anything yet.
328
387
  */
329
- export function resolveCheck(expression, resolveOperand, pointerDenotesCollection, regexMatchStepBudget, artifactPath) {
388
+ export function resolveCheck(expression, resolveOperand, pointerDenotesCollection, referenceSetKeys, regexMatchStepBudget, artifactPath) {
330
389
  return resolveNode(expression, ABSENT, {
331
390
  resolveOperand,
332
391
  pointerDenotesCollection,
392
+ referenceSetKeys,
333
393
  regexMatchStepBudget,
334
394
  artifactPath,
335
395
  });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * AD-18's excluded categories, as patterns that read a value rather than a name.
3
+ *
4
+ * AD-18 excludes "credentials, tokens, real names, email addresses, account
5
+ * identifiers, and transaction content" from every artifact this package
6
+ * produces or publishes, and binds "published examples and test fixtures as
7
+ * strictly as real runs". Until this module the whole prohibition was carried by
8
+ * one test that scans `corpus/dev`, so a contract carrying a live-shaped token
9
+ * compiled clean and the token travelled into the sealed brief.
10
+ *
11
+ * Only the value-shaped half of that scan ships here, and the split is the point
12
+ * rather than an economy. AD-18's own reading of the rule for the header channel
13
+ * is that a declaration "names the header and its type and never carries a
14
+ * credential value", so a name is what an author is supposed to write: an
15
+ * operation declaring a `password` body key or an `Authorization` header is a
16
+ * contract for an authentication API, and a gate matching `\bpassword\b` would
17
+ * reject exactly the systems this package exists to evaluate. The patterns below
18
+ * therefore match only strings whose shape is decidable without knowing what
19
+ * field they sit in: a PEM header, a token with an issuer prefix, an address, a
20
+ * checkable account number, a card-shaped digit run. The name-shaped patterns
21
+ * stay in `tests/architecture/dev-corpus.test.ts`, where review rather than a
22
+ * compiler decides.
23
+ *
24
+ * Each pattern carries a string it must fire on, and a test asserts every one
25
+ * still does. A pattern that has rotted into a regex matching nothing is worse
26
+ * than no pattern: it reports clean forever.
27
+ */
28
+ /** AD-18's six categories, in the rule's own order. */
29
+ export declare const EXCLUDED_CATEGORIES: readonly ['credentials', 'tokens', 'real names', 'email addresses', 'account identifiers', 'transaction content'];
30
+ export type ExcludedCategory = (typeof EXCLUDED_CATEGORIES)[number];
31
+ export type ExcludedContentPattern = {
32
+ readonly category: ExcludedCategory;
33
+ readonly regex: RegExp;
34
+ /** a string the pattern must match, so a rotted pattern fails its own test. */
35
+ readonly fires: string;
36
+ };
37
+ /**
38
+ * `real names` has no member here and never will: a personal name has no surface
39
+ * form, so every pattern that reaches one reads the field that carries it, which
40
+ * is a name-shaped test. The category stays in the tuple above because AD-18
41
+ * names six and dropping one from the vocabulary would make the gate look total.
42
+ */
43
+ export declare const EXCLUDED_VALUE_PATTERNS: readonly ExcludedContentPattern[];
44
+ export type ExcludedContentHit = {
45
+ readonly category: ExcludedCategory;
46
+ /** the artifact-rooted path of the string that matched. */
47
+ readonly path: string;
48
+ /** the matched run, which is short by construction and is what a caller redacts. */
49
+ readonly match: string;
50
+ };
51
+ /**
52
+ * Every string anywhere inside one value, against every value-shaped pattern.
53
+ *
54
+ * Object keys are walked as paths and never scanned as content: a key is a name
55
+ * by construction, which is the same line the pattern set is drawn on. The walk
56
+ * is total over JSON and stops at nothing, so a token nested under a body
57
+ * example twelve levels down is found; the first matching category per string
58
+ * wins, since one string reported six times says nothing more than once.
59
+ *
60
+ * Order is the walk's order, which for an object is its own key order. A caller
61
+ * that needs a stable report over two spellings of the same object sorts what
62
+ * comes back; the compile gate below reads only the first hit and never the
63
+ * order.
64
+ */
65
+ export declare function scanExcludedContent(value: unknown, path: string): readonly ExcludedContentHit[];
@@ -0,0 +1,113 @@
1
+ /**
2
+ * AD-18's excluded categories, as patterns that read a value rather than a name.
3
+ *
4
+ * AD-18 excludes "credentials, tokens, real names, email addresses, account
5
+ * identifiers, and transaction content" from every artifact this package
6
+ * produces or publishes, and binds "published examples and test fixtures as
7
+ * strictly as real runs". Until this module the whole prohibition was carried by
8
+ * one test that scans `corpus/dev`, so a contract carrying a live-shaped token
9
+ * compiled clean and the token travelled into the sealed brief.
10
+ *
11
+ * Only the value-shaped half of that scan ships here, and the split is the point
12
+ * rather than an economy. AD-18's own reading of the rule for the header channel
13
+ * is that a declaration "names the header and its type and never carries a
14
+ * credential value", so a name is what an author is supposed to write: an
15
+ * operation declaring a `password` body key or an `Authorization` header is a
16
+ * contract for an authentication API, and a gate matching `\bpassword\b` would
17
+ * reject exactly the systems this package exists to evaluate. The patterns below
18
+ * therefore match only strings whose shape is decidable without knowing what
19
+ * field they sit in: a PEM header, a token with an issuer prefix, an address, a
20
+ * checkable account number, a card-shaped digit run. The name-shaped patterns
21
+ * stay in `tests/architecture/dev-corpus.test.ts`, where review rather than a
22
+ * compiler decides.
23
+ *
24
+ * Each pattern carries a string it must fire on, and a test asserts every one
25
+ * still does. A pattern that has rotted into a regex matching nothing is worse
26
+ * than no pattern: it reports clean forever.
27
+ */
28
+ /** AD-18's six categories, in the rule's own order. */
29
+ export const EXCLUDED_CATEGORIES = [
30
+ 'credentials',
31
+ 'tokens',
32
+ 'real names',
33
+ 'email addresses',
34
+ 'account identifiers',
35
+ 'transaction content',
36
+ ];
37
+ /**
38
+ * `real names` has no member here and never will: a personal name has no surface
39
+ * form, so every pattern that reaches one reads the field that carries it, which
40
+ * is a name-shaped test. The category stays in the tuple above because AD-18
41
+ * names six and dropping one from the vocabulary would make the gate look total.
42
+ */
43
+ export const EXCLUDED_VALUE_PATTERNS = [
44
+ {
45
+ category: 'credentials',
46
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
47
+ fires: '-----BEGIN RSA PRIVATE KEY-----',
48
+ },
49
+ {
50
+ category: 'tokens',
51
+ regex: /(?<![A-Za-z])(?:[Bb]earer\s+[\w.~+/-]{16,}|eyJ[\w-]{8,}\.[\w-]{8,}|sk-[A-Za-z0-9]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{16})/,
52
+ fires: 'Authorization: Bearer abcdefghijklmnopqrstuv',
53
+ },
54
+ {
55
+ category: 'email addresses',
56
+ regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/,
57
+ fires: '{"contact":"ada@example.com"}',
58
+ },
59
+ {
60
+ // Case-sensitive: an IBAN is upper case, and a case-insensitive form
61
+ // matches a run inside a lower-case hex digest.
62
+ category: 'account identifiers',
63
+ regex: /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b|\b\d{3}-\d{2}-\d{4}\b/,
64
+ fires: 'GB29NWBK60161331926819',
65
+ },
66
+ {
67
+ category: 'transaction content',
68
+ regex: /(?<![0-9A-Za-z])\d{4}(?:[ -]\d{4}){3}(?![0-9A-Za-z])/,
69
+ fires: 'charged 4111 1111 1111 1111',
70
+ },
71
+ ];
72
+ const step = (path, key) => typeof key === 'number' ? `${path}[${key}]` : `${path}.${key}`;
73
+ /**
74
+ * Every string anywhere inside one value, against every value-shaped pattern.
75
+ *
76
+ * Object keys are walked as paths and never scanned as content: a key is a name
77
+ * by construction, which is the same line the pattern set is drawn on. The walk
78
+ * is total over JSON and stops at nothing, so a token nested under a body
79
+ * example twelve levels down is found; the first matching category per string
80
+ * wins, since one string reported six times says nothing more than once.
81
+ *
82
+ * Order is the walk's order, which for an object is its own key order. A caller
83
+ * that needs a stable report over two spellings of the same object sorts what
84
+ * comes back; the compile gate below reads only the first hit and never the
85
+ * order.
86
+ */
87
+ export function scanExcludedContent(value, path) {
88
+ const hits = [];
89
+ const visit = (node, at) => {
90
+ if (typeof node === 'string') {
91
+ for (const { category, regex } of EXCLUDED_VALUE_PATTERNS) {
92
+ const match = regex.exec(node);
93
+ if (match === null)
94
+ continue;
95
+ hits.push({ category, path: at, match: match[0] });
96
+ return;
97
+ }
98
+ return;
99
+ }
100
+ if (Array.isArray(node)) {
101
+ node.forEach((element, index) => {
102
+ visit(element, step(at, index));
103
+ });
104
+ return;
105
+ }
106
+ if (node === null || typeof node !== 'object')
107
+ return;
108
+ for (const [key, child] of Object.entries(node))
109
+ visit(child, step(at, key));
110
+ };
111
+ visit(value, path);
112
+ return hits;
113
+ }
@@ -1,5 +1,5 @@
1
- /** AD-5's twenty-three compile-time failure codes, as data, in the table's order. */
2
- export declare const FAILURE_CODES: readonly ['missing-requirement-linkage', 'no-observable-success-criterion', 'unreachable-check-evidence', 'malformed-operator-expression', 'quantifier-over-non-collection', 'quantifier-nesting-exceeded', 'unresolved-reference-set', 'duplicate-operation-signature', 'undeclared-mandatory-input', 'oracle-missing-channel', 'direction-check-misaligned', 'unsupported-interface-kind', 'nested-temporal-clause', 'plan-exceeds-scripting-bound', 'binding-cycle', 'captured-channel-undeclared', 'rubric-scores-reasoning-prose', 'rubric-unanchored', 'rubric-evidence-unreachable', 'forbidden-input-floor-incomplete', 'scoped-reference-resolves-forbidden', 'waiver-incomplete', 'brief-exceeds-scripting-bound'];
1
+ /** AD-5's twenty-six compile-time failure codes, as data, in the table's order. */
2
+ export declare const FAILURE_CODES: readonly ['missing-requirement-linkage', 'no-observable-success-criterion', 'unreachable-check-evidence', 'malformed-operator-expression', 'quantifier-over-non-collection', 'quantifier-nesting-exceeded', 'unresolved-reference-set', 'duplicate-operation-signature', 'undeclared-mandatory-input', 'oracle-missing-channel', 'direction-check-misaligned', 'unsupported-interface-kind', 'nested-temporal-clause', 'plan-exceeds-scripting-bound', 'binding-cycle', 'captured-channel-undeclared', 'rubric-scores-reasoning-prose', 'rubric-unanchored', 'rubric-evidence-unreachable', 'forbidden-input-floor-incomplete', 'scoped-reference-resolves-forbidden', 'waiver-incomplete', 'brief-exceeds-scripting-bound', 'unresolved-artifact-reference', 'irreducible-step-reference', 'excluded-content-in-declaration'];
3
3
  export type FailureCode = (typeof FAILURE_CODES)[number];
4
4
  /**
5
5
  * AD-5's compile-time failure registry: thrown when a structural error fails
@@ -1,11 +1,11 @@
1
- /** AD-5's twenty-three compile-time failure codes, as data, in the table's order. */
1
+ /** AD-5's twenty-six compile-time failure codes, as data, in the table's order. */
2
2
  // This lives in `core/`, since it's a plain data tuple and `core/schemas/`
3
3
  // is reserved for Zod definitions. The compiler that emits these codes lives
4
4
  // in `core/compile/`, one directory over.
5
5
  //
6
6
  // `scripts/check-ad5-registry.ts` checks this tuple against AD-5's own
7
7
  // registry table under `npm run validate`; the tuple's own invariants
8
- // (twenty-three members, unique, kebab-case) are locked in
8
+ // (twenty-six members, unique, kebab-case) are locked in
9
9
  // tests/schemas/failure-codes.test.ts.
10
10
  export const FAILURE_CODES = [
11
11
  'missing-requirement-linkage',
@@ -31,6 +31,9 @@ export const FAILURE_CODES = [
31
31
  'scoped-reference-resolves-forbidden',
32
32
  'waiver-incomplete',
33
33
  'brief-exceeds-scripting-bound',
34
+ 'unresolved-artifact-reference',
35
+ 'irreducible-step-reference',
36
+ 'excluded-content-in-declaration',
34
37
  ];
35
38
  /**
36
39
  * AD-5's compile-time failure registry: thrown when a structural error fails
@@ -67,7 +67,7 @@ export type AgreementField = (typeof AGREEMENT_FIELDS)[number];
67
67
  */
68
68
  export type DuplicateRecordIdentifier = {
69
69
  readonly kind: Extract<IngestConditionKind, 'duplicate-record-identifier'>;
70
- readonly subject: 'observation' | 'finding' | 'oracle-disposition';
70
+ readonly subject: 'observation' | 'finding' | 'oracle-disposition' | 'judge-result';
71
71
  readonly identifier: string;
72
72
  readonly occurrences: number;
73
73
  };
@@ -106,6 +106,14 @@ export const ingest = (record, manifest, configuration) => {
106
106
  'oracle-disposition',
107
107
  record.oracleDispositions.map((disposition) => disposition.oracleId),
108
108
  ],
109
+ // AD-17's "one judge call scoring all named criteria", read as far as
110
+ // one record can be read: a criterion scored twice is not one call's
111
+ // product whatever the rest of the record says. Keyed by the pair,
112
+ // since two rubrics may each declare a criterion of the same name.
113
+ [
114
+ 'judge-result',
115
+ record.judgeResults.map((result) => `${result.rubricId}/${result.criterionId}`),
116
+ ],
109
117
  ]) {
110
118
  const counts = new Map();
111
119
  for (const identifier of identifiers) {
@@ -1,5 +1,6 @@
1
+ import type { ReferenceSetKeys } from '../evaluate/resolution.ts';
1
2
  import type { EvalContract } from '../schemas/eval-contract.ts';
2
- import type { Operation } from '../schemas/interface.ts';
3
+ import type { AnyOperation } from '../schemas/interface.ts';
3
4
  import type { ProbeRequest } from '../schemas/port-messages.ts';
4
5
  import type { JsonValue } from '../schemas/primitives.ts';
5
6
  import type { Probe } from '../schemas/probe.ts';
@@ -15,7 +16,7 @@ export type PlannedLeg = {
15
16
  readonly legId: string;
16
17
  readonly purpose: PlannedLegPurpose;
17
18
  readonly request: ProbeRequest;
18
- readonly operation: Operation;
19
+ readonly operation: AnyOperation;
19
20
  readonly inputs: WitnessInputs;
20
21
  };
21
22
  export type PlannedCheck = {
@@ -28,7 +29,7 @@ export type PlannedCheck = {
28
29
  readonly interfaceId: string;
29
30
  readonly operationId: string;
30
31
  readonly witness: SensitivityWitness | null;
31
- readonly operation: Operation;
32
+ readonly operation: AnyOperation;
32
33
  } | {
33
34
  readonly kind: 'state-reset';
34
35
  readonly legIds: readonly [string, string];
@@ -39,19 +40,20 @@ export type PlannedCheck = {
39
40
  readonly kind: 'seeded-faults-scoped';
40
41
  readonly defectId: string;
41
42
  readonly witness: ManifestationWitness;
42
- readonly operation: Operation;
43
+ readonly operation: AnyOperation;
43
44
  readonly cleanLegIds: readonly string[];
44
45
  } | {
45
46
  readonly kind: 'seeded-fault-fired';
46
47
  readonly defectId: string;
47
48
  readonly witness: ManifestationWitness | null;
48
- readonly operation: Operation | null;
49
+ readonly operation: AnyOperation | null;
49
50
  };
50
51
  export type PreflightPlan = {
51
52
  readonly runId: string;
52
53
  readonly legs: readonly PlannedLeg[];
53
54
  readonly checks: readonly PlannedCheck[];
54
55
  readonly referenceSets: Readonly<Record<string, JsonValue[]>>;
56
+ readonly referenceSetKeys: ReferenceSetKeys;
55
57
  };
56
58
  /**
57
59
  * AD-10's plan, derived from the interfaces the contract's probes exercise.