@agentsfleet/orly 0.12.1 → 0.13.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 CHANGED
@@ -42,25 +42,30 @@ Works with Claude Code, Codex, OpenCode, and Amp.
42
42
 
43
43
  ---
44
44
 
45
- ## 0.12: migration verification
45
+ ## 0.13: measured judgment comparison
46
46
 
47
- 0.12.0 shipped migration verification and setup guidance. The `.orly/` layout
48
- and six existing `orly judge` questions originally shipped in 0.11.0.
49
- This release focuses on package verification, migration evidence and clearer setup guidance; it adds no new runtime judgment question.
47
+ The 0.13.0 source candidate adds a deterministic examiner for unsupported completion claims.
48
+ It freezes the six runtime questions and evaluates five proposed checks separately.
49
+ Those checks cover obligations, evidence, production wiring, failure tests and finding resolution.
50
50
 
51
- The `agentsfleet` trial upgraded a dedicated worktree from 0.10.14 using the packed candidate.
52
- Owner files, hooks and verification commands survived; a second update wrote nothing.
53
- See the [release report](evals/release/release-report.md) for exact proof and remaining release checks.
51
+ From a source checkout, run `bun evals/judgments/comparison/run.ts --check` to validate the frozen inputs.
52
+ Expected output is JSON with six baseline questions, five candidates and `passed: true`.
53
+ Run `bun evals/judgments/comparison/run.ts --offline` for whole-task exercises with hidden behavioral checks.
54
+ A successful run reports `passed: true`; seeded false completions remain failed task outcomes.
54
55
 
55
- Jev, TypeSafe's bounded judgment model, supplies advice only when requested.
56
- Existing deterministic checks retain control of gate results.
57
- The comparison evaluator is parked for proposed 0.13 work; model accuracy and candidate benefits remain unmeasured.
58
- See the [judgment reference](docs/JUDGMENTS.md) for evidence selection, offline replay and explicit source upload.
56
+ A separate explicit `jev-run.ts --live` command measures TypeSafe Jev answers against the frozen expectations.
57
+ Its retained report lists every measured answer, grading match, disagreement and unavailable response.
58
+ Replay reproduces every result with zero new requests. Autonomous agent improvement remains unmeasured.
59
+ All five candidates remain evaluation-only; uncertain and failed replies stay in the report.
60
+ An additional rehearsal uses the packed candidate and actual `agentsfleet` source in an isolated worktree.
61
+ See the [evaluation design](docs/architecture/judgment-evaluation.md) for commands, evidence and limits.
62
+
63
+ The `.orly/` layout, six runtime questions and 0.12.1 audit and check-diagnostic fixes remain unchanged.
64
+ The [0.12 report](evals/release/release-report.md) retains the earlier package and consumer migration evidence.
65
+ TypeSafe's Jev model supplies runtime advice through explicit `orly judge` requests; contributor measurement uses the separate live evaluator.
59
66
 
60
- 0.12.1 avoids copying repository files for staged audits with no matching files
61
- and makes hosted-check monitoring show job states and links before reporting a failure.
62
67
  See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
63
- The install commands below fetch the published package.
68
+ The install commands below fetch the published package; 0.13.0 publication is a separate owner action.
64
69
 
65
70
  ---
66
71
 
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0 — 2026-10-07
4
+
5
+ A source-checkout examiner checks unsupported completion claims and grades actual TypeSafe Jev answers against frozen expectations.
6
+
7
+ - **Comparison** — freezes six runtime questions and evaluates five proposed checks with separately validated expected answers.
8
+ - **Task evidence** — hidden behavioral checks reject seeded false completions; reports retain failed and missing attempts.
9
+ - **Measurement** — retains typed Jev replies, uncertainty, failures and per-example grades; replay sends no new requests.
10
+ - **OpenAPI guidance** — names the two surviving parity tests and makes build regeneration plus a clean diff an explicit verify step.
11
+ - **Adoption** — keeps candidates evaluation-only because independent provenance and paired-task improvement remain unproved.
12
+
13
+ The installed `.orly/` layout and 0.12.1 fixes remain unchanged.
14
+
3
15
  ## 0.12.1
4
16
 
5
17
  Staged audits avoid copying repository files when their filename scope is empty.
package/docs/JUDGMENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
  type: reference
3
3
  audience: contributor
4
4
  verified: 2026-10-07
5
- product_version: 0.12.0
5
+ product_version: 0.13.0
6
6
  executable: false
7
7
  ---
8
8
 
@@ -10,8 +10,9 @@ executable: false
10
10
 
11
11
  ## Synopsis
12
12
 
13
- The 0.12.0 candidate retains the six questions shipped in 0.11.0.
14
- The comparison evaluator is parked for proposed 0.13 work; this release makes no new model-quality claim.
13
+ The 0.13.0 candidate retains the six questions shipped in 0.11.0.
14
+ The contributor [comparison examiner](architecture/judgment-evaluation.md) tests five evaluation-only additions with offline controls and explicitly requested TypeSafe Jev measurements.
15
+ Its results establish examiner behavior; model quality and autonomous agent improvement remain unmeasured.
15
16
 
16
17
  `orly judge` asks one bounded question about evidence you select.
17
18
  It accompanies planning, verification, review and documentation.
@@ -452,7 +452,7 @@ table decides the paths and methods, `#[utoipa::path]` beside each handler
452
452
  carries the prose and the status codes, and the `afd_wire` types carry the
453
453
  schemas. The `public/openapi/` YAML tree this section used to describe was
454
454
  retired with the Zig daemon; a hand-edit to the JSON is reverted by the next
455
- regeneration and fails a test before it gets that far.
455
+ regeneration.
456
456
 
457
457
  ### Adding, renaming, or removing an endpoint
458
458
 
@@ -468,15 +468,24 @@ regeneration and fails a test before it gets that far.
468
468
  --bin agentsfleetd -- --no-banner openapi > ../public/openapi.json
469
469
  ```
470
470
 
471
- **Parity is mechanical, not reviewer-enforced.** Three tests grade it, and the
471
+ **Parity is mechanical, not reviewer-enforced.** Two tests grade it, and the
472
472
  first is the one that used to be a review obligation:
473
473
 
474
474
  | Test | What it refuses |
475
475
  |---|---|
476
476
  | `test_coverage_gate_rust_source` | a served route with no annotation, or an annotation for a route nobody mounts — named with its method and direction |
477
- | `test_openapi_build_is_the_source` | a committed artifact that is not what the build emits |
478
477
  | `test_documented_codes_match_refusals` | an operation that omits a refusal its guard or scope rung guarantees |
479
478
 
479
+ No test checks that `public/openapi.json` matches the build. Regeneration is a
480
+ verify step: from the repository root, run the generator, then check for drift.
481
+ The subshell keeps the diff command at the repository root.
482
+
483
+ ```bash
484
+ (cd rustd && cargo run -q -p agentsfleetd --features openapi \
485
+ --bin agentsfleetd -- --no-banner openapi > ../public/openapi.json)
486
+ git diff --exit-code public/openapi.json
487
+ ```
488
+
480
489
  The prose is graded too: `scripts/check_documentation_rules.py` reads the
481
490
  generated document, so a description that breaks the wording rules fails
482
491
  `make lint-all`. Fix it at the annotation — or, for a schema description, at the
package/llms.txt CHANGED
@@ -17,8 +17,14 @@ originally shipped in 0.11.0. The 0.12.1 patch avoids irrelevant staged audit
17
17
  snapshots and preserves hosted-check diagnostics. The `bunx` commands below
18
18
  fetch the published package; available versions are listed at
19
19
  https://github.com/agentsfleet/orly/releases. Use an installed local tarball for candidate checks.
20
- The comparison evaluator is parked for proposed 0.13 work; do not claim measured
21
- judgment improvement or enable new questions from its unfinished results.
20
+ The 0.13.0 source candidate adds contributor-only offline controls and a separate explicit TypeSafe Jev measurement command.
21
+ Run `bun evals/judgments/comparison/run.ts --check` or `--offline` from source.
22
+ The separate `jev-run.ts --live` collects actual model replies; `--replay` checks saved answers with no new requests.
23
+ See `docs/architecture/judgment-evaluation.md` for required inputs and measured limitations.
24
+ Expected output is JSON with `passed: true`; deliberate false completions stay failed.
25
+ All five proposed checks remain evaluation-only. Do not infer model accuracy or
26
+ agent improvement from scripted controls. Live model runs, publication, merge
27
+ and live consumer upgrades need subsequent owner instruction.
22
28
 
23
29
  ---
24
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,138 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { join } from "node:path";
3
+
4
+ import catalog from "../evals/judgments/comparison/catalog.json";
5
+ import { CASE_CLASS, CHECK_ERROR, SPLIT } from "../evals/judgments/comparison/types";
6
+ import { validateCases, validateCatalog } from "../evals/judgments/comparison/validate";
7
+ import { OrlyError } from "./model";
8
+ import { MODEL, QUESTIONS, ROLE } from "./judgments/constants";
9
+ import { digest } from "./judgments/files";
10
+ import { cleanupTemporaryDirectories, temporaryDirectory } from "./gates_test_support";
11
+
12
+ const OTHER_MODEL = "different-model";
13
+ const DIGEST_HEX_LENGTH = 64;
14
+ const WRONG_DIGEST = "0".repeat(DIGEST_HEX_LENGTH);
15
+ const SOURCE_PATH = "requirement.md";
16
+ const SOURCE_CONTENT = "Input must include a locale. The caller supplies the locale before formatting.\n";
17
+
18
+ afterEach(cleanupTemporaryDirectories);
19
+
20
+ async function caseFixture() {
21
+ const root = temporaryDirectory();
22
+ await Bun.write(join(root, SOURCE_PATH), SOURCE_CONTENT);
23
+ const entry = {
24
+ id: "locale-prerequisite", question: QUESTIONS[0], origin: "locale-source", split: SPLIT.development,
25
+ classification: CASE_CLASS.healthy, author: "fixture-author", requirement: "Name the required locale before formatting.",
26
+ evidence: [{ role: ROLE.spec, content: SOURCE_CONTENT.trim(), source: { path: SOURCE_PATH, digest: digest(SOURCE_CONTENT) } }],
27
+ };
28
+ return { root, entry };
29
+ }
30
+
31
+ test("comparison_preserves_runtime_baseline", () => {
32
+ const parsed = validateCatalog(catalog);
33
+ expect(parsed.model).toBe(MODEL);
34
+ expect(parsed.baseline.map((entry) => entry.question)).toEqual([...QUESTIONS]);
35
+ expect(parsed.candidates.map((entry) => entry.question)).toEqual([
36
+ "plan.obligation_coverage", "evidence.sufficiency", "verify.production_wiring",
37
+ "review.failure_coverage", "review.finding_resolution",
38
+ ]);
39
+ });
40
+
41
+ test("comparison_rejects_changed_model", () => {
42
+ expect(() => validateCatalog({ ...catalog, model: OTHER_MODEL })).toThrow(new OrlyError(CHECK_ERROR.schema));
43
+ });
44
+
45
+ test("comparison_rejects_changed_baseline_definition", () => {
46
+ const changed = structuredClone(catalog);
47
+ for (const entry of changed.baseline) entry.definition_digest = WRONG_DIGEST;
48
+ expect(() => validateCatalog(changed)).toThrow(new OrlyError(CHECK_ERROR.baseline));
49
+ });
50
+
51
+ test("comparison_rejects_missing_baseline_question", () => {
52
+ expect(() => validateCatalog({ ...catalog, baseline: catalog.baseline.slice(1) })).toThrow(new OrlyError(CHECK_ERROR.schema));
53
+ });
54
+
55
+ test("comparison_rejects_duplicate_baseline_question", () => {
56
+ const first = catalog.baseline.at(0);
57
+ expect(first).toBeDefined();
58
+ expect(() => validateCatalog({ ...catalog, baseline: catalog.baseline.map(() => first) })).toThrow(new OrlyError(CHECK_ERROR.baseline));
59
+ });
60
+
61
+ test("candidate_inputs_are_complete", () => {
62
+ const parsed = validateCatalog(catalog);
63
+ for (const candidate of parsed.candidates) {
64
+ expect(candidate.required_roles.length).toBeGreaterThan(0);
65
+ expect(Object.keys(candidate.criteria).sort()).toEqual(["ambiguous", "defective", "insufficient", "supported"]);
66
+ }
67
+ });
68
+
69
+ test("comparison_rejects_missing_candidate", () => {
70
+ expect(() => validateCatalog({ ...catalog, candidates: catalog.candidates.slice(1) })).toThrow(new OrlyError(CHECK_ERROR.schema));
71
+ });
72
+
73
+ test("comparison_rejects_duplicate_candidates", () => {
74
+ const first = catalog.candidates.at(0);
75
+ expect(first).toBeDefined();
76
+ expect(() => validateCatalog({ ...catalog, candidates: catalog.candidates.map(() => first) })).toThrow(new OrlyError(CHECK_ERROR.candidates));
77
+ });
78
+
79
+ test("comparison_rejects_missing_evidence_roles", () => {
80
+ const candidates = catalog.candidates.map((entry) => ({ ...entry, required_roles: entry.required_roles.slice(1) }));
81
+ expect(() => validateCatalog({ ...catalog, candidates })).toThrow(new OrlyError(CHECK_ERROR.roles));
82
+ });
83
+
84
+ test("comparison_rejects_empty_question_instructions", () => {
85
+ const candidates = catalog.candidates.map((entry) => ({ ...entry, instructions: "" }));
86
+ expect(() => validateCatalog({ ...catalog, candidates })).toThrow(new OrlyError(CHECK_ERROR.schema));
87
+ });
88
+
89
+ test("comparison_rejects_unbounded_answer_fields", () => {
90
+ const candidates = catalog.candidates.map((entry) => ({ ...entry, criteria: { ...entry.criteria, run_command: "true" } }));
91
+ expect(() => validateCatalog({ ...catalog, candidates })).toThrow(new OrlyError(CHECK_ERROR.schema));
92
+ });
93
+
94
+ test("comparison_rejects_unknown_catalog_fields", () => {
95
+ expect(() => validateCatalog({ ...catalog, enable_remote: true })).toThrow(new OrlyError(CHECK_ERROR.schema));
96
+ });
97
+
98
+ test("comparison_rejects_invalid_provenance", async () => {
99
+ const { root, entry } = await caseFixture();
100
+ const checked = validateCatalog(catalog);
101
+ expect(await validateCases(root, checked, [entry])).toEqual([entry]);
102
+ const changed = { ...entry, evidence: entry.evidence.map((item) => ({ ...item, source: { ...item.source, digest: WRONG_DIGEST } })) };
103
+ await expect(validateCases(root, checked, [changed])).rejects.toThrow(new OrlyError(CHECK_ERROR.source));
104
+ });
105
+
106
+ test("comparison_rejects_duplicate_cases", async () => {
107
+ const { root, entry } = await caseFixture();
108
+ await expect(validateCases(root, validateCatalog(catalog), [entry, entry])).rejects.toThrow(new OrlyError(CHECK_ERROR.duplicate));
109
+ });
110
+
111
+ test("comparison_rejects_copied_evidence_with_changed_identity", async () => {
112
+ const { root, entry } = await caseFixture();
113
+ const copied = { ...entry, id: "copied-case", origin: "invented-origin", split: SPLIT.heldout };
114
+ await expect(validateCases(root, validateCatalog(catalog), [entry, copied])).rejects.toThrow(new OrlyError(CHECK_ERROR.duplicate));
115
+ });
116
+
117
+ test("comparison_rejects_origin_crossing_splits", async () => {
118
+ const { root, entry } = await caseFixture();
119
+ const copied = { ...entry, id: "second-case", requirement: "State the locale needed by the formatter.", split: SPLIT.heldout };
120
+ await expect(validateCases(root, validateCatalog(catalog), [entry, copied])).rejects.toThrow(new OrlyError(CHECK_ERROR.split));
121
+ });
122
+
123
+ test("comparison_rejects_invented_evidence_text", async () => {
124
+ const { root, entry } = await caseFixture();
125
+ const changed = { ...entry, evidence: entry.evidence.map((item) => ({ ...item, content: "Unrelated invented source." })) };
126
+ await expect(validateCases(root, validateCatalog(catalog), [changed])).rejects.toThrow(new OrlyError(CHECK_ERROR.source));
127
+ });
128
+
129
+ test("comparison_rejects_incomplete_case_roles", async () => {
130
+ const { root, entry } = await caseFixture();
131
+ const changed = { ...entry, evidence: entry.evidence.map((item) => ({ ...item, role: ROLE.context })) };
132
+ await expect(validateCases(root, validateCatalog(catalog), [changed])).rejects.toThrow(new OrlyError(CHECK_ERROR.evidence));
133
+ });
134
+
135
+ test("comparison_rejects_empty_case_corpus", async () => {
136
+ const { root } = await caseFixture();
137
+ await expect(validateCases(root, validateCatalog(catalog), [])).rejects.toThrow(new OrlyError(CHECK_ERROR.cases));
138
+ });
@@ -0,0 +1,81 @@
1
+ import { expect, test } from "bun:test";
2
+ import { resolve } from "node:path";
3
+
4
+ import { loadCases } from "../evals/judgments/comparison/corpus";
5
+ import { independentlyExpected } from "../evals/judgments/comparison/hidden/label-oracle";
6
+ import { comparisonCommand } from "../evals/judgments/comparison/run";
7
+ import { collectTasks } from "../evals/judgments/comparison/report";
8
+ import { loadTasks } from "../evals/judgments/comparison/tasks";
9
+ import { CASE_CLASS, CANDIDATE, CLASSIFICATION, TASK_CONTROL, TASK_STATE } from "../evals/judgments/comparison/types";
10
+ import { QUESTIONS } from "./judgments/constants";
11
+
12
+ const ROOT = resolve(import.meta.dir, "..");
13
+ const OFFLINE = "--offline";
14
+ const INCOMPLETE = "incomplete";
15
+ const FIXTURE_FAILURE = "injected unavailable execution";
16
+ const TEST_TIMEOUT = 120_000;
17
+
18
+ test("hidden_assertions_reject_false_completion_and_preserve_honest_blocks", async () => {
19
+ const result = await comparisonCommand([OFFLINE], ROOT);
20
+ expect(result.exit).toBe(0);
21
+ expect(result.report.controls).toHaveLength(12);
22
+ for (const control of result.report.controls) {
23
+ expect(control.state).not.toBe(INCOMPLETE);
24
+ if (control.state === INCOMPLETE) throw new Error(control.error);
25
+ expect(control.linked.result.exit_code).toBe(0);
26
+ expect(control.cleanup).toEqual({ workspace_removed: true, receipts_removed: true });
27
+ if (control.control === TASK_CONTROL.falseCompletion) {
28
+ expect(control.state).toBe(TASK_STATE.failed);
29
+ expect(control.independent.checks.some((check) => !check.passed)).toBe(true);
30
+ }
31
+ }
32
+ expect(result.report.controls.filter((entry) => entry.state === TASK_STATE.completed)).toHaveLength(5);
33
+ expect(result.report.controls.filter((entry) => entry.state === TASK_STATE.blocked)).toHaveLength(1);
34
+ expect(result.report.observations.every((entry) => entry.answer === null)).toBe(true);
35
+ expect(result.report.adoption.every((entry) => !entry.eligible)).toBe(true);
36
+ expect(result.report.live_requests).toBe(0);
37
+ expect(result.report.autonomous_completion).toBeNull();
38
+ const repeated = await comparisonCommand([OFFLINE], ROOT);
39
+ expect(repeated.report.reproducible).toEqual(result.report.reproducible);
40
+ }, TEST_TIMEOUT);
41
+
42
+ test("missing_task_attempts_are_retained_without_preventing_later_attempts", async () => {
43
+ let called = 0;
44
+ const tasks = await loadTasks(ROOT);
45
+ const attempts = await collectTasks(ROOT, tasks, async () => { called++; throw new Error(FIXTURE_FAILURE); });
46
+ expect(called).toBe(12);
47
+ expect(attempts).toHaveLength(12);
48
+ expect(attempts.every((entry) => entry.state === INCOMPLETE && entry.completion_credit === 0)).toBe(true);
49
+ });
50
+
51
+ test("question_oracles_use_role_evidence_and_never_the_stated_classification", async () => {
52
+ const cases = await loadCases(ROOT);
53
+ for (const question of [...QUESTIONS, ...Object.values(CANDIDATE)]) {
54
+ const healthy = cases.find((entry) => entry.question === question && entry.classification === CASE_CLASS.healthy);
55
+ const defective = cases.find((entry) => entry.question === question && entry.classification === CASE_CLASS.defective);
56
+ if (!healthy || !defective) throw new Error("Missing paired question evidence.");
57
+ expect(independentlyExpected({ ...healthy, classification: CASE_CLASS.defective }).classification).toBe(CASE_CLASS.healthy);
58
+ expect(independentlyExpected({ ...healthy, evidence: defective.evidence }).classification).toBe(CASE_CLASS.defective);
59
+ }
60
+ });
61
+
62
+ test("exact_assertion_can_correctly_reject_wrong_application_output", async () => {
63
+ const entry = (await loadCases(ROOT)).find((entry) => entry.question === QUESTIONS[2] && entry.classification === CASE_CLASS.healthy);
64
+ if (!entry) throw new Error("Missing assertion fixture.");
65
+ const changed = { ...entry, evidence: entry.evidence.map((item) => ({ ...item,
66
+ content: JSON.stringify({ ...JSON.parse(item.content), observed: "wrong output" }) })) };
67
+ expect(independentlyExpected(changed).expected_native).toBe("exact");
68
+ });
69
+
70
+ test("missing_source_and_missing_dependency_inventory_have_different_answers", async () => {
71
+ const cases = await loadCases(ROOT);
72
+ const gap = cases.find((entry) => entry.question === CANDIDATE.evidence && entry.classification === CASE_CLASS.defective);
73
+ if (!gap) throw new Error("Missing dependency-gap fixture.");
74
+ expect(independentlyExpected(gap).expected_native).toBe(CLASSIFICATION.defective);
75
+ const noInventory = { ...gap, evidence: gap.evidence.map((item) => item.role === "dependencies" ? { ...item, content: "{}" } : item) };
76
+ expect(independentlyExpected(noInventory).expected_native).toBe(CLASSIFICATION.insufficient);
77
+ });
78
+
79
+ test("contributor_command_refuses_live_or_unknown_arguments", async () => {
80
+ await expect(comparisonCommand(["--live"], ROOT)).rejects.toThrow("Use exactly --check or --offline");
81
+ });
@@ -0,0 +1,134 @@
1
+ import { expect, test } from "bun:test";
2
+ import { resolve } from "node:path";
3
+
4
+ import { loadCases } from "../evals/judgments/comparison/corpus";
5
+ import { corpusJevExamples, evaluateJev, JEV_PREFLIGHT_ERROR, MAX_JEV_REQUESTS, type JevAttempt, type JevExample } from "../evals/judgments/comparison/jev";
6
+ import { CANDIDATE, labelsSchema } from "../evals/judgments/comparison/types";
7
+ import { validateCatalog } from "../evals/judgments/comparison/validate";
8
+ import { ANSWER_KIND, MAX_REQUEST_BYTES, MAX_STATE_BYTES, MODEL, REPLY_KEY } from "./judgments/constants";
9
+ import type { Reply } from "./judgments/wire";
10
+ import { OrlyError } from "./model";
11
+
12
+ const ROOT = resolve(import.meta.dir, "..");
13
+ const CREDENTIAL = "test-credential";
14
+ const PRIVATE_DETAIL = "private-provider-detail";
15
+ const FAILURE = "retention write failed";
16
+ const SCAN = "scan:";
17
+ const REQUEST = "request:";
18
+
19
+ async function examples(): Promise<JevExample[]> {
20
+ const catalog = validateCatalog(await Bun.file(resolve(ROOT, "evals/judgments/comparison/catalog.json")).json());
21
+ const cases = await loadCases(ROOT);
22
+ const labels = labelsSchema.parse(await Bun.file(resolve(ROOT, "evals/judgments/comparison/labels.json")).json());
23
+ const all = corpusJevExamples(catalog, cases, labels);
24
+ return [all.find((entry) => entry.question === "document.claim"), all.find((entry) => entry.question === "verify.assertion"),
25
+ all.find((entry) => entry.question === CANDIDATE.wiring)].filter((entry) => entry !== undefined);
26
+ }
27
+
28
+ function response(entry: JevExample): Reply {
29
+ const question = entry.input.definition.question;
30
+ const answer = question.type === ANSWER_KIND.noul ? { type: question.type, noul: 1 } : {
31
+ type: question.type, choice: entry.expected, confidence: 1,
32
+ probabilities: Object.fromEntries(Object.keys(question.criteria).map((key) => [key, key === entry.expected ? 1 : 0])),
33
+ };
34
+ return { model: MODEL, answers: { [REPLY_KEY]: answer }, usage: { input_tokens: 100, output_tokens: 20 } };
35
+ }
36
+
37
+ test("jev_requests_preserve_actual_native_answers_and_failures", async () => {
38
+ const selected = await examples();
39
+ const order: string[] = [];
40
+ const retained: JevAttempt[] = [];
41
+ let ordinal = 0;
42
+ using server = Bun.serve({ port: 0, async fetch(request) {
43
+ const entry = selected[ordinal++];
44
+ if (!entry) throw new Error("Unexpected request.");
45
+ order.push(REQUEST + entry.id);
46
+ expect(await request.text()).toBe(entry.input.request);
47
+ expect(request.headers.get("authorization")).toBe(`Bearer ${CREDENTIAL}`);
48
+ expect(retained.at(-1)).toMatchObject({ id: entry.id, requests: 1, reply: null });
49
+ return ordinal === 2 ? new Response(PRIVATE_DETAIL, { status: 429 }) : Response.json(response(entry));
50
+ } });
51
+ const actual = await evaluateJev(selected, CREDENTIAL, {
52
+ scan: async (input) => { order.push(SCAN + selected.find((entry) => entry.input.request === input)?.id); },
53
+ fetch: (request) => fetch(new Request(server.url, request)),
54
+ }, async (attempt) => { retained.push(attempt); });
55
+ expect(ordinal).toBe(3);
56
+ expect(order.slice(0, 3)).toEqual(selected.map((entry) => SCAN + entry.id));
57
+ expect(actual.map((entry) => entry.requests)).toEqual([1, 1, 1]);
58
+ expect(actual.map((entry) => entry.reply !== null)).toEqual([true, false, true]);
59
+ expect(actual[1]?.failure).toContain("HTTP 429; no automatic retry");
60
+ expect(JSON.stringify(actual)).not.toContain(PRIVATE_DETAIL);
61
+ expect(JSON.stringify(actual)).not.toContain(CREDENTIAL);
62
+ expect(retained).toHaveLength(6);
63
+ const first = selected[0];
64
+ if (!first) throw new Error("Missing input.");
65
+ expect(actual[0]?.reply).toEqual(response(first));
66
+ });
67
+
68
+ test("jev_missing_credentials_and_each_scanner_failure_prevent_every_upload", async () => {
69
+ const selected = await examples();
70
+ for (const failAt of [0, 1, 2, 3]) {
71
+ let scans = 0;
72
+ let uploads = 0;
73
+ const attempts = await evaluateJev(selected, failAt === 0 ? undefined : CREDENTIAL, {
74
+ scan: async () => { if (++scans === failAt) throw new OrlyError("Secret scanner failed; nothing was uploaded."); },
75
+ fetch: async () => { uploads++; throw new Error(PRIVATE_DETAIL); },
76
+ });
77
+ expect(scans).toBe(failAt);
78
+ expect(uploads).toBe(0);
79
+ expect(attempts).toHaveLength(3);
80
+ expect(attempts.every((entry) => entry.requests === 0 && entry.reply === null && entry.failure !== null)).toBe(true);
81
+ }
82
+ });
83
+
84
+ test("jev_invalid_schedule_and_forged_upload_refuse_before_scanning", async () => {
85
+ const selected = await examples();
86
+ const entry = selected[0];
87
+ if (!entry) throw new Error("Missing input.");
88
+ let touched = 0;
89
+ const dependencies = { scan: async () => { touched++; }, fetch: async () => { touched++; return Response.json({}); } };
90
+ const badInputs = ["{", "x".repeat(MAX_REQUEST_BYTES + 1), JSON.stringify({ ...JSON.parse(entry.input.request), model: "unpinned" }),
91
+ JSON.stringify({ ...JSON.parse(entry.input.request), state: { requirement: "bounded", evidence: [{ role: "spec", text: "x".repeat(MAX_STATE_BYTES + 1) }] } })];
92
+ const invalid = [[], [entry, entry], Array.from({ length: MAX_JEV_REQUESTS + 1 }, (_, id) => ({ ...entry, id: String(id) })),
93
+ ...badInputs.map((request) => [{ ...entry, input: { ...entry.input, request } }])];
94
+ for (const schedule of invalid) await expect(evaluateJev(schedule, CREDENTIAL, dependencies)).rejects.toThrow(JEV_PREFLIGHT_ERROR);
95
+ expect(touched).toBe(0);
96
+ });
97
+
98
+ test("jev_malformed_native_reply_and_transport_failure_remain_missing_and_continue", async () => {
99
+ const selected = await examples();
100
+ let ordinal = 0;
101
+ const attempts = await evaluateJev(selected, CREDENTIAL, { scan: async () => {}, fetch: async () => {
102
+ ordinal++;
103
+ if (ordinal === 1) return Response.json({ model: "wrong-model", answers: {}, usage: {} });
104
+ if (ordinal === 2) throw new Error(CREDENTIAL + PRIVATE_DETAIL);
105
+ return new Response("{");
106
+ } });
107
+ expect(ordinal).toBe(3);
108
+ expect(attempts.every((entry) => entry.reply === null && entry.requests === 1 && entry.failure !== null)).toBe(true);
109
+ expect(attempts[0]?.failure).toContain("pinned model and answer schema");
110
+ expect(attempts[1]?.failure).toContain("Provider transport failed");
111
+ expect(attempts[2]?.failure).toContain("not valid JSON");
112
+ expect(JSON.stringify(attempts)).not.toContain(CREDENTIAL);
113
+ expect(JSON.stringify(attempts)).not.toContain(PRIVATE_DETAIL);
114
+ });
115
+
116
+ test("jev_retention_failure_at_each_ordered_write_prevents_the_next_upload", async () => {
117
+ const selected = await examples();
118
+ for (const failAt of [1, 2, 3, 4, 5, 6]) {
119
+ let writes = 0;
120
+ let uploads = 0;
121
+ const durable: JevAttempt[] = [];
122
+ await expect(evaluateJev(selected, CREDENTIAL, { scan: async () => {}, fetch: async () => {
123
+ const entry = selected[uploads++];
124
+ if (!entry) throw new Error("Unexpected upload.");
125
+ return Response.json(response(entry));
126
+ } }, async (attempt) => {
127
+ if (++writes === failAt) throw new Error(FAILURE);
128
+ durable.push(attempt);
129
+ })).rejects.toThrow(FAILURE);
130
+ expect(uploads).toBe(Math.floor(failAt / 2));
131
+ expect(durable).toHaveLength(failAt - 1);
132
+ if (failAt % 2 === 0) expect(durable.at(-1)).toMatchObject({ requests: 1, reply: null });
133
+ }
134
+ });