Repository navigation
Conversation
…ked in CI Records the request the pinned Claude Code (2.1.318) sends for a fixed tiny task, with and without a skill, normalized (dates, versions, ids, model, work dir, host facts). The arm's argv comes from its own builder, is run against a recording server (no model call), and must match the golden and pass payload_audit against it. A fidelity CI job runs the test with the npm build of the pinned version. scripts/refresh_fidelity_golden.py re-takes the golden in a clean container; tests/fixtures/fidelity/README.md says how. Refs intel#354 Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
OpenCodeReview: Commit e00782f was not reviewed. This PR has reached its review limit (6 reviews). CI at `e00782f`: 9 still running, 7 passing, 2 skipped
To review this commit: re-request review from exai-bot in the Reviewers list. <details><summary>Last review (5323b53): no findings</summary> Open threads from the bot: 0 </details> |
There was a problem hiding this comment.
🟡 Changes recommended
Fidelity comparisons remain unreliable because excluded skill descriptions and billing-version suffixes survive filtering and normalization, while both runners omit the solver’s skill-disabling environment settings.
3 open issues
What changed in this PR
3 new must-fix findings.
This PR adds a Claude Code request-fidelity check for dokimi’s docker-claude arm, supporting #354 by comparing recorded requests with pinned golden captures without calling a model.
Changes:
- Adds request recording, normalization, comparison, and golden-refresh tooling.
- Introduces bare and skill-enabled fixtures with fidelity and payload-audit tests.
- Adds a CI job using pinned Claude Code and exempts the fixture skill from license headers.
| File | Description |
|---|---|
dokimi/tests/test_fidelity_golden.py |
Tests normalization, payload auditing, sealed flags, and golden comparisons. |
dokimi/tests/fixtures/fidelity/skill/sum-fast/SKILL.md |
Provides the fixed skill used during captures. |
dokimi/tests/fixtures/fidelity/README.md |
Documents the fidelity contract and refresh procedure. |
dokimi/tests/fixtures/fidelity/claude-code-2.1.318/skill.json |
Stores the skill-enabled request baseline. |
dokimi/tests/fixtures/fidelity/claude-code-2.1.318/bare.json |
Stores the no-skill request baseline. |
dokimi/scripts/refresh_fidelity_golden.py |
Captures and writes refreshed golden requests. |
dokimi/dokimi/harnesses/fidelity.py |
Implements recording, normalization, runners, and comparisons. |
dokimi/changelog.d/20261104_130000_napetrov_fidelity-golden.md |
Announces the fidelity check and tooling. |
.github/workflows/ci.yml |
Adds the pinned-client fidelity job. |
.github/.licenserc.yaml |
Exempts the measured fixture skill from header insertion. |
Review effort: Balanced
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #400 +/- ##
==========================================
+ Coverage 94.76% 94.79% +0.02%
==========================================
Files 352 355 +3
Lines 46407 46869 +462
Branches 7949 8013 +64
==========================================
+ Hits 43978 44429 +451
- Misses 1539 1548 +9
- Partials 890 892 +2
🚀 New features to boost your workflow:
|
…tras under the cap CI (ratchet, process and import-architecture tests) rejected the fidelity module: direct subprocess calls and a complexity-11 function with no baseline. Behaviour is unchanged for the golden; a client that times out now returns a timed-out Completed instead of raising. Refs intel#354 Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…claude binary The Tests jobs have no claude on PATH, so the golden test skips there and the changed lines were 55% covered (diff-cover gate 80%). These tests drive the recording server with a fake client, and check the docker and host command lines and the golden round trip. Refs intel#354 Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Skill listing: an excluded entry is dropped with its continuation lines. Operator reminders: only the reminder element or line is removed, other text in the block is kept. Billing version: the whole cc_version value is blanked. API version and beta headers are compared raw. The runners use agent_env(), so the sealed skill switches apply; the client launch is checked by the fake client. uid/gid are read only for a container capture. Goldens are retaken with the pinned claude 2.1.318 on the host. Thread: PRRT_kwDOVBg7g86z0vXW Thread: PRRT_kwDOVBg7g86z0vfQ Thread: PRRT_kwDOVBg7g86z0vek Thread: PRRT_kwDOVBg7g86z0vXd Thread: PRRT_kwDOVBg7g86z0vfh Thread: PRRT_kwDOVBg7g86z1Knb Thread: PRRT_kwDOVBg7g86z1Knk Thread: PRRT_kwDOVBg7g86z1rIf Thread: IC_kwDOVBg7g88AAAABi_eM7g Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Fixed in 83c816d: same finding as the thread above (test argv forwarding), same fix. |
|
Already fixed in 83c816d (napetrov): all three findings are on head, tested. No claim here: the review body is the bot's run marker, so there is nothing to answer in it. No claim here: the review body is the bot's run marker, so there is nothing to answer in it. No claim here: the review body is the bot's run marker, so there is nothing to answer in it. No claim here: the review body is the bot's run marker. Its two findings are the threads above. Not a blocker: codecov.yml makes the patch status informational. The two new branches of this PR have tests. |
A block whose skill listing had no kept entry was dropped whole, so the text after the listing never reached the comparison or the leak audit. Only the listing is removed now; the block is dropped when nothing else is left. Thread: PRRT_kwDOVBg7g86z7JFB Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
process.run reports a nonzero exit or a timeout as a result, and both runners discarded it, so a request sent before the client failed was recorded as a capture: the golden test could pass on it and the refresh could write it. Both runners now raise, with the client's last stderr line, unless it exited 0. Thread: PRRT_kwDOVBg7g86z7JFD Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRR_kwDOVBg7g88AAAABWE4iWA: already fixed in 83c816d Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
e5778b8 to
574b5f2
Compare
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 574b5f2. The recorder, the runners and the fixes for the earlier bot threads hold up: the three fixes I re-checked (continuation lines, text after an excluded-only listing, a client that fails after posting) are pinned by tests. The problems are in what the normalizer hides and in what its tests do not pin. Four things are worth fixing before merge, and one is a nit.
What I ran, on a Linux review host at 574b5f2:
make ci-localwith Docker: 35 passed, 2 failed, 7 skipped; 15,734 tests, 1 failed. The failedteststep is a timing test (test_unbalanced_quote_in_hook_completes_quickly, 1.35 s against a 1 s limit) in a file this PR does not touch. The failedInstall the pinned Claude Codestep is theci_local.pyfinding below. The changed-line coverage gate and the fidelity golden step (24 passed) pass.tests/test_fidelity_golden.pywith the pinnedclaude2.1.318: 24 passed.- Reproductions for the code findings, offline.
- A mutation run on
fidelity.py: 77 mutants, 35 of which pass the 22 tests that run without theclaudebinary. The ones that decide a verdict are in the inline comments.
Fidelity step in make ci-local runs a global npm install. ci_local.py skips only pip install lines (PIP_INSTALL) and has no entry for the new Install the pinned Claude Code step (.github/workflows/ci.yml:406). The step ran npm install -g @anthropic-ai/claude-code@2.1.318 here and failed with EACCES ... mkdir '/usr/local/lib/node_modules'. On a machine where the prefix is writable it replaces the contributor's global claude. Suggestion: in ci_local.py, treat the step like the pip ones (skip it and use a claude already on PATH, or add ("fidelity", "Install the pinned Claude Code") to NEEDS_NETWORK).
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 5323b53, which fix the nit from my review:
5323b53:.github/workflows/ci.yml:393: addedtimeout-minutes: 15afterruns-on:in the fidelity job.
make ci-local at 5323b53: 36 passed, 1 failed, 7 skipped.
Not approving yet. Still open: the two bugs and the two risks from my review.
Only the model the request sends is blanked. Other model ids in the system prompt, the beta header and the user agent stay raw, so a change there shows in the golden instead of being masked. Thread: PRRT_kwDOVBg7g860Cmwg Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…exactly A skill whose name only contains a kept name is no longer excluded, and text after the listing (such as a bullet list) is kept. Thread: PRRT_kwDOVBg7g860Cmwj Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…del left raw Residue now catches the run-specific forms it missed before: slash dates, month-year stamps, absolute paths under the host mounts, and e-mail addresses. A top-level model field that is not blanked is reported too. Thread: PRRT_kwDOVBg7g860Cmwo Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The fidelity job's install step runs npm install -g, which fails without a writable prefix and replaces a contributor's global claude where it succeeds. ci-local now skips that step and uses the claude on PATH. Thread: PRR_kwDOVBg7g88AAAABWMFqlQ Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> Thread: PRR_kwDOVBg7g88AAAABWNCXXA Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at 84b2024. The recorder, the runners and the fixes for the earlier threads hold up. Two things need a change before merge: the skill-listing filter hides a broken seal from the golden, and make ci-local now fails for anyone without claude 2.1.318. The rest is in the line comments.
What I ran, at 84b2024:
make ci-local --offline: 19 passed, 1 failed, 14 skipped. The failed row isfidelity / Run the fidelity golden: this host hasclaude2.1.303, and withDOKIMI_FIDELITY_REQUIRED=1both golden cases fail (see theci_local.pycomment). The other 28 tests oftest_fidelity_golden.pypass.- Offline repros on the committed goldens: two bundled skills added to the
skilllisting, and another model in the bare request, both normalize to the golden with an empty diff. external-catalog-smoke(docker-oracle,docker-probe) with Docker: both pass, every isolation probe closed.
Checked, no issues
- Earlier threads: the continuation-line, closing-tag, exact-name, billing-suffix, raw
anthropic-*header, failed-client and residue fixes are on this head, each with a test. - Arm argv:
arm_argvcallsDockerSolverHarness._claude_args, so the capture uses the flags the arm sends; the fake client checks the prompt, the plugin dir and the sealed skill switches. - ci-local: the global
npm install -gis skipped, andtest_host_install_entries_match_real_stepscatches a renamed step. - Changelog and licenses: the fragment is under
Addedand says how to refresh; new files have the SPDX header, and the fixture skill is exempt for a stated reason.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…laude is on PATH make ci-local skips the global install and uses the claude on PATH; with another version DOKIMI_FIDELITY_REQUIRED made both golden cases FAIL. The var is now dropped then, and the golden tests skip. Thread: PRRT_kwDOVBg7g860Mf-A Review: PRR_kwDOVBg7g88AAAABWRR13Q Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…side the module fidelity.py ships in the wheel and tests/ does not, so FIXTURE_SKILL pointed into site-packages. capture_arm now takes skill_dir; the test and the refresh script pass the fixture. Thread: PRRT_kwDOVBg7g860Mf-U Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The request's model is blanked by the normalizer, so an arm that dropped or changed --model normalized to the golden. capture_arm now checks body.model. Thread: PRRT_kwDOVBg7g860Mf-K Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The id was replaced as a substring (claude-haiku-4-5-20251001 became <MODEL>-20251001) and _MODEL_NAME took the period after the version. Golden retaken in debian:trixie-slim with 2.1.318: only that period changes. Thread: PRRT_kwDOVBg7g860Mf-g Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…rm runs container_agent sets CLAUDE_CONFIG_DIR=$HOME/.claude; the runners used $HOME/config, so the golden pinned a memory path the arm never sends. Golden retaken: only that path changes. Thread: PRRT_kwDOVBg7g860Mf-O Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… named operator host The normalizer dropped every listing entry but sum-fast on every capture, so a broken seal (bundled skills back in the listing) normalized to the golden. normalize_request now keeps the listing unless keep_skills is given, and capture_arm passes it only with operator_host=True (DOKIMI_FIDELITY_OPERATOR_HOST=1 in the test). Golden retaken: unchanged. Thread: PRRT_kwDOVBg7g860Mf98 Review: PRR_kwDOVBg7g88AAAABWRR13Q Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…th cases pass Thread: PRRT_kwDOVBg7g860Mf-b Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… ratchet) Thread: PRRT_kwDOVBg7g860Mf-A Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860Mf-U Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ment too (self-review) Thread: PRRT_kwDOVBg7g860Mf-A Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Both blockers fixed in ddd0dd0; goldens retaken, ci-local green. |
The step env is merged over the caller's environment, so a deleted DOKIMI_FIDELITY_REQUIRED that the contributor exported still reached pytest and the fidelity row failed after the notice said it would skip. The tests now export the var, as README step 2 asks contributors to. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…y on an operator host The pinned client sends a token-budget and a commit-attribution reminder in a clean container too, so stripping them always hid both from the diff and the leak audit. Goldens retaken; residue allows the reminder's fixed address. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
LGTM
I checked e00782f. All seven points from my first round are fixed, each with a test: the skill listing and the client's reminders are compared as sent, so a broken seal is a diff, and make ci-local no longer fails with another claude on PATH.
Merge: safe now: it merges cleanly into current main, 4 commits behind, and its tests, the growth ratchet and schema_compat pass on the merge. No open PR conflicts with it once it is in.
What I ran, at e00782f:
make ci-local --offline: 19 passed, 1 failed, 14 skipped. The failed row isschema_compat vs the PR base:mainaddedrun_id/dokimi_versionto the schemas in #387 after this branch's base, and this PR changes no schema. The fidelity row passes withclaude2.1.303 on PATH: the NOTICE setsDOKIMI_FIDELITY_REQUIRED=0, and the two golden cases skip.- My earlier repros on the new goldens: two extra skills in the listing give a diff in both cases; a request for another model is refused by
check_model;claude-haiku-4-5-20251001is kept andnamed <MODEL_NAME>.keeps its period. - On the merge with
main:test_fidelity_golden.py,test_ci_local.py,test_refresh_fidelity_golden.py,test_growth_ratchet.py,test_schema_compat.py: 208 passed, 2 skipped. - I could not run the pinned 2.1.318 client here (no npm); @xaleryb ran it on the host and in
debian:trixie-slim.
Checked, no issues
- Operator-host mode:
DOKIMI_FIDELITY_OPERATOR_HOST=1filters the capture and, throughoperator_view, the golden the same way; CI never sets it, and the refresh script cannot write a--hostcapture into the golden directory. - ci-local: the pin variable is set to
0, not deleted, so a value exported in the shell does not come back; the version comes from the job's own npm pin, and a renamed step failstest_pinned_claude_entries_match_real_steps.
antonwolfy
left a comment
There was a problem hiding this comment.
Review at e00782f. The changes since my last review hold up: the skill listing and the client's reminders are now compared as sent, the model is checked in capture_arm, the memory path matches the arm's, and --host needs --out. Three risks are left, all in the operator-host path and the refresh script. I'd fix them before merge.
What I ran, on a Linux review host at e00782f:
make ci-localwith Docker: 35 passed, 1 failed, 8 skipped; 15,708 tests passed. The changed-line coverage gate and the fidelity golden step pass. The failedRun testsstep istest_unbalanced_quote_in_hook_completes_quickly(2.4 s against a 1 s limit) plus two timeouts intest_audit_test_quality.py, all in files this PR does not touch.- Reproductions for the code findings, offline.
- A mutation run on
fidelity.pyandrefresh_fidelity_golden.py: 30 mutants, 8 of which pass the full fidelity test files. The ones that decide a verdict are in the inline comments.
Checked, no issues
- Operator-host view:
operator_view(golden)equals the operator-host normalization of a raw request with extra skills, a continuation line, a trailing bullet and the two reminders put back. - Model id: only whole ids are blanked (
claude-haiku-4-5-20251001is kept);residueflags a model field that is not<MODEL>. - ci-local: the pin variable is set to
0, a missingclaudeor an unreadable pin drops it, and the tests cover both. - Commits: every non-merge commit has a
Signed-off-by.
| return kept | ||
| # Every entry was excluded: the listing goes, but the text around it is the request's too. | ||
| rest = kept.replace(_SKILLS_HEADER, "", 1).strip() | ||
| return rest or None |
There was a problem hiding this comment.
An operator-host bare capture with extra skills keeps an empty <system-reminder> shell, so it never matches the golden.
When every listing entry is excluded, the block is cut down to the wrapper, and rest or None does not see it as empty. The guard at line 286 only runs when a reminder was removed.
- Input:
<system-reminder>\nThe following skills are available for use with the Skill tool:\n\n- dataviz: x\n- loop: y\n</system-reminder>,_keep_text(text, ("sum-fast",)). - Output:
'<system-reminder>\n\n\n</system-reminder>'.normalize_requestputs it intomessagesas its own text block. bare.jsonhas no listing block, sodiff_requests(capture, operator_view(golden))is non-empty on any host that has skills other thansum-fast. That is the case this mode exists for.
No test covers a listing that is only a wrapper: test_excluded_only_listing_keeps_the_text_around_it has loose text after it.
Suggestion: after kept = _keep_listing(...), return None when re.sub(r"</?system-reminder>|\s", "", rest) is empty, as line 286 does, and add a test with a wrapper-only listing.
|
|
||
| def operator_view(request: dict) -> dict: | ||
| """A normalized golden as an operator-host capture sees it: no reminders, the fixture's skill only.""" | ||
| return _strip_operator_extras(request, (FIXTURE_SKILL_NAME,)) |
There was a problem hiding this comment.
Operator-host mode is not pinned by any test that runs without the pinned claude, so two ways to break it pass the suite.
Mutation check, both pass test_fidelity_golden.py and test_refresh_fidelity_golden.py (40 passed):
operator_viewstrips with()instead of(FIXTURE_SKILL_NAME,). The existing test usesbare.json, which has no listing, so theskillgolden would lose itssum-fastentry on the golden side.capture_armline 551 askeep = None. No test setsoperator_host=True, so the flag can be ignored.
Only test_docker_claude_payload_matches_golden would catch either, and it skips without the pinned binary.
Suggestion: add a test that operator_view of skill.json keeps the sum-fast entry and drops the reminders, and a fake-client test that capture_arm(..., operator_host=True) removes an extra skill from the listing while operator_host=False keeps it.
| finally: | ||
| shutil.rmtree(scratch, ignore_errors=True) | ||
| dirty = residue(request) | ||
| if dirty: |
There was a problem hiding this comment.
Nothing tests the residue gate that keeps a dirty golden from being written.
Mutation check: if dirty: replaced by if False:, and its return 1 changed to return 0, both pass the full fidelity test files. The refresh tests stub capture_arm and never return a request with a date, path or raw model id. A refactor that drops the gate writes a dirty golden, and the golden's own residue test only fails later.
The call at line 67 is also unpinned: adding operator_host=True passes. That would write a golden with the reminders and the extra skills removed, and CI would no longer see a change in either.
Suggestion: add a test in test_refresh_fidelity_golden.py where the stub returns {"x": "/home/bob/a"}: assert main() returns 1 and writes no file. Assert that the stub received operator_host as false and skill_dir as the fixture.

Summary
dokimi/harnesses/fidelity.py: record, normalize, compare, capture one arm.scripts/refresh_fidelity_golden.py: retake the golden (clean container, pinned version).tests/test_fidelity_golden.py: normalization, operator-extras filter, golden pins, live audit, sealed argv flags, and the arm-vs-golden check (skips without the pinned binary, fails withDOKIMI_FIDELITY_REQUIRED=1).fidelity(afterstatic) installs@anthropic-ai/claude-code@2.1.318and runs the test.Refs #354.
Evidence
pytest tests/test_fidelity_golden.pygives 8 passed with the nativeclaude2.1.318 and with the npm build@anthropic-ai/claude-code@2.1.318(DOKIMI_FIDELITY_REQUIRED=1).ruff checkclean on the new Python files. Local skywalking-eyes header check flags no fidelity file.fidelityjob, first run pending.Merge Danger
Door: two-way. A CI job, a test and a golden; revert removes the check.
Refreshing the golden is a deliberate step:
tests/fixtures/fidelity/README.mdsays what to review in the diff.Blast Radius: CI. One new job per PR (npm install of a pinned CLI, one short local run, no model calls). A failure blocks merge until the golden or the arm is fixed.