@agentsfleet/orly 0.12.0 → 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 +24 -16
- package/audits/deinit-pairs.sh +1 -1
- package/audits/design-tokens.sh +1 -1
- package/audits/error-codes.sh +1 -1
- package/audits/logging.sh +1 -1
- package/audits/rust-error.sh +1 -1
- package/audits/scope.sh +9 -1
- package/audits/sql-mod.sh +1 -1
- package/audits/ufs.sh +1 -1
- package/dispatch/lib.sh +1 -1
- package/docs/CHANGELOG.md +21 -1
- package/docs/JUDGMENTS.md +4 -3
- package/docs/ORLY_ARCHITECTURE.md +7 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +12 -3
- package/llms.txt +13 -5
- package/package.json +1 -1
- package/skills/orly-babysit-prs/SKILL.md +19 -4
- package/src/judgment_comparison.test.ts +138 -0
- package/src/judgment_comparison_controls.test.ts +81 -0
- package/src/judgment_comparison_jev.integration.test.ts +134 -0
- package/src/judgment_comparison_jev_consumer.test.ts +144 -0
- package/src/judgment_comparison_jev_report.test.ts +167 -0
- package/src/judgment_labels.test.ts +119 -0
- package/src/judgment_score.test.ts +166 -0
- package/src/judgment_tasks.test.ts +164 -0
- package/src/judgment_tasks_findings.test.ts +76 -0
- package/src/judgment_tasks_recovery.test.ts +119 -0
- package/src/judgments/transport.ts +2 -2
- package/src/judgments/types.ts +1 -0
- package/src/judgments/wire.ts +2 -2
- package/src/review_poll.test.ts +28 -4
- package/src/scanner_snapshot.test.ts +82 -0
package/README.md
CHANGED
|
@@ -42,22 +42,30 @@ Works with Claude Code, Codex, OpenCode, and Amp.
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
## 0.
|
|
46
|
-
|
|
47
|
-
0.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
## 0.13: measured judgment comparison
|
|
46
|
+
|
|
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
|
+
|
|
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.
|
|
55
|
+
|
|
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.
|
|
66
|
+
|
|
67
|
+
See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
|
|
68
|
+
The install commands below fetch the published package; 0.13.0 publication is a separate owner action.
|
|
61
69
|
|
|
62
70
|
---
|
|
63
71
|
|
package/audits/deinit-pairs.sh
CHANGED
package/audits/design-tokens.sh
CHANGED
|
@@ -39,7 +39,7 @@ set -euo pipefail
|
|
|
39
39
|
|
|
40
40
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
41
41
|
audit_scope_init --all "$@"
|
|
42
|
-
audit_index_snapshot
|
|
42
|
+
audit_index_snapshot '*.tsx' '*.jsx' 'ui/packages/app/*' 'ui/packages/website/*' 'ui/packages/design-system/*'
|
|
43
43
|
|
|
44
44
|
MODE="$AUDIT_MODE"
|
|
45
45
|
ROOT="$(git rev-parse --show-toplevel)"
|
package/audits/error-codes.sh
CHANGED
package/audits/logging.sh
CHANGED
|
@@ -43,7 +43,7 @@ set -euo pipefail
|
|
|
43
43
|
|
|
44
44
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
45
45
|
audit_scope_init --all "$@"
|
|
46
|
-
audit_index_snapshot
|
|
46
|
+
audit_index_snapshot '*.zig' '*.rs' 'agentsfleet/src/*.js' 'agentsfleet/src/*.jsx' 'agentsfleet/src/*.ts' 'agentsfleet/src/*.tsx'
|
|
47
47
|
|
|
48
48
|
MODE="--all"
|
|
49
49
|
STRICT=0
|
package/audits/rust-error.sh
CHANGED
package/audits/scope.sh
CHANGED
|
@@ -37,7 +37,7 @@ audit_scope_init() {
|
|
|
37
37
|
# the index copy and checked-out files belong to this invocation alone.
|
|
38
38
|
audit_index_snapshot() {
|
|
39
39
|
[ "$AUDIT_MODE" = --staged ] || return 0
|
|
40
|
-
local gitdir index
|
|
40
|
+
local gitdir index changed=0
|
|
41
41
|
AUDIT_SNAPSHOT_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/orly-index.XXXXXX")" || return 1
|
|
42
42
|
trap 'rm -rf -- "$AUDIT_SNAPSHOT_ROOT"' EXIT
|
|
43
43
|
trap 'exit 130' INT
|
|
@@ -46,6 +46,14 @@ audit_index_snapshot() {
|
|
|
46
46
|
index="$(git rev-parse --git-path index)" || exit 1
|
|
47
47
|
cp "$index" "$AUDIT_SNAPSHOT_ROOT/index" || exit 1
|
|
48
48
|
export GIT_DIR="$gitdir" GIT_INDEX_FILE="$AUDIT_SNAPSHOT_ROOT/index" GIT_OPTIONAL_LOCKS=0
|
|
49
|
+
if [ "$#" -gt 0 ]; then
|
|
50
|
+
git diff --cached --quiet --diff-filter=ACMRT -- "$@" || changed=$?
|
|
51
|
+
case "$changed" in
|
|
52
|
+
0) return 0 ;;
|
|
53
|
+
1) ;;
|
|
54
|
+
*) printf 'cannot select staged audit files (git exit %s)\n' "$changed" >&2; exit 2 ;;
|
|
55
|
+
esac
|
|
56
|
+
fi
|
|
49
57
|
export GIT_WORK_TREE="$AUDIT_SNAPSHOT_ROOT/tree"
|
|
50
58
|
mkdir "$GIT_WORK_TREE" || exit 1
|
|
51
59
|
git checkout-index --all --prefix="$GIT_WORK_TREE/" || exit 1
|
package/audits/sql-mod.sh
CHANGED
package/audits/ufs.sh
CHANGED
|
@@ -55,7 +55,7 @@ set -euo pipefail
|
|
|
55
55
|
|
|
56
56
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
57
57
|
audit_scope_init --all "$@"
|
|
58
|
-
audit_index_snapshot
|
|
58
|
+
audit_index_snapshot '*.zig' '*.ts' '*.tsx' '*.js' '*.jsx' '*.rs' '*.go'
|
|
59
59
|
|
|
60
60
|
# Default: full-codebase scan (`--all` is an explicit alias). `--staged` is the
|
|
61
61
|
# pre-commit lens — it narrows the per-file checks to `git diff --cached`. The
|
package/dispatch/lib.sh
CHANGED
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
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
|
+
|
|
15
|
+
## 0.12.1
|
|
16
|
+
|
|
17
|
+
Staged audits avoid copying repository files when their filename scope is empty.
|
|
18
|
+
|
|
19
|
+
- **Audit preflight** — selects filenames from a copied index before materialising repository files; relevant audits retain coherent staged reads.
|
|
20
|
+
- **Check diagnostics** — prints hosted job or pipeline states and links before reporting pending or failed checks.
|
|
21
|
+
- **Release status** — corrects documentation that still described published 0.12.0 as an unpublished candidate.
|
|
22
|
+
|
|
23
|
+
## 0.12.0 — 2026-10-07
|
|
4
24
|
|
|
5
25
|
Setup guidance now distinguishes the published package from the local release candidate.
|
|
6
26
|
|
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.
|
|
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.
|
|
14
|
-
The comparison
|
|
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.
|
|
@@ -84,6 +84,13 @@ Every criterion is mechanical — it reads an exit code or a file. Claims that
|
|
|
84
84
|
cannot be proven that way stay prose and are graded by the spec's rubric; they
|
|
85
85
|
never become fake criteria.
|
|
86
86
|
|
|
87
|
+
Filename-scoped staged audits copy Git's index before selecting relevant paths.
|
|
88
|
+
An empty selection skips the repository checkout; matching paths still trigger
|
|
89
|
+
a private checkout, dependency reads and indexed-link checks. Each audit owns
|
|
90
|
+
its temporary files and cleanup. Audits with unconditional registry checks
|
|
91
|
+
retain a complete checkout. Separate relevant audits retain separate snapshots;
|
|
92
|
+
this optimization does not introduce a shared snapshot cache.
|
|
93
|
+
|
|
87
94
|
Four behaviours worth knowing:
|
|
88
95
|
|
|
89
96
|
- **No spec, no problem — but closing is not escaping.** Spec criteria skip
|
|
@@ -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
|
|
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.**
|
|
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
|
@@ -12,11 +12,19 @@ Package: `@agentsfleet/orly` (npm)
|
|
|
12
12
|
Source: https://github.com/agentsfleet/orly
|
|
13
13
|
License: Massachusetts Institute of Technology (MIT)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
0.12.0 shipped migration verification; the runtime commands and `.orly/` layout
|
|
16
|
+
originally shipped in 0.11.0. The 0.12.1 patch avoids irrelevant staged audit
|
|
17
|
+
snapshots and preserves hosted-check diagnostics. The `bunx` commands below
|
|
18
|
+
fetch the published package; available versions are listed at
|
|
19
|
+
https://github.com/agentsfleet/orly/releases. Use an installed local tarball for candidate checks.
|
|
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.
|
|
20
28
|
|
|
21
29
|
---
|
|
22
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.
|
|
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",
|
|
@@ -93,8 +93,17 @@ Each cycle, after the review-thread walk, poll CI and act by cause:
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
set -euo pipefail
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
CHECK_STATUS=0
|
|
97
|
+
CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket,link) || CHECK_STATUS=$?
|
|
98
|
+
printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[];
|
|
99
|
+
(.name | type == "string") and (.state | type == "string") and
|
|
100
|
+
(.link | type == "string") and (.bucket | IN("pass", "fail", "pending", "skipping", "cancel")))' >/dev/null \
|
|
101
|
+
|| { printf 'BABYSIT: invalid check response (exit %s); reset quiet polls\n%s\n' "$CHECK_STATUS" "$CHECKS" >&2; exit 1; }
|
|
102
|
+
printf '%s' "$CHECKS" | jq -r '.[] | "\(.name) | \(.state) | \(.bucket) | \(.link)"'
|
|
103
|
+
case "$CHECK_STATUS" in
|
|
104
|
+
0|1|8) ;; # GitHub reports failed and pending checks with nonzero statuses.
|
|
105
|
+
*) printf 'BABYSIT: check fetch incomplete (exit %s); reset quiet polls\n' "$CHECK_STATUS" >&2; exit 1 ;;
|
|
106
|
+
esac
|
|
98
107
|
printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[]; .bucket == "pass" or .bucket == "skipping")' >/dev/null \
|
|
99
108
|
|| { echo "BABYSIT: checks empty or not green; reset quiet polls" >&2; exit 1; }
|
|
100
109
|
CHECK_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
|
|
@@ -107,8 +116,14 @@ echo "BABYSIT: ci=green revision=$CHECK_HEAD"
|
|
|
107
116
|
|
|
108
117
|
```bash
|
|
109
118
|
set -euo pipefail
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
PIPELINE_STATUS=0
|
|
120
|
+
PIPELINES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" --paginate | jq -sc 'add') || PIPELINE_STATUS=$?
|
|
121
|
+
printf '%s' "$PIPELINES" | jq -e 'type == "array" and length > 0 and all(.[];
|
|
122
|
+
(.id | type == "number") and (.sha | type == "string") and (.status | type == "string"))' >/dev/null \
|
|
123
|
+
|| { printf 'BABYSIT: invalid pipeline response (exit %s); reset quiet polls\n%s\n' "$PIPELINE_STATUS" "$PIPELINES" >&2; exit 1; }
|
|
124
|
+
printf '%s' "$PIPELINES" | jq -r '.[] | "pipeline \(.id) | \(.status) | \(.web_url // "URL not reported")"'
|
|
125
|
+
[ "$PIPELINE_STATUS" -eq 0 ] \
|
|
126
|
+
|| { printf 'BABYSIT: pipeline fetch incomplete (exit %s); reset quiet polls\n' "$PIPELINE_STATUS" >&2; exit 1; }
|
|
112
127
|
printf '%s' "$PIPELINES" | jq -e --arg revision "$POLL_HEAD" \
|
|
113
128
|
'[.[] | select(.sha == $revision)] | sort_by(.id) | last | .status == "success"' >/dev/null \
|
|
114
129
|
|| { echo "BABYSIT: current revision has no successful pipeline; reset quiet polls" >&2; exit 1; }
|
|
@@ -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
|
+
});
|