macOS/arm64 ParaView artifact tier with clone-safe receipts - #28
macOS/arm64 ParaView artifact tier with clone-safe receipts#28aurascoper wants to merge 3 commits into
Conversation
Freeze expected hashes in render_manifest.json beside the renderers. verify_artifacts.py read derived_manifest.sha256 from beside the data, so a consistently tampered clone -- data altered, manifest and receipt regenerated -- passed all 209 hash checks and certified itself. Make the negative controls runnable rather than narrated: controls.py, 7 controls on APFS clones, each mutation asserting it changed exactly one site. Control 5 is the consistent tamper; it must pass unpinned or the hole it documents is not real. Pinned baselines: 16 and 20 checks, 0 failures. Both protected CSVs verified against HEAD, unmodified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1GLLCekStea7UHjRF51aw
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11a3553950
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if "--receipt" in sys.argv: | ||
| _rp = sys.argv[sys.argv.index("--receipt") + 1] | ||
| if os.path.isfile(_rp): |
There was a problem hiding this comment.
Refuse an unreadable state receipt
When --receipt names a missing file, this leaves RECEIPT as None and continues with the unpinned 18-check path, so a typo can produce a successful verification while silently dropping the frozen-manifest protection that this commit adds. Match verify_artifacts.py by failing closed whenever the option was supplied but could not be loaded.
AGENTS.md reference: AGENTS.md:L176-L184
Useful? React with 👍 / 👎.
| m = re.search(r'(<Property name="RGBPoints".*?)(9)(\b)', text, re.S) | ||
| assert m, "control could not locate the signal LUT upper bound in the state" | ||
| sub_once(state, re.escape(m.group(0)), (m.group(1) + "5" + m.group(3)).replace("\\", "\\\\"), expect=1) |
There was a problem hiding this comment.
Mutate the LUT upper-bound element specifically
For a normal serialized Viridis transfer function, this regex replaces the first standalone digit 9 anywhere after the first RGBPoints property, rather than selecting the signal LUT's final x-coordinate. The committed control log confirms that the alleged 0–5 mutation reloads as 0–0.004874, so the test fires after corrupting unrelated RGBPoints structure and does not prove that changing the actual upper bound is detected; locate the signal LUT and assert that its final coordinate became exactly 5.
AGENTS.md reference: AGENTS.md:L62-L68
Useful? React with 👍 / 👎.
| rc_u, nf_u, log_u = run_artifacts(c, None, wd) # unpinned: expected to PASS | ||
| rc_p, nf_p, log_p = run_artifacts(c, receipt, wd) # pinned: must FAIL | ||
| verdict = "FIRES" if nf_p else "DID-NOT-FIRE" |
There was a problem hiding this comment.
Require both consistent-tamper outcomes
The control's stated contract requires the unpinned run to have zero failures and the pinned run to fail, but the verdict consumes only nf_p. If the unpinned verifier later begins failing—or merely fails for an unrelated reason—this control still records FIRES and all_fired: true, so it no longer demonstrates the self-certification hole documented in the README; require nf_u == 0 as well as a nonzero pinned failure count.
AGENTS.md reference: AGENTS.md:L78-L85
Useful? React with 👍 / 👎.
| ck("Species_1to7 filter present", thr_sp is not None, thr_sp is not None) | ||
| ck("species threshold 1..7", thr_sp.LowerThreshold == 1 and thr_sp.UpperThreshold == 7, |
There was a problem hiding this comment.
Guard missing proxies before dereferencing them
When a state lacks Species_1to7, the presence check records a failure but the next line immediately dereferences None; Interior_mask_1 has the same ordering. The intended missing-filter abort below is therefore unreachable for these inputs, and the verifier exits with a traceback without writing its machine-readable failure receipt. Move all proxy-dependent checks after the existing None guard.
Useful? React with 👍 / 👎.
| m = _re.search(r"^threshold\s*=\s*([0-9.]+)", open(cfg).read(), _re.M) | ||
| if m and abs(float(m.group(1)) - THRESHOLD) > 1e-12: | ||
| raise SystemExit("FATAL: THRESHOLD=%s but %s declares %s" % (THRESHOLD, cfg, m.group(1))) | ||
| print("threshold reconciled against", cfg) |
There was a problem hiding this comment.
Refuse a threshold that was not parsed
When config.toml exists but the threshold is absent or written in a TOML-valid form outside [0-9.]+ (for example scientific notation), m is false and execution still prints that the threshold was reconciled before rendering every frame with the hard-coded 5.0 label. Require a successfully parsed threshold before claiming reconciliation, otherwise the movie and receipt can silently misstate the producer's declared endpoint.
AGENTS.md reference: AGENTS.md:L176-L184
Useful? React with 👍 / 👎.
…tated The LUT control fired for four commits without performing its stated mutation. Its regex (<Property name="RGBPoints".*?)(9)(\b) with re.S is leftmost-first and lazy, so in a 1024-element Viridis table it matched the `9` in <Element index="9"/> -- an index attribute, eight elements in -- and rewrote it to index="5", producing a duplicate index and destroying index 9. The array was mangled, the verifier read `signal LUT range 0..9 -> 0.0..0.004874`, and the control counted one failure and looked healthy. README.md reported `0.0..5.0`, which no stored run ever produced; the committed receipt said 0.004874 the whole time. It now mutates through the parsed document and asserts the element it changed was at x=9.0 before and x=5.0 after. sub_once asserts a count; a count cannot tell you the edit landed on the right element. Control 5 asserted half its contract. Its expectation is "unpinned PASSES, pinned FAILS" but the verdict read `"FIRES" if nf_p else ...`, so the unpinned result fed a display string and nothing else. A regression breaking the unpinned tier, or closing the hole the control documents, would have left it green. Now requires nf_u == 0 and nf_p. verify_state.py loaded its receipt only `if os.path.isfile(...)` with no else, and printed nothing about pinning, so a typo'd --receipt ran UNPINNED with output byte-identical to a deliberate unpinned run. It now shares verify_artifacts.py's contract: load, validate the four required pinned keys, or exit 2. Each run declares its mode -- pinned or self-consistency -- in the log and the receipt. Unpinned 18 checks, pinned 20. Two adjacent repairs. verify_state.py establishes every proxy's presence before reading any property on it; it used to read thr_sp.LowerThreshold immediately after the presence check and abort only later, so a missing filter raised AttributeError instead of reporting a failure. And animate_4d.py parses config.toml as TOML and refuses a missing or undeclared threshold -- its regex form did `if m and ...: raise` then printed "threshold reconciled" unconditionally, claiming a reconciliation it had not performed whenever the regex failed to match. 7 controls fire, 0 blocked. Baselines 16 and 20 checks, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1GLLCekStea7UHjRF51aw
The four ParaView-6.1.1-on-macOS issues and the absent libopenvkl dylib were stated in full in two files: here, and inline in docs/visualization/lattice_viewer.md on docs/phase2-macos-pipeline (#27), where they were inlined to repair a reference to a path that does not exist on that branch's base. Nothing linked the two copies and they had already drifted, "Preset is" against "The preset is" and "both views" against "the views", before either branch merged. The account stays in lattice_viewer.md and this file points at it. That direction, not the other one: lattice_viewer.md is already on feat/lattice-viewer, the base both branches share, so the pointer is at worst early. If this branch merges first the section is not there yet and the pointer does not resolve until #27 lands, which the text says plainly. A pointer the other way would have been to a file absent from #27's base entirely, which is the finding that started this. The heading here has read "macOS notes for the doc" since it was written. The notes have now gone to the doc. Left alone deliberately: render_manifest.json carries the same environment fact as a note field (make_render_manifest.py:126). A receipt recording the conditions it was produced under is not a third copy of the account, and editing frozen evidence to tidy prose would be the wrong trade. No receipt hashes any .md, on this branch or at the stack tip, so nothing was invalidated. verify_artifacts.py --receipt render_manifest.json still reports 16 checks, 0 failures, with all four frozen pins matching. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1GLLCekStea7UHjRF51aw
First ParaView read of this exporter's output on Apple Silicon, plus the artifact-provenance tier around it. Docs-only; scripts and receipts, no
.vti/.png/.mp4/evidence.The hole this closes
verify_artifacts.pyreadderived_manifest.sha256from beside the data. A clone tampered consistently — bit flipped, manifest regenerated, receipt regenerated — passed every check, because the manifest it was checked against was the one the tamperer rewrote. Reproduced: one bit flipped insignal_mcs000050.vtiat byte 1506123, both manifest files regenerated, yields 0 of 209 artifact hashes altered and "manifest matches its own receipt: True".make_render_manifest.pywritesrender_manifest.jsonbeside the renderers, freezingderived_manifest_sha256,parent_manifest_sha256,pvd_sha256and the artifact count, alongside the rendering recipe (camera, slice as a full numeric coordinate, threshold predicate, adjacency rule, LUT ranges, ray-tracing status).--receiptpins both verifiers to it.The pin that catches it is not the obvious one. That tamper leaves the
.pvdbyte-identical, so pinning the.pvdalone misses it entirely. Only the pinnedderived_manifest_sha256bites.Controls are runnable, not narrated
controls.py— seven controls on APFS copy-on-write clones, 12 s total, each mutation assertingn == 1. Control 5 is the consistent tamper and is the only one whose expected result includes a pass: unpinned it must pass, or the hole it documents is not real.verify_artifacts.pyverify_state.pycontrols.py --with-stateTwo things deliberately not presented as controls
ParaView 6.1.1 does not serialise
EnableRayTracinginto a.pvsmat all — verified by saving one state at the default 0 and another explicitly at 1 and grepping both. A reloaded state always reports 0 regardless of how frames were rendered, so that assertion is kept and relabelled as documentation of an invariant. The render-time value is the evidence and lives inbuild_report.json.Protected CSVs are recorded by
git hash-objectagainstHEAD:, not as a before/after pair — a before/after pair only proves this pipeline was innocent.The macOS pvpython account lives in one place now (
6951f29)The four ParaView-6.1.1 issues and the absent
libopenvkl_module_cpu_device.dylibwere statedin full both here and inline in
docs/visualization/lattice_viewer.mdon #27, where they wereinlined to repair a reference to a path that does not exist on that branch's base. Nothing linked
the two copies and they had drifted before either branch merged, "Preset is" against "The preset
is" and "both views" against "the views".
The account now lives in
lattice_viewer.mdand the section here points at it. That directionbecause
lattice_viewer.mdis already onfeat/lattice-viewer, the base both branches share,so the pointer is at worst early: if this branch merges before #27 the section is not there yet
and the pointer does not resolve until #27 lands, which the text says plainly. A pointer the
other way would have been to a file absent from #27's base entirely, which is the finding that
started this. The heading here has read "macOS notes for the doc" since it was written.
render_manifest.jsonkeeps the same environment fact as anotefield(
make_render_manifest.py:126) and is left alone: a receipt recording the conditions it wasproduced under is not a third copy of the account, and editing frozen evidence to tidy prose
would be the wrong trade. No receipt hashes any
.md, here or at the stack tip, so nothing wasinvalidated:
verify_artifacts.py --receipt render_manifest.jsonstill reports 16 checks, 0failures, all four frozen pins matching.
🤖 Generated with Claude Code
https://claude.ai/code/session_01V1GLLCekStea7UHjRF51aw