eval-quality 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -67
- package/corpus/dev/README.md +20 -13
- package/corpus/dev/compile-seal-example/brief.json +1 -1
- package/corpus/dev/compile-seal-example/contract.json +1 -1
- package/corpus/dev/contracts/absent-collection-locations.json +1 -1
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
- package/corpus/dev/contracts/absent-success-indicator.json +1 -1
- package/corpus/dev/contracts/empty-channel-roles.json +1 -1
- package/corpus/dev/contracts/empty-collection-locations.json +1 -1
- package/corpus/dev/contracts/empty-request-shapes.json +1 -1
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
- package/corpus/dev/contracts/fragment-selection.json +1 -0
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
- package/corpus/dev/contracts/no-operation-inventory.json +1 -1
- package/corpus/dev/contracts/no-read-back-relation.json +1 -1
- package/corpus/dev/contracts/no-state-change-marker.json +1 -1
- package/corpus/dev/contracts/no-type-violating-step.json +1 -1
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
- package/corpus/dev/contracts/review-corpus.json +1 -0
- package/corpus/dev/contracts/satisfied-declarations.json +1 -1
- package/corpus/dev/contracts/single-required-response-key.json +1 -1
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
- package/corpus/dev/index.json +1 -1
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.js +1 -0
- package/dist/application/score.d.ts +48 -0
- package/dist/application/score.js +186 -0
- package/dist/cli/arguments.d.ts +7 -3
- package/dist/cli/arguments.js +73 -5
- package/dist/cli/exit-codes.d.ts +6 -2
- package/dist/cli/exit-codes.js +8 -9
- package/dist/cli/main.js +9 -0
- package/dist/cli/render.d.ts +3 -6
- package/dist/cli/render.js +57 -5
- package/dist/cli/run.d.ts +13 -3
- package/dist/cli/run.js +136 -16
- package/dist/core/canonical/canonicalize.d.ts +12 -0
- package/dist/core/canonical/canonicalize.js +18 -4
- package/dist/core/compile/bindings.d.ts +89 -0
- package/dist/core/compile/bindings.js +334 -0
- package/dist/core/compile/compile.d.ts +17 -0
- package/dist/core/compile/compile.js +37 -1
- package/dist/core/compile/excluded-content.d.ts +11 -0
- package/dist/core/compile/excluded-content.js +42 -0
- package/dist/core/compile/expression-legality.d.ts +49 -0
- package/dist/core/compile/expression-legality.js +158 -38
- package/dist/core/compile/forbidden-inputs.d.ts +8 -1
- package/dist/core/compile/forbidden-inputs.js +16 -3
- package/dist/core/compile/interface-inventory.d.ts +57 -2
- package/dist/core/compile/interface-inventory.js +119 -13
- package/dist/core/compile/reachability.d.ts +28 -1
- package/dist/core/compile/reachability.js +167 -35
- package/dist/core/compile/schema-version.d.ts +2 -0
- package/dist/core/compile/schema-version.js +25 -0
- package/dist/core/compile/sensitivity-witness.d.ts +31 -12
- package/dist/core/compile/sensitivity-witness.js +110 -23
- package/dist/core/compile/step-reference.d.ts +2 -0
- package/dist/core/compile/step-reference.js +49 -0
- package/dist/core/coverage/operations.d.ts +62 -0
- package/dist/core/coverage/operations.js +57 -0
- package/dist/core/coverage/relevance.d.ts +4 -2
- package/dist/core/coverage/relevance.js +22 -23
- package/dist/core/coverage/satisfaction.d.ts +2 -2
- package/dist/core/coverage/satisfaction.js +73 -48
- package/dist/core/declared-inputs.d.ts +83 -4
- package/dist/core/declared-inputs.js +105 -8
- package/dist/core/emit/emit.d.ts +9 -0
- package/dist/core/emit/emit.js +214 -0
- package/dist/core/emit/private-artifact-digest.d.ts +11 -0
- package/dist/core/emit/private-artifact-digest.js +31 -0
- package/dist/core/evaluate/evidence-resolution.d.ts +10 -3
- package/dist/core/evaluate/evidence-resolution.js +74 -11
- package/dist/core/evaluate/operators.d.ts +22 -0
- package/dist/core/evaluate/operators.js +17 -1
- package/dist/core/evaluate/resolution.d.ts +14 -3
- package/dist/core/evaluate/resolution.js +63 -3
- package/dist/core/excluded-content.d.ts +65 -0
- package/dist/core/excluded-content.js +113 -0
- package/dist/core/failure-codes.d.ts +2 -2
- package/dist/core/failure-codes.js +7 -2
- package/dist/core/ingest/conditions.d.ts +201 -0
- package/dist/core/ingest/conditions.js +48 -0
- package/dist/core/ingest/index.d.ts +13 -0
- package/dist/core/ingest/index.js +11 -0
- package/dist/core/ingest/ingest.d.ts +68 -0
- package/dist/core/ingest/ingest.js +318 -0
- package/dist/core/lineage/stage-table.d.ts +21 -4
- package/dist/core/lineage/stage-table.js +35 -3
- package/dist/core/preflight/plan.d.ts +7 -5
- package/dist/core/preflight/plan.js +75 -32
- package/dist/core/preflight/projection.d.ts +6 -3
- package/dist/core/preflight/projection.js +22 -2
- package/dist/core/preflight/reduce.js +47 -7
- package/dist/core/preflight/witness-evidence.d.ts +5 -5
- package/dist/core/preflight/witness-evidence.js +73 -20
- package/dist/core/schemas/artifact.d.ts +1049 -30
- package/dist/core/schemas/constraint-ledger.js +51 -0
- package/dist/core/schemas/defect-signature.d.ts +695 -0
- package/dist/core/schemas/defect-signature.js +175 -0
- package/dist/core/schemas/eval-contract.d.ts +550 -12
- package/dist/core/schemas/eval-contract.js +32 -2
- package/dist/core/schemas/evidence-artifact.d.ts +109 -19
- package/dist/core/schemas/evidence-artifact.js +64 -8
- package/dist/core/schemas/interface.d.ts +741 -13
- package/dist/core/schemas/interface.js +120 -7
- package/dist/core/schemas/isolation-manifest.js +16 -9
- package/dist/core/schemas/plan.d.ts +329 -3
- package/dist/core/schemas/plan.js +89 -3
- package/dist/core/schemas/pointer.d.ts +47 -1
- package/dist/core/schemas/pointer.js +89 -8
- package/dist/core/schemas/port-messages.d.ts +194 -10
- package/dist/core/schemas/port-messages.js +73 -6
- package/dist/core/schemas/probe-body.d.ts +18 -0
- package/dist/core/schemas/probe-body.js +13 -0
- package/dist/core/schemas/probe-qualification.d.ts +168 -0
- package/dist/core/schemas/probe-qualification.js +106 -0
- package/dist/core/schemas/probe.d.ts +431 -0
- package/dist/core/schemas/probe.js +6 -2
- package/dist/core/schemas/scoring-policy.d.ts +1 -0
- package/dist/core/schemas/scoring-policy.js +6 -1
- package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
- package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
- package/dist/core/schemas/sealed-run-record.d.ts +161 -12
- package/dist/core/schemas/sealed-run-record.js +92 -15
- package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
- package/dist/core/schemas/sensitivity-witness.js +61 -5
- package/dist/core/score/binding-order.d.ts +31 -0
- package/dist/core/score/binding-order.js +88 -0
- package/dist/core/score/bindings.d.ts +93 -0
- package/dist/core/score/bindings.js +309 -0
- package/dist/core/score/ladder-table.d.ts +23 -0
- package/dist/core/score/ladder-table.js +109 -0
- package/dist/core/score/ladder.d.ts +229 -0
- package/dist/core/score/ladder.js +464 -0
- package/dist/core/score/mode-agreement.d.ts +30 -0
- package/dist/core/score/mode-agreement.js +16 -0
- package/dist/core/score/outcome-table.d.ts +17 -0
- package/dist/core/score/outcome-table.js +172 -0
- package/dist/core/score/outcome.d.ts +466 -0
- package/dist/core/score/outcome.js +562 -0
- package/dist/core/score/qualification.d.ts +83 -0
- package/dist/core/score/qualification.js +645 -0
- package/dist/core/score/quotation.d.ts +56 -0
- package/dist/core/score/quotation.js +226 -0
- package/dist/core/score/reduce-trials.d.ts +80 -0
- package/dist/core/score/reduce-trials.js +90 -0
- package/dist/core/score/score.d.ts +72 -0
- package/dist/core/score/score.js +607 -0
- package/dist/core/score/selection.d.ts +80 -0
- package/dist/core/score/selection.js +98 -0
- package/dist/core/score/strength.d.ts +58 -0
- package/dist/core/score/strength.js +227 -0
- package/dist/core/score/witness.d.ts +138 -0
- package/dist/core/score/witness.js +320 -0
- package/dist/core/seal/derived-reference.d.ts +2 -9
- package/dist/core/seal/derived-reference.js +293 -46
- package/dist/core/seal/plan-index.d.ts +18 -3
- package/dist/core/seal/plan-index.js +44 -8
- package/dist/core/seal/seal.js +8 -3
- package/dist/core/stage-contracts.d.ts +62 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ports/environment-probe-port.d.ts +60 -4
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/probe-conformance.d.ts +29 -0
- package/dist/testing/probe-conformance.js +34 -5
- package/package.json +12 -3
- package/schemas/eval-contract.schema.json +1787 -797
- package/schemas/evidence-artifact.schema.json +126 -9
- package/schemas/isolation-manifest.schema.json +17 -10
- package/schemas/probe.schema.json +636 -5
- package/schemas/rubric.schema.json +1 -1
- package/schemas/scoring-policy.schema.json +8 -1
- package/schemas/sealed-evaluator-brief.schema.json +11 -1
- package/schemas/sealed-run-record.schema.json +314 -40
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Observation, OracleDisposition, RunModeValue, SealedRunRecord } from '../schemas/sealed-run-record.ts';
|
|
2
|
+
import type { EvaluatorRecommendation } from '../schemas/verdict.ts';
|
|
3
|
+
import { type UnwitnessedQuotation } from '../score/quotation.ts';
|
|
4
|
+
import type { IngestStage } from '../stage-contracts.ts';
|
|
5
|
+
import { type IngestCondition } from './conditions.ts';
|
|
6
|
+
/**
|
|
7
|
+
* `validated-observations`, the internal product AD-24 exempts from
|
|
8
|
+
* publication: a TypeScript type with no Zod schema, no registry entry, and no
|
|
9
|
+
* `schemaVersion`.
|
|
10
|
+
*
|
|
11
|
+
* `unwitnessedQuotations` and `isolationViolation` are derived here rather than
|
|
12
|
+
* left for the next stage to rebuild out of condition entries, because both are
|
|
13
|
+
* shapes the ladder already declares.
|
|
14
|
+
*
|
|
15
|
+
* The three carried arrays are copies and their elements are not: an
|
|
16
|
+
* observation, a finding, or a disposition on this product is the same object
|
|
17
|
+
* the record holds. The stage is pure and mutates nothing, so a caller that also
|
|
18
|
+
* does not mutate the record it passed in sees a stable product; deep-copying
|
|
19
|
+
* every observation to defend against a caller mutating its own input is a cost
|
|
20
|
+
* no consumer in this package asks for. `isolationViolation` is a list where
|
|
21
|
+
* `EvidenceIntegrityInputs` still declares one nullable string: the ladder's own
|
|
22
|
+
* row type says a multi-entry `reasons` "names each affected oracle, gap, or
|
|
23
|
+
* finding separately, matching AD-21's 'the record carries every condition that
|
|
24
|
+
* fired'", so the single-valued field is the shape that is wrong. Collapsing to
|
|
25
|
+
* it here would mean inventing a separator and an ordering rule that the field's
|
|
26
|
+
* widening then deletes.
|
|
27
|
+
*/
|
|
28
|
+
export type ValidatedObservations = {
|
|
29
|
+
/** `record.runId`, restated for the same reason `mode` is: `score.ts` reads it off the first trial to build `emit`'s own `runId` field, and no later stage may re-derive or default it. */
|
|
30
|
+
readonly runId: string;
|
|
31
|
+
readonly mode: RunModeValue;
|
|
32
|
+
/**
|
|
33
|
+
* `AssessmentCommon.evaluatorRecommendation` (`score/ladder.ts:107`) is
|
|
34
|
+
* required and two shared ladder rows read it directly; nothing before
|
|
35
|
+
* this field supplied it to anything score-shaped. Read off
|
|
36
|
+
* `record.evaluatorRecommendation` the same way `mode` already is above:
|
|
37
|
+
* restated, never derived, recomputed, or defaulted.
|
|
38
|
+
*/
|
|
39
|
+
readonly evaluatorRecommendation: EvaluatorRecommendation;
|
|
40
|
+
/** ascending `sequence`, then `observationId`, so the record's array order is never what a consumer reads. */
|
|
41
|
+
readonly observations: readonly Observation[];
|
|
42
|
+
/** ascending `findingId`; entries sharing one keep their presented order, which is all the record supplies. */
|
|
43
|
+
readonly findings: readonly SealedRunRecord['findings'][number][];
|
|
44
|
+
/** ascending `oracleId`, on the same terms as `findings`. */
|
|
45
|
+
readonly dispositions: readonly OracleDisposition[];
|
|
46
|
+
readonly conditions: readonly IngestCondition[];
|
|
47
|
+
readonly unwitnessedQuotations: readonly UnwitnessedQuotation[];
|
|
48
|
+
/** empty when nothing fired; one entry per offending value, never a joined summary. */
|
|
49
|
+
readonly isolationViolation: readonly string[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The stage. Reads nothing outside its three declared inputs: a rule needing
|
|
53
|
+
* the eval contract, the rubric, or bytes resolved through the corpus port is
|
|
54
|
+
* routed to whoever declares them rather than absorbed here on the strength of
|
|
55
|
+
* being nearby.
|
|
56
|
+
*
|
|
57
|
+
* Two rules read the evaluator configuration and they are separate. AD-32's
|
|
58
|
+
* agreement rule compares the digest the record declares against the one the
|
|
59
|
+
* manifest declares, which is `cross-artifact-disagreement` and never reads the
|
|
60
|
+
* artifact. AD-24 and AD-11 require the digest to be recomputed from the
|
|
61
|
+
* artifact, which is the only one of the three that is not caller-attested and
|
|
62
|
+
* is why the artifact is a declared input at all.
|
|
63
|
+
*
|
|
64
|
+
* AD-24's "absent or incomplete" splits the same way the manifest's does:
|
|
65
|
+
* incomplete is a schema rejection the application boundary already raises, and
|
|
66
|
+
* absent is a shape no schema can describe, so the parameter admits `null`.
|
|
67
|
+
*/
|
|
68
|
+
export declare const ingest: IngestStage<ValidatedObservations>;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-24's ingest stage: three already-parsed artifacts in, the validated
|
|
3
|
+
* observations plus every condition detected out.
|
|
4
|
+
*
|
|
5
|
+
* The organizing rule is the isolation manifest's own `.meta` sentence, "a
|
|
6
|
+
* schema rejection is the correct expression of unparseable and incomplete, so
|
|
7
|
+
* nothing is admitted for their sake, and the violating case is
|
|
8
|
+
* `core/ingest`'s". Bytes that will not parse are the application layer's to
|
|
9
|
+
* reject; bytes that parse and then contradict each other are a finding about
|
|
10
|
+
* the run. So a detected problem is returned as data and nothing here throws.
|
|
11
|
+
*
|
|
12
|
+
* One fault does propagate and is named, from two call sites into the same
|
|
13
|
+
* module. `auditQuotation` canonicalizes the three structured evidence
|
|
14
|
+
* channels, and the evaluator configuration's digest is recomputed by
|
|
15
|
+
* canonicalizing the whole artifact, so an unsafe integer or a lone surrogate in
|
|
16
|
+
* either raises AD-28's `non-canonicalizable-value` out of `core/canonical`. It
|
|
17
|
+
* is neither caught nor re-raised here, which is what lets a caller tell an
|
|
18
|
+
* invalid run from a crashed one.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here re-derives what `resolveOutcome` already decides. Its ten
|
|
21
|
+
* invalidating conditions are per oracle and reach the ladder through
|
|
22
|
+
* `outcome.resolution.invalidatingConditions`; these eleven are per record and
|
|
23
|
+
* reach it through `EvidenceIntegrityInputs` and `OutcomeStateInputs`. Two
|
|
24
|
+
* pairs are related rather than identical: `judge-result-unscored` is the
|
|
25
|
+
* derivation `OutcomeInputs.judgeConduct` has never had, and a
|
|
26
|
+
* `dangling-citation` and an `unwitnessed-detection-claim` can both fire on one
|
|
27
|
+
* finding, in which case the basis names both and the rung is the same either
|
|
28
|
+
* way.
|
|
29
|
+
*/
|
|
30
|
+
import { digestArtifact } from '../canonical/digest.js';
|
|
31
|
+
import { FORBIDDEN_INPUT_FLOOR } from '../schemas/eval-contract.js';
|
|
32
|
+
import { auditQuotation, } from '../score/quotation.js';
|
|
33
|
+
import { AGREEMENT_FIELDS } from './conditions.js';
|
|
34
|
+
/**
|
|
35
|
+
* Observed values with no entry in their allowlist, in the manifest's own array
|
|
36
|
+
* order and each named once. A value observed twice outside its allowlist is one
|
|
37
|
+
* value outside its allowlist; the payload is what a consumer renders, and
|
|
38
|
+
* `isolationViolation` is specified as one entry per offending value.
|
|
39
|
+
*/
|
|
40
|
+
const outsideAllowlist = (observed, allowed) => {
|
|
41
|
+
const permitted = new Set(allowed);
|
|
42
|
+
return [...new Set(observed)].filter((value) => !permitted.has(value));
|
|
43
|
+
};
|
|
44
|
+
/** The path AD-27 digests the evaluator configuration under, matching `seal.ts`'s treatment of the contract. */
|
|
45
|
+
const EVALUATOR_CONFIGURATION_ARTIFACT_PATH = 'EvaluatorConfiguration';
|
|
46
|
+
/**
|
|
47
|
+
* A total order over the two condition families whose source array declares no
|
|
48
|
+
* order of its own. A finding carries no sequence field and a judge result is
|
|
49
|
+
* addressed by its `(rubricId, criterionId)` pair, so reading either in array
|
|
50
|
+
* position would make the recorded conditions depend on a position NFR9 forbids
|
|
51
|
+
* reading; `auditQuotation` sorts its own result for the same reason.
|
|
52
|
+
*
|
|
53
|
+
* Keyed on the whole payload rather than on the identifier alone. `observations`
|
|
54
|
+
* is the only one of the three arrays the record schema refines for uniqueness,
|
|
55
|
+
* so a repeated `findingId` and a repeated `(rubricId, criterionId)` are both
|
|
56
|
+
* representable, and a comparator keyed on the identifier would leave the tied
|
|
57
|
+
* pair ordered by the array position the sort exists to stop reading. Entries
|
|
58
|
+
* that still tie here are equal values, so the order between them cannot be
|
|
59
|
+
* observed.
|
|
60
|
+
*
|
|
61
|
+
* Joined on U+0000 rather than compared position by position. Every identifier
|
|
62
|
+
* in a key is drawn from `IDENTIFIER_SOURCE` or a `X-digits` prefix form, whose
|
|
63
|
+
* lowest code point is U+002D, so the separator sorts below every character a
|
|
64
|
+
* segment can carry and the joined comparison is the element-wise one.
|
|
65
|
+
*/
|
|
66
|
+
const compareKeys = (left, right) => {
|
|
67
|
+
const a = left.join('\u0000');
|
|
68
|
+
const b = right.join('\u0000');
|
|
69
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The stage. Reads nothing outside its three declared inputs: a rule needing
|
|
73
|
+
* the eval contract, the rubric, or bytes resolved through the corpus port is
|
|
74
|
+
* routed to whoever declares them rather than absorbed here on the strength of
|
|
75
|
+
* being nearby.
|
|
76
|
+
*
|
|
77
|
+
* Two rules read the evaluator configuration and they are separate. AD-32's
|
|
78
|
+
* agreement rule compares the digest the record declares against the one the
|
|
79
|
+
* manifest declares, which is `cross-artifact-disagreement` and never reads the
|
|
80
|
+
* artifact. AD-24 and AD-11 require the digest to be recomputed from the
|
|
81
|
+
* artifact, which is the only one of the three that is not caller-attested and
|
|
82
|
+
* is why the artifact is a declared input at all.
|
|
83
|
+
*
|
|
84
|
+
* AD-24's "absent or incomplete" splits the same way the manifest's does:
|
|
85
|
+
* incomplete is a schema rejection the application boundary already raises, and
|
|
86
|
+
* absent is a shape no schema can describe, so the parameter admits `null`.
|
|
87
|
+
*/
|
|
88
|
+
export const ingest = (record, manifest, configuration) => {
|
|
89
|
+
const conditions = [];
|
|
90
|
+
// Prior to every other check: a record that uses one identifier twice cannot
|
|
91
|
+
// address its own entries, and every consumer downstream addresses by them.
|
|
92
|
+
// Reported before the conditions that name one, so a reader meets the
|
|
93
|
+
// ambiguity before a condition that relies on it.
|
|
94
|
+
//
|
|
95
|
+
// Observations lead because a repeat there changes an answer rather than an
|
|
96
|
+
// order: `auditQuotation` indexes them into a `Map` keyed on `observationId`,
|
|
97
|
+
// so the last entry with a repeated identifier wins and decides which body a
|
|
98
|
+
// quotation is checked against.
|
|
99
|
+
for (const [subject, identifiers] of [
|
|
100
|
+
[
|
|
101
|
+
'observation',
|
|
102
|
+
record.observations.map((observation) => observation.observationId),
|
|
103
|
+
],
|
|
104
|
+
['finding', record.findings.map((finding) => finding.findingId)],
|
|
105
|
+
[
|
|
106
|
+
'oracle-disposition',
|
|
107
|
+
record.oracleDispositions.map((disposition) => disposition.oracleId),
|
|
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
|
+
],
|
|
117
|
+
]) {
|
|
118
|
+
const counts = new Map();
|
|
119
|
+
for (const identifier of identifiers) {
|
|
120
|
+
counts.set(identifier, (counts.get(identifier) ?? 0) + 1);
|
|
121
|
+
}
|
|
122
|
+
const repeated = [...counts.entries()]
|
|
123
|
+
.filter(([, occurrences]) => occurrences > 1)
|
|
124
|
+
.sort((a, b) => compareKeys([a[0]], [b[0]]));
|
|
125
|
+
for (const [identifier, occurrences] of repeated) {
|
|
126
|
+
conditions.push({
|
|
127
|
+
kind: 'duplicate-record-identifier',
|
|
128
|
+
subject,
|
|
129
|
+
identifier,
|
|
130
|
+
occurrences,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// A cited identifier matching a declared observation is the cross-artifact
|
|
135
|
+
// rule `observationIds` leaves to this stage. Declared on every finding
|
|
136
|
+
// branch, so the check is too: AD-23's word is "additionally", a floor on
|
|
137
|
+
// `defect` rather than a prohibition on the other two.
|
|
138
|
+
const declared = new Set(record.observations.map((observation) => observation.observationId));
|
|
139
|
+
const dangling = record.findings
|
|
140
|
+
.map((finding) => ({
|
|
141
|
+
findingId: finding.findingId,
|
|
142
|
+
// Deduplicated first, in first-citation order: the condition names
|
|
143
|
+
// which identifiers resolved to nothing, and a finding citing one
|
|
144
|
+
// twice has one bad citation to report rather than two.
|
|
145
|
+
unresolvedObservationIds: [...new Set(finding.observationIds)].filter((observationId) => !declared.has(observationId)),
|
|
146
|
+
}))
|
|
147
|
+
.filter((entry) => entry.unresolvedObservationIds.length > 0)
|
|
148
|
+
.sort((a, b) => compareKeys([a.findingId, ...a.unresolvedObservationIds], [b.findingId, ...b.unresolvedObservationIds]));
|
|
149
|
+
for (const entry of dangling) {
|
|
150
|
+
conditions.push({ kind: 'dangling-citation', ...entry });
|
|
151
|
+
}
|
|
152
|
+
// The same rule over the record's second citation site. `resolveOutcome`
|
|
153
|
+
// reads a disposition's `observationIds` only for emptiness, so a `violated`
|
|
154
|
+
// disposition whose one citation names nothing would otherwise be believed,
|
|
155
|
+
// which is the shape AD-33 says invalidates rather than being believed.
|
|
156
|
+
const danglingDispositions = record.oracleDispositions
|
|
157
|
+
.map((disposition) => ({
|
|
158
|
+
oracleId: disposition.oracleId,
|
|
159
|
+
unresolvedObservationIds: [...new Set(disposition.observationIds)].filter((observationId) => !declared.has(observationId)),
|
|
160
|
+
}))
|
|
161
|
+
.filter((entry) => entry.unresolvedObservationIds.length > 0)
|
|
162
|
+
.sort((a, b) => compareKeys([a.oracleId, ...a.unresolvedObservationIds], [b.oracleId, ...b.unresolvedObservationIds]));
|
|
163
|
+
for (const entry of danglingDispositions) {
|
|
164
|
+
conditions.push({ kind: 'dangling-disposition-citation', ...entry });
|
|
165
|
+
}
|
|
166
|
+
// ADR-009 Decision 2's precedence: cited identifiers govern the witness
|
|
167
|
+
// match and quotation audits it. This is that audit's caller.
|
|
168
|
+
const unwitnessedQuotations = auditQuotation(record);
|
|
169
|
+
if (unwitnessedQuotations.length > 0) {
|
|
170
|
+
conditions.push({
|
|
171
|
+
kind: 'unwitnessed-quotation',
|
|
172
|
+
quotations: unwitnessedQuotations,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const isolationViolation = [];
|
|
176
|
+
if (manifest === null) {
|
|
177
|
+
// AD-16's absent case. The agreement and forbidden-input checks read
|
|
178
|
+
// manifest fields and cannot run; every record-internal check still does,
|
|
179
|
+
// so a run that is both manifest-less and internally inconsistent reports
|
|
180
|
+
// both rather than only the first.
|
|
181
|
+
conditions.push({ kind: 'isolation-manifest-absent' });
|
|
182
|
+
isolationViolation.push('isolation manifest absent');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const mounts = outsideAllowlist(manifest.observedMounts, manifest.allowedMounts);
|
|
186
|
+
const networkTargets = outsideAllowlist(manifest.observedNetworkTargets, manifest.networkAllowlist);
|
|
187
|
+
const toolCalls = outsideAllowlist(manifest.observedToolCalls, manifest.toolAllowlist);
|
|
188
|
+
// One count over the three allowlists. The condition is "some observed
|
|
189
|
+
// value sits outside its allowlist", and asking it as three
|
|
190
|
+
// short-circuiting disjuncts would leave two of them evaluated in one
|
|
191
|
+
// direction only.
|
|
192
|
+
const exceeded = mounts.length + networkTargets.length + toolCalls.length;
|
|
193
|
+
if (manifest.violation !== null || exceeded > 0) {
|
|
194
|
+
conditions.push({
|
|
195
|
+
kind: 'isolation-manifest-violation',
|
|
196
|
+
violation: manifest.violation,
|
|
197
|
+
mountsOutsideAllowlist: mounts,
|
|
198
|
+
networkTargetsOutsideAllowlist: networkTargets,
|
|
199
|
+
toolCallsOutsideAllowlist: toolCalls,
|
|
200
|
+
});
|
|
201
|
+
// The declared violation first, then mounts, network targets, and tool
|
|
202
|
+
// calls, each in the manifest's own array order and one entry per
|
|
203
|
+
// offending value.
|
|
204
|
+
if (manifest.violation !== null) {
|
|
205
|
+
isolationViolation.push(`manifest violation: ${manifest.violation}`);
|
|
206
|
+
}
|
|
207
|
+
for (const value of mounts) {
|
|
208
|
+
isolationViolation.push(`mount outside allowlist: ${value}`);
|
|
209
|
+
}
|
|
210
|
+
for (const value of networkTargets) {
|
|
211
|
+
isolationViolation.push(`network target outside allowlist: ${value}`);
|
|
212
|
+
}
|
|
213
|
+
for (const value of toolCalls) {
|
|
214
|
+
isolationViolation.push(`tool call outside allowlist: ${value}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// AD-16's "account for each forbidden input by name". Read off the floor
|
|
218
|
+
// rather than off the manifest's own key order, so the seven are named in
|
|
219
|
+
// the order the list that generates them declares.
|
|
220
|
+
const admitted = FORBIDDEN_INPUT_FLOOR.filter((input) => !manifest.forbiddenInputAccounting[input].withheld);
|
|
221
|
+
if (admitted.length > 0) {
|
|
222
|
+
conditions.push({
|
|
223
|
+
kind: 'forbidden-input-not-withheld',
|
|
224
|
+
inputs: admitted,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// AD-32's agreement rule. Both artifacts carry all three fields, so the
|
|
228
|
+
// comparison is between them and never against the configuration artifact
|
|
229
|
+
// standing behind the digest.
|
|
230
|
+
for (const field of AGREEMENT_FIELDS) {
|
|
231
|
+
if (record[field] === manifest[field])
|
|
232
|
+
continue;
|
|
233
|
+
conditions.push({
|
|
234
|
+
kind: 'cross-artifact-disagreement',
|
|
235
|
+
field,
|
|
236
|
+
recordValue: record[field],
|
|
237
|
+
manifestValue: manifest[field],
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// AD-24: "ingest computes its digest from the artifact and invalidates the
|
|
242
|
+
// run when it is absent or incomplete", restated by AD-11 as the one
|
|
243
|
+
// scoring-version input this stage recomputes. The record's declaration is
|
|
244
|
+
// the operand because AD-32 puts the trust boundary there: a caller that
|
|
245
|
+
// declares one configuration and runs another leaves the two declarations
|
|
246
|
+
// agreeing and only the recomputation disagreeing.
|
|
247
|
+
if (configuration === null) {
|
|
248
|
+
conditions.push({ kind: 'evaluator-configuration-absent' });
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const computedDigest = digestArtifact(configuration, EVALUATOR_CONFIGURATION_ARTIFACT_PATH);
|
|
252
|
+
if (computedDigest !== record.evaluatorConfigurationDigest) {
|
|
253
|
+
conditions.push({
|
|
254
|
+
kind: 'evaluator-configuration-digest-mismatch',
|
|
255
|
+
declaredDigest: record.evaluatorConfigurationDigest,
|
|
256
|
+
computedDigest,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// AD-17's record-decidable half. `null` is the shape AD-6's `judge-error`
|
|
261
|
+
// fires on, which is why the schema must parse it and why this is a condition
|
|
262
|
+
// rather than a parse failure.
|
|
263
|
+
//
|
|
264
|
+
// Deduplicated by the pair that addresses the result: `judgeResults` carries
|
|
265
|
+
// no uniqueness refinement, so one criterion may appear twice, and two
|
|
266
|
+
// byte-identical conditions naming one criterion are one finding a basis line
|
|
267
|
+
// can act on rather than two.
|
|
268
|
+
//
|
|
269
|
+
// The two citation families deduplicate identifiers inside an entry and never
|
|
270
|
+
// the entries themselves, which is the opposite answer to the same shape and
|
|
271
|
+
// is deliberate. A repeated `(rubricId, criterionId)` is one criterion scored
|
|
272
|
+
// twice, so the second entry names nothing new. Two findings sharing a
|
|
273
|
+
// `findingId` are two findings the record cannot address apart, and their
|
|
274
|
+
// citations may differ, so collapsing them would drop a bad citation on the
|
|
275
|
+
// floor.
|
|
276
|
+
const unscored = [
|
|
277
|
+
...new Map(record.judgeResults
|
|
278
|
+
.filter((result) => result.score === null)
|
|
279
|
+
.map((result) => [
|
|
280
|
+
`${result.rubricId}/${result.criterionId}`,
|
|
281
|
+
{ rubricId: result.rubricId, criterionId: result.criterionId },
|
|
282
|
+
])).values(),
|
|
283
|
+
].sort((a, b) => compareKeys([a.rubricId, a.criterionId], [b.rubricId, b.criterionId]));
|
|
284
|
+
for (const entry of unscored) {
|
|
285
|
+
conditions.push({ kind: 'judge-result-unscored', ...entry });
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
// Read off the record and restated, never derived, recomputed, or
|
|
289
|
+
// defaulted, the same posture `mode` below takes.
|
|
290
|
+
runId: record.runId,
|
|
291
|
+
// Read off the record and restated, never derived, recomputed, or
|
|
292
|
+
// defaulted: AD-21 fixes mode before ingest and there is no fourth
|
|
293
|
+
// parameter for a caller to disagree with it through.
|
|
294
|
+
mode: record.mode,
|
|
295
|
+
evaluatorRecommendation: record.evaluatorRecommendation,
|
|
296
|
+
// `selectObservations`' own sort, matched exactly so two readers of one
|
|
297
|
+
// record cannot disagree about which observation came first. The
|
|
298
|
+
// identifier tie-break cannot fire on a record that parsed, since
|
|
299
|
+
// `sequence` is refined unique per record; it is kept for comparator
|
|
300
|
+
// identity with the shipped sort rather than because the case is real.
|
|
301
|
+
observations: [...record.observations].sort((a, b) => a.sequence - b.sequence || (a.observationId < b.observationId ? -1 : 1)),
|
|
302
|
+
// Sorted for the same reason the conditions are: neither array declares an
|
|
303
|
+
// order, so carrying the record's would make the product depend on a
|
|
304
|
+
// position nothing may read. The identifier is a total key on any record
|
|
305
|
+
// that does not repeat one, and a record that does repeats it into a
|
|
306
|
+
// `duplicate-record-identifier` condition, so the one input whose product
|
|
307
|
+
// still varies with presentation order is one the run already reports as
|
|
308
|
+
// unaddressable. Ordering that pair by content instead would mean
|
|
309
|
+
// canonicalizing every finding, which buys determinism for a record
|
|
310
|
+
// already flagged at the cost of making `non-canonicalizable-value`
|
|
311
|
+
// reachable from a field no quotation cites.
|
|
312
|
+
findings: [...record.findings].sort((a, b) => compareKeys([a.findingId], [b.findingId])),
|
|
313
|
+
dispositions: [...record.oracleDispositions].sort((a, b) => compareKeys([a.oracleId], [b.oracleId])),
|
|
314
|
+
conditions,
|
|
315
|
+
unwitnessedQuotations,
|
|
316
|
+
isolationViolation,
|
|
317
|
+
};
|
|
318
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AD-24's stage-signature table and AD-29's producer map, as data: six stages
|
|
3
|
-
* with their inputs,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* type-only to keep zod off that
|
|
3
|
+
* with their artifact inputs, the non-artifact values they receive, their one
|
|
4
|
+
* owned output, and the lineage edge each writes; twelve interchange artifacts
|
|
5
|
+
* with one producer apiece. The `check:lineage` scanner derives its allowlist
|
|
6
|
+
* from `module`, so the registry import stays type-only to keep zod off that
|
|
7
|
+
* gate's load path.
|
|
7
8
|
*/
|
|
8
9
|
import type { InterchangeArtifactKey } from '../schemas/artifact.ts';
|
|
9
10
|
/** AD-24's six stages, in the order the Consistency Conventions list them. */
|
|
@@ -16,6 +17,20 @@ export type PipelineStage = (typeof PIPELINE_STAGES)[number];
|
|
|
16
17
|
*/
|
|
17
18
|
export declare const INTERNAL_PRODUCTS: readonly ['probe-plan', 'probe-observations', 'validated-observations', 'scored-outcomes-and-verdict'];
|
|
18
19
|
export type InternalProduct = (typeof INTERNAL_PRODUCTS)[number];
|
|
20
|
+
/**
|
|
21
|
+
* Values a stage reads at its boundary that the `inputs` column does not name.
|
|
22
|
+
* AD-24 writes the table in artifacts, and owed item 6 records the source of
|
|
23
|
+
* run mode as absent from it: a row naming `sealed-run-record` says which bytes
|
|
24
|
+
* arrive and leaves open which value fixes the run's mode. Closed rather than
|
|
25
|
+
* free text, so the column cannot become a notes field.
|
|
26
|
+
*
|
|
27
|
+
* The three digests join `mode` for the same reason: they are exactly what
|
|
28
|
+
* this column exists for, "values a stage reads at its boundary that the
|
|
29
|
+
* inputs column does not name" -- `emit`'s three caller-attested AD-11
|
|
30
|
+
* digests have no artifact source anywhere in this pipeline.
|
|
31
|
+
*/
|
|
32
|
+
export declare const STAGE_VALUE_INPUTS: readonly ['mode', 'corpusDigest', 'fixtureDigest', 'evaluatorConfigurationDigest'];
|
|
33
|
+
export type StageValueInput = (typeof STAGE_VALUE_INPUTS)[number];
|
|
19
34
|
/**
|
|
20
35
|
* What a stage does to the two AD-29 fields on the artifact it owns. `mints`
|
|
21
36
|
* covers a root and a revision alike, since both write the fields.
|
|
@@ -25,6 +40,8 @@ export type LineageEdge = 'mints' | 'carries-through' | 'none';
|
|
|
25
40
|
export type ArtifactProducer = PipelineStage | 'caller' | 'embedded';
|
|
26
41
|
export type StageSignature = {
|
|
27
42
|
readonly inputs: readonly (InterchangeArtifactKey | InternalProduct)[];
|
|
43
|
+
/** non-artifact values the stage receives; see STAGE_VALUE_INPUTS. */
|
|
44
|
+
readonly valueInputs: readonly StageValueInput[];
|
|
28
45
|
readonly owns: InterchangeArtifactKey | InternalProduct;
|
|
29
46
|
/** the owned output's registry key; null when the output is internal. */
|
|
30
47
|
readonly ownsInterchange: InterchangeArtifactKey | null;
|
|
@@ -18,9 +18,28 @@ export const INTERNAL_PRODUCTS = [
|
|
|
18
18
|
'validated-observations',
|
|
19
19
|
'scored-outcomes-and-verdict',
|
|
20
20
|
];
|
|
21
|
+
/**
|
|
22
|
+
* Values a stage reads at its boundary that the `inputs` column does not name.
|
|
23
|
+
* AD-24 writes the table in artifacts, and owed item 6 records the source of
|
|
24
|
+
* run mode as absent from it: a row naming `sealed-run-record` says which bytes
|
|
25
|
+
* arrive and leaves open which value fixes the run's mode. Closed rather than
|
|
26
|
+
* free text, so the column cannot become a notes field.
|
|
27
|
+
*
|
|
28
|
+
* The three digests join `mode` for the same reason: they are exactly what
|
|
29
|
+
* this column exists for, "values a stage reads at its boundary that the
|
|
30
|
+
* inputs column does not name" -- `emit`'s three caller-attested AD-11
|
|
31
|
+
* digests have no artifact source anywhere in this pipeline.
|
|
32
|
+
*/
|
|
33
|
+
export const STAGE_VALUE_INPUTS = [
|
|
34
|
+
'mode',
|
|
35
|
+
'corpusDigest',
|
|
36
|
+
'fixtureDigest',
|
|
37
|
+
'evaluatorConfigurationDigest',
|
|
38
|
+
];
|
|
21
39
|
export const STAGE_SIGNATURES = {
|
|
22
40
|
compile: {
|
|
23
41
|
inputs: ['eval-contract'],
|
|
42
|
+
valueInputs: [],
|
|
24
43
|
owns: 'eval-contract',
|
|
25
44
|
ownsInterchange: 'eval-contract',
|
|
26
45
|
// The caller authors the contract and its lineage; `compile` validates
|
|
@@ -31,6 +50,7 @@ export const STAGE_SIGNATURES = {
|
|
|
31
50
|
},
|
|
32
51
|
seal: {
|
|
33
52
|
inputs: ['eval-contract'],
|
|
53
|
+
valueInputs: [],
|
|
34
54
|
owns: 'sealed-evaluator-brief',
|
|
35
55
|
ownsInterchange: 'sealed-evaluator-brief',
|
|
36
56
|
lineage: 'mints',
|
|
@@ -42,15 +62,21 @@ export const STAGE_SIGNATURES = {
|
|
|
42
62
|
'isolation-manifest',
|
|
43
63
|
'evaluator-configuration',
|
|
44
64
|
],
|
|
65
|
+
// Mode arrives on the sealed run record and is named here because owed
|
|
66
|
+
// item 6 asks the table for the source of run mode. `ingest` is the last
|
|
67
|
+
// stage that may read it from the caller and the first that may reject
|
|
68
|
+
// its absence; no later stage derives, recomputes, or defaults it.
|
|
69
|
+
valueInputs: ['mode'],
|
|
45
70
|
owns: 'validated-observations',
|
|
46
71
|
ownsInterchange: null,
|
|
47
72
|
lineage: 'none',
|
|
48
|
-
module:
|
|
73
|
+
module: 'src/core/ingest/ingest.ts',
|
|
49
74
|
},
|
|
50
75
|
preflight: {
|
|
51
76
|
// AD-34 splits the stage into `plan` and `reduce`; both halves' inputs
|
|
52
77
|
// are the stage's inputs, and only `reduce` returns an artifact.
|
|
53
78
|
inputs: ['eval-contract', 'probe', 'probe-plan', 'probe-observations'],
|
|
79
|
+
valueInputs: [],
|
|
54
80
|
owns: 'preflight-verdict',
|
|
55
81
|
ownsInterchange: 'preflight-verdict',
|
|
56
82
|
lineage: 'mints',
|
|
@@ -64,19 +90,25 @@ export const STAGE_SIGNATURES = {
|
|
|
64
90
|
'preflight-verdict',
|
|
65
91
|
'scoring-policy',
|
|
66
92
|
],
|
|
93
|
+
valueInputs: [],
|
|
67
94
|
// AD-24: "score produces the outcome and verdict values emit
|
|
68
95
|
// serializes". Owed item 6 records that type as unnamed; this names it.
|
|
69
96
|
owns: 'scored-outcomes-and-verdict',
|
|
70
97
|
ownsInterchange: null,
|
|
71
98
|
lineage: 'none',
|
|
72
|
-
module:
|
|
99
|
+
module: 'src/core/score/score.ts',
|
|
73
100
|
},
|
|
74
101
|
emit: {
|
|
75
102
|
inputs: ['scored-outcomes-and-verdict'],
|
|
103
|
+
valueInputs: [
|
|
104
|
+
'corpusDigest',
|
|
105
|
+
'fixtureDigest',
|
|
106
|
+
'evaluatorConfigurationDigest',
|
|
107
|
+
],
|
|
76
108
|
owns: 'evidence-artifact',
|
|
77
109
|
ownsInterchange: 'evidence-artifact',
|
|
78
110
|
lineage: 'mints',
|
|
79
|
-
module:
|
|
111
|
+
module: 'src/core/emit/emit.ts',
|
|
80
112
|
},
|
|
81
113
|
};
|
|
82
114
|
export const ARTIFACT_PRODUCERS = {
|
|
@@ -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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|