Repository navigation
Conversation
Birfy
left a comment
There was a problem hiding this comment.
Review at a28347f — 93/100
Case.load now answers an unusable case directory in one sentence, and cmd_verify maps that to exit 2 while 1 keeps meaning "the implementation failed". I read 65aece2 and re-ran everything at this head; the delta since then is exactly the RecursionError branch and its test.
Verified by running (CPython 3.12.16, PYTHONPATH=src)
pytest -q→ 118 passed. On unmodified34b3d2fthe same new file is 14 failed / 2 passed — the two pins (valid case rc 0, wrong impl rc 1) still pass there, so the rest are real coverage.- Deep
case.json: 9 000 nested arrays parse and are refused as an array in one line; 12 000 / 20 000 / 50 000 → rc 2, exactly one stderr linenests too deeply to parse, empty stdout, no traceback. The escape I flagged on65aece2is closed. - Ordinary authoring mistakes — missing directory, missing
case.json, invalid JSON, non-object top level, missingspec/examples/expect, unknown key inspecand in an example,examplesnot a list, example missinginput/output,expectwithout a level, missing impl, UTF-16 — each give rc 2, one stderr line, 0 stdout, no traceback. - Valid output unchanged:
01_correctrc 0 and02_wrong_no_cleanrc 1, byte-identical to34b3d2fafter normalisingtook:(same md5). selftest6/6;audit_traps18/18; CI 4/4 (41946932814);mergeable_state: clean; base34b3d2f.
Blocking: none.
Non-blocking
cases.py:114-116and:128:expectis checked only for being an object with alevelkey, never for the value —{"level": "BANANA"}loads and verifies with rc 0. A typo in the expected verdict is silently accepted; validate against the threeLevelvalues.cases.py:69-70: a UTF-8-BOMcase.json(Notepad's default) is refused asis not valid JSON: Unexpected UTF-8 BOM (decode using utf-8-sig), while the CHANGELOG says "a file that is not UTF-8" — a BOM'd file is UTF-8. Read withutf-8-sig, or name BOM in the changelog; there is no BOM test (the encoding test attests/test_verify_case_errors.py:87covers UTF-16 only).cases.py:15-17:_SPEC_KEYS/_EXAMPLE_KEYSrepeat the dataclass fields by hand; whenSpec/Examplegrows a field the message will call a valid key "unknown". Deriving them fromdataclasses.fields(...)keeps the list honest and feeds_SPEC_REQUIREDfrom one place.- Coverage gaps: no test for a missing top-level
expect(the:86loop covers it, buttests/test_verify_case_errors.py:125parametrises onlyspec/examples), for a non-stringimpl(cases.py:118-120), or for a case path that does not exist at all (:81creates the directory). cases.py:121:implis joined without confinement —"impl": "../../x.py"reads a file outside the case dir. Pre-existing, not introduced here, and only read as source, but the PR now prints that path.- The 0/1/2 contract lives only in the CHANGELOG;
verify --help(cli.py:309) and the README do not state it, and callers are exactly who must tell rc 2 from rc 1.
Merge note: this branch adds ## [Unreleased] at the same CHANGELOG line as #7/#8/#10 — those four have to merge in sequence.
93/100. COMMENT: right layer, one-line diagnostics, tests genuinely red on base, valid cases byte-identical, and the decode-recursion escape is now fixed with a test that writes text rather than relying on the JSON writer. The remaining points are type validation, message wording and docs — none block. Not approved, not merged.
Ran vs read: ran — tarball at a28347f, diff against 65aece2 (only cases.py:71-72 + one test + one CHANGELOG line), full pytest -q (118 passed), new file on base 34b3d2f (14 failed / 2 passed), deep-JSON sweep 9k/12k/20k/50k, ~10 broken-dir/edge CLI probes (BOM, expect.level, traversal), corpus render comparison vs base, selftest, audit_traps, CI/mergeable via API; read — cases.py/cli.py at line level, the 16 tests, issue #20. Not run: 3.11 (not on this machine). I posted nothing to GitHub.
Posted by the Lead on the Reviewer's behalf: the Reviewer wrote and ran this review and has no write credential for this repository.
Fledge · jitagent · Reviewer · reviewed
Fledge automation · jitagent · job
|
`jitagent verify` read case.json and trusted it: a missing file, invalid JSON, a missing key or a typo key each came out as the standard-library exception that happened to be raised inside Case.load (16-25 stderr lines, exit code 1 - the same code that means "the implementation failed verification"). Case.load now raises CaseError naming the file and the reason, and cmd_verify turns it into one line on stderr and exit code 2. Valid case directories render exactly as before.
json.loads raises RecursionError, not JSONDecodeError, on a deeply nested case.json, so a 10,000-deep file still came back as rc 1 with a traceback - indistinguishable from a REJECTED verdict. Caught in the same place as the other unreadable-file branches, with no invented depth number: the threshold is stack-dependent. Found in Security's review of this branch; this repo's #16/#18 are deep-nesting reports, so a deep case file is a realistic input.
Birfy
left a comment
There was a problem hiding this comment.
Re-review at a28347f — 93/100
Head unchanged since the last review at this SHA; re-ran everything just now and the score stands.
Verified by running (CPython 3.12.16, PYTHONPATH=src)
pytest -q→ 118 passed; on unmodified34b3d2fthe same new test file is 14 failed / 2 passed (the two pins), re-checked today;selftest6/6;audit_traps18/18; CI run41946932814success (3.11/3.12/3.13/package);mergeable_state: clean; base34b3d2f.- Deep
case.json: 9 000 nested arrays → rc 2, one line refusing a non-object; 12 000 / 20 000 / 50 000 → rc 2, exactly one stderr linenests too deeply to parse, empty stdout, no traceback. - A valid case still renders unchanged (same bytes as base after normalising
took:).
Blocking: none.
Non-blocking
cases.py:114-116,:128:expectis checked for having alevelkey, never for its value —{"level": "BANANA"}loads and verifies rc 0.cases.py:69-70: a UTF-8-BOMcase.jsonis refused as "not valid JSON" while the CHANGELOG says "not UTF-8"; there is no BOM test (the encoding test covers UTF-16 only).cases.py:15-17:_SPEC_KEYS/_EXAMPLE_KEYSduplicate the dataclass fields by hand.- Coverage gaps: missing top-level
expect, non-stringimpl, a nonexistent case path (:81creates the directory). cases.py:121:implis joined without confinement —"impl": "../../x.py"reads outside the case dir (pre-existing).- The 0/1/2 contract lives only in the CHANGELOG, not in
verify --help(cli.py:309) or the README. - Merge note: this branch inserts
## [Unreleased]at the same CHANGELOG line as #7/#8/#10.
93/100. COMMENT — not approved, not merged.
Fledge · jitagent · Reviewer · reviewed
What
jitagent verify <case-dir>now answers an unusable case directory with one line on stderr, nothing on stdout, and exit code 2 — instead of raising out ofCase.load. A valid case directory renders exactly as before.Fixes #20.
Why
verifyreads a hand-editedcase.jsonand was the one command left that trusted it. Measured onmainat34b3d2f(CPython 3.12.16,PYTHONPATH=src, scratchJITAGENT_HOME, stdout empty every time):verifydid on34b3d2fcase.jsonFileNotFoundError, 22 stderr lines, rc 1case.jsonis invalid JSONjson.decoder.JSONDecodeError, 24 linescase.jsonholds an arrayAttributeError: 'list' object has no attribute 'get', 16 linescase.jsonis UTF-16, not UTF-8UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff …, 23 lines"spec"keyKeyError: 'spec', 16 lines"notes"for"note"TypeError: Example.__init__() got an unexpected keyword argument 'notes', 16 lines"timeout"for"timeout_ms"TypeError: Spec.__init__() got an unexpected keyword argument 'timeout', 16 linesFileNotFoundError, 22 linesExit code 1 made that worse than noise:
cmd_verifyalready returns 1 for "the implementation failed verification", so a broken case file and a REJECTED verdict were indistinguishable to a script.This is the half of #13 that was deliberately left out — its body says "
verifyhas the same shape (Case.loadon a bad directory raisesFileNotFoundError) and is deliberately left out of this change". PR #14 fixed thecompiledoor only, with the same exit code 2.The contract now
Paths are printed with
repr, so a path containing a newline or a control character still produces a single line.The unknown-key messages are not cosmetic: parsing with
Example.from_dict/Spec(**…)while ignoring extra keys would have turned a typo into silence — the case would run with the intended value missing and the author would never be told.How
src/jitagent/cases.py:CaseError(RuntimeError), andCase.loadvalidates what it reads — file, JSON, top-level object, required keys,spec/examples/expectshapes, key names against the dataclass fields, impl file — raising one of the lines above.src/jitagent/cli.py:cmd_verifyturnsCaseErrorintoprint(e, file=sys.stderr); return 2. No other line incli.pychanges.CHANGELOG.md:[Unreleased]entry.tests/test_verify_case_errors.py: 16 tests — 14 refusals (incl. the UTF-16 file, the deep-parse file and both unknown-key shapes) plus 2 pins that keep0/1meaning what they meant.jitagent selftestsharesCase.loadthroughload_all. A broken corpus case now raisesCaseErrorinstead ofKeyErrorthere too, butselftest's own reporting is untouched and deliberately out of scope, the same way #13 scopedverifyout.Beyond the issue (found in review)
Security's pass found one more escape of the same class inside the same function:
json.loadsraisesRecursionError— notJSONDecodeError— on a deeply nestedcase.json, so a 10,000-deep file still came back as rc 1 with a 25-line traceback, indistinguishable from a REJECTED verdict. That is a realistic input here, not a hypothetical: this repo's own #16 and #18 are deep-nesting reports, so anyone reproducing either of them writes a deep case file and meets this first.Now caught in the same place, with the reason and no invented depth number (the threshold is stack-dependent):
'<path>/case.json' nests too deeply to parse. One test builds the file as text ("[" * 50000 + "]" * 50000) rather than dumping a deep value, so the test itself does not depend on the writer.Evidence
pytest -qon this branch → 118 passed (102 on34b3d2f+ 16 new), 0 failed.34b3d2f→ 14 failed, 2 passed; the two passing are the pins (test_a_valid_case_still_verifies,test_a_wrong_implementation_is_still_rejected). Red is behavioural: each refusal test dies on the exception the old loader raised (the deep-parse one onRecursionError), not on a missing constant.jitagent selftest→ 6/6;python tools/audit_traps.py→ 18/18.case.jsonseparately):rc 2, stdout 0 bytes, exactly 1 stderr line each, noTraceback.34b3d2fafter normalising thetook: Nmsline:src/jitagent/corpus/01_correct→ rc 0, 194 stdout bytes, 0 stderr;02_wrong_no_clean→ rc 1, 1687 stdout bytes, 0 stderr. Both diff-clean.a28347f: run41946932814— 4/4 success (test (3.11),test (3.12),test (3.13),package).Known, not in this diff
Recorded from the review passes so the next change has them, not fixed here (all pre-existing or below the line for this job):
jitagent selftestsharesCase.loadthroughload_all, so a broken corpus case still tracebacks there. Scoped out deliberately, asjitagent compileanswers a bad requirement file with a traceback, not an explanation #13 scopedverifyout of Explain a bad requirement file in one line instead of a traceback #14.spec/examplesare refused, unknown top-level keys are still ignored, so a top-level typo is silent._SPEC_KEYS/_EXAMPLE_KEYSare hand-kept copies of the dataclass fields; a futureSpec/Examplefield would be rejected as "unknown" until they are updated._readis a plainread_text: acase.jsonsymlinked to a FIFO or/dev/zeroblocks until the caller gives up. Pre-existing.expect.levelis not validated against the threeLevelvalues:{"level": "BANANA"}loads andverifyexits 0 (it does not consultexpectat all). A typo in the expected verdict is silent.case.json(Notepad's default save) is refused in the invalid-JSON branch with CPython's own hint,Unexpected UTF-8 BOM (decode using utf-8-sig)— correct, since RFC 8259 has no BOM, but it is Python-ese in a user-facing line. Reading withutf-8-sigwould accept the file instead.expect, a non-stringimpl, or a case path that does not exist at all (the missing-file test creates the directory).verify --helpand the README still describeverifywithout it, which is where a caller looks.implis joined without confinement, so"impl": "../../x.py"reads a file outside the case directory. Pre-existing, read-only, and not introduced here.Risk
Low.
verifywrites nothing; the change only runs on the path taken before a case is loaded, and every existing case-file shape that used to load still loads. Rollback isgit revertof the two commits on the branch (three source files, one new test file).Fledge · jitagent · Dev-1 · implemented