Add the slab depth geometry, and two verifiers that can fail - #19
Add the slab depth geometry, and two verifiers that can fail#19aurascoper wants to merge 13 commits into
Conversation
Geometry now enters radiodialysis_rhs() only through face weights built in the parameter constructor: w+- = (r +- dr/2)/r cylindrical, 1 for the slab. The axis/substratum node is untouched, and the cylindrical path is byte equivalent to the previous inline stencil (residual 2.28e-16). slab_parms() declares three semantics rather than inheriting them (rule 4): the Robin coefficient is k_L and not P_eff, radiation is off because no membrane exists at a biofilm/liquid interface, and D_eff is molecular rather than the reactor-scale dispersion value. X_total has no default and stops, naming RADIODIALYSIS: BLOCKED, because lambda ~ 1/sqrt(U) puts "is any gradient resolvable" downstream of that gate. Two independent verifiers, both against c(z) = c_b cosh(z/lam)/cosh(L/lam): verify_biofilm_depth_profile.R the shipping R, 13 checks verify_biofilm_depth_profile.py a numpy re-coding of the same scheme They agree to the digit on the shared quantity (max rel err 2.31e-07 at N=40), so agreement rules out a shared transcription error. Second order confirmed by refinement ratio 4.21 -> 4.05 over N=20..160 at both phi=0.306 and phi=3.7, the tolerance being on the normalised constant C rather than a raw error, since C is N-independent and a fixed bound cannot serve both regimes. Negative controls, per rule 1. Cylindrical weights run against the slab solution must fail the slab bound, and do by 3 to 5 orders. The R harness mutates face_weights (swapped, perturbed 0.1%, forced to 1) and requires each mutant caught, gated on a clean baseline first, because a wrong reference makes every mutant read as caught. That gate is itself a passing check, so it is structural rather than remembered. CI asserts on the receipt, not the exit status: parse() is not execution and was green over code that ran nowhere. A verifier that exits 0 having skipped its deSolve check is the all-pass-over-skip bug moved into YAML, so the job reads checks_run/failures/skipped out of the JSON and fails on a count that drifts from EXPECTED_CHECKS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7b10905a8
ℹ️ 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".
The apt step exited 0 and deSolve was still missing: setup-r@v2 provides its
own R build reading R_LIBS_USER, while r-cran-desolve installs into the
distro R's site-library, which that R never looks at. CI reported
PASSED WHAT RAN -- NOT A CLEAN RUN (11 checks run, 0 failures, 1 skipped)
UNCOVERED: time integration (deSolve not installed)
and exited 1, so the job went red rather than green over an uncovered
time integration. That is rule 2 behaving correctly; the bug is upstream of
it, in the install.
Install through R instead, with use-public-rspm for a binary rather than a
Fortran source build. stopifnot() on the namespace because install.packages()
only WARNS on an unavailable package, which would hand the verifier the same
silent absence one step later (rule 3).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review New since your last pass on Please review that commit specifically, plus whether the receipt assertion in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23d4533707
ℹ️ 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".
P1, .github/workflows/model-contracts.yml. The receipt assertion regex-
scraped three counters and never read the producer's own completeness
declaration, so {"checks_run":14,"failures":0,"skipped":0,"complete":false}
satisfied every check. A gate accepting a self-declared incomplete run is
rule 3 in the one place whose whole job is to refuse that. Now parsed with
jsonlite, `complete` required and isTRUE, counters required to be integers,
and a missing key refused by name. Exercised against seven fixtures: valid
passes, and complete=false, absent complete, count drift, a skip, a failure,
truncated JSON and an absent file each refuse with their own message.
P2, biofilms_radiodialysis.R. `if (geom == "slab") k_L else P_eff` read every
unrecognised value as cylindrical. face_weights() validates, but the weights
reach radiodialysis_rhs() precomputed in parms, so nothing validated there.
Now switch() with an unnamed stop() default. Check 14 asserts both halves:
an unknown geom errors, and both supported values still run, since a dispatch
refusing everything would satisfy the first half alone. Confirmed to bite by
restoring the if/else, which fails it at refused=FALSE with both positive
halves still TRUE.
P2, the numpy re-coding ran nowhere. It and its negative controls could
regress without failing any check, while the PR leaned on cross-implementation
agreement. Now run in radiodialysis-stability, which already installs numpy,
and receipt-asserted on case count and per-case verdicts for the same reason
as the R one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b31faa601b
ℹ️ 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".
Two of Codex's three on b31faa6. The third (X_red) is a modelling decision and is left open. P2, receipt counters. as.integer() coerces rather than validates, so {"checks_run":"14","failures":false} certified as complete and negative counters passed every `> 0L` comparison. int1() now requires a non-negative whole-number numeric scalar and names the field it refused. Fixtures: valid passes; string, boolean, negative, fractional and array counters each refuse at their own field. P2, numpy case identities. Counting three cases accepted three copies of default_regime, and a case whose checks were deleted has all([]) == True and reports passed -- the can't-fail shape one level down. Moved to scripts/assert_numpy_receipt.py, which pins the case names AND the check names within each case, and carries --self-test running six doctored receipts (case dropped, duplicated, checks deleted, swap control flipped, case renamed, overall pass false). CI runs the self-test BEFORE the real assertion, so a gate that has gone blind fails the build ahead of the thing it is meant to be guarding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex P1 on #19. X_red was fixed at 0.3 while X_total was caller-declared, so U did not scale with the declared basis, and at a corrected low X_total the reducing mass exceeded total biomass. The fraction reading was always the intent and was never implemented: the constructor comment said "metal-reducing fraction (Shewanella proxy)", the Shiny slider is labelled "fraction" with min 0 max 1, and docs/research/radiotrophic_calibration_map.md:395 already writes the contract as X_red = f_red,dry * X_total. Only the arithmetic disagreed. U = X_total * (k_ads + k_red * f_red_active), f_red_active in [0, 1] in one helper, uptake_rate_of(), used by both radiodialysis_rhs() and penetration_depth(), so the multiplication exists once. Two consequences are now structural rather than asserted: X_red <= X_total by construction, and U is proportional to X_total, so the whole uptake term inherits that gate instead of needing a second one. Check 15 asserts all three halves -- U proportional to X_total, lambda ratio sqrt(10) between X_total 1.0 and 0.1, and a fraction outside [0,1] refused (1.5, -0.1, NA, a vector, a string). Confirmed to bite by restoring the additive form, which fails it at U(0.1)/U(1)=0.196 and lambda ratio 2.256 against sqrt(10)=3.162. Nothing shipping moves: at X_total = 1.0 the arithmetic is identical, and the cylindrical byte-equivalence regression still reads 2.28e-16. ON PROVENANCE, since it decides what 0.3 means rather than only where it multiplies. It is NOT 2/7 of the seven species: the coupled path counts one, S. oneidensis (biofilms_potts.jl:1377), and 0.3 traces to 45de4ba with no recorded basis. It is labelled a TAXONOMIC proxy standing in an ACTIVITY slot, which active_from_taxonomic() refuses without a measured activity fraction. So 0.3 stays an unvalidated placeholder gated by RADIODIALYSIS: BLOCKED, and the comment says so. Suites: coupling 310 passed 6 skipped, calibration 360, contract 7, R verifier 15/15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review
Worth re-checking: whether any path still reaches |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a7d59260a
ℹ️ 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".
Three of Codex's four on 6a7d592. The fourth (the Julia coupled solvers) is left open; it is entangled with the gate. X_total was unvalidated. The structural bound f*X_total <= X_total assumes a finite non-negative scalar, and a hand-built parms list with X_total = -1 reached both callers, returned a negative uptake, and broke the bound the helper is cited for. A guard checking one of two operands proves nothing about their product. Both are validated now, and check 15 exercises malformed totals (-1, NA, Inf, -Inf, a vector, a string) alongside the fraction ones. String verdicts were certified as passing. JSON "false" is a non-empty string and therefore truthy, so `not ok` accepted a producer schema regression whose every verdict was the string "false". Verdicts must now be boolean true (`is not True`), and two doctored receipts covering it joined the self-test, which now runs eight. The gate's own script was outside the workflow's path filters, so a pull request changing only scripts/assert_numpy_receipt.py ran none of its negative controls. scripts/** added to both filters -- a gate that does not run when it changes is the same defect one level out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex's P1 on 6a7d592, resolved by refusing rather than by conforming. The Julia coupled path was blocked only by a side effect: the pre-fraction additive uptake happens to misbehave at non-unit X_total, and that was doing the work of a guard. A tripwire any tidy-up can remove is not a gate. So step_radiolysis! -- the entry point both ports share -- now refuses outright, naming the occupancy-to-fraction defect. The arithmetic is deliberately NOT made conformant. Mirroring R's uptake_rate_of() here would make this path conformant without making it correct: the X_red reaching it is red_cells[i]/counts[i], one species' occupied sites over all interior sites, which README.md:344 records as neither a biomass fraction nor a reducer fraction. That would turn a visible defect into an invisible one. The additive form stays as a marker that the reconciliation is unfinished. WHAT THIS TURNED UP. validate_serial.jl steps the coupled path at X_total = 0.065 -- a site-occupancy mean, exactly the gated quantity -- so enforcing the block broke the CSV determinism fixture. Its recorded columns do not depend on the gated basis: the CSV is CPM quantities plus rd.m, whose ODE (dm/dt = -k_dam*Ddot_R*m) contains neither X_total nor X_red. So the gate takes one narrow acknowledgement, basis_gate_ack, for runs that compare blocked output against blocked output and assert no magnitude. Three sites hold it, not one: validate_serial.jl, and the genealogy and checkpoint tests, which read rd.c/rd.s only to compare two code paths or a round trip. Two guards keep that honest. A census test enumerates the sites, so a fourth fails the build rather than appearing quietly. And an independence test runs the harness with the uptake constants at 10x and requires every recorded column byte-identical -- the day anything c- or s-derived is recorded, the exemption's claim fails with it. The gate test asserts the asymmetry rather than agreement: R scales with the basis (check 15), this path refuses. Confirmed to bite by deleting the guard call, which fails it six times. Bool not Symbol: the field is HDF5-serialised by export_checkpoint.jl and has to survive a restart. Julia 2/34/30/68/42/9/14/22, R 15/15, numpy 3/3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Enforcing the gate surfaced two more coupled call sites, and the census test is what surfaced them rather than a CI failure nobody read. export_checkpoint.jl's CLI stepped the coupled path without the acknowledgement, so `export_checkpoint.jl transport` exited 1 and took coupling/tests/test_julia_interop.py with it. It is a fourth ack site, and unlike the other three it is not a comparison: export_restart_checkpoint writes rd/c and rd/s into an interchange file that other tools read. So the file declares it (rule 4). rd/basis_gate_blocked and rd/basis_gate_note now travel beside the arrays, because a consumer cannot tell a gated basis from a sound one by looking at a Float64 vector. Asserted in both directions, since a marker that is always true is as uninformative as one that is always false. Getting the false direction honest turned up the sharper fact: advance_window! reconstructs the params with X_total = mean(compute_radial_biomass(...)), so an advanced coupled simulation can NEVER export an ungated basis. The unblocked case exists only at zero MCS. The test says so rather than papering over it with a hand-set X_total that the first step would overwrite. The transport snapshot needed the ack only to step; it exports lattice, fields and dose and carries no radiodialysis array, so it gets no marker. Julia 2/34/30/68/48/9/14/22, coupling test_julia_interop 2/2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review
The arithmetic in both Julia ports stays non-conformant deliberately: the What your finding exposed is that the block was never enforced — the additive form merely happens to misbehave at non-unit Worth re-checking:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c93d1ef91
ℹ️ 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".
Both of Codex's on 2c93d1e, and both were defects in guards I wrote. PROVENANCE. Inferring "gated" from X_total != 1.0 fails two ways. A coupled state can produce mean(X_tot) == 1.0 legitimately -- every sampled interior site occupied -- colliding with the standalone default and slipping the gate. And c/s are path-dependent, so a run that stepped on an occupancy basis stays gated even after X_total returns to 1.0, while the exported marker would have said false. Rule 4: the producer that installs the basis declares it. RadiolysisState carries basis_from_occupancy, set at each of the three installation sites and never cleared, serialised into the restart file and restored rather than recomputed. The gate and the export marker both read it. Four tests: the collision, the stickiness, a genuinely standalone state still allowed, and -- because the first two set the flag by hand -- that the real coupled loop sets it, so the guard is not protecting a field nobody writes. Confirmed to bite: dropping the provenance term fails two. CENSUS. It grepped for the literal "basis_gate_ack = true", so `basis_gate_ack=true`, extra spacing, a line break or a field assignment all opened the gate with the census still green. It asserted a bound it could not enforce, which is the shape it exists to catch. It parses now, walking the AST for assignments in any spelling, and distinguishes OPENING from PROPAGATION: the reconstruction sites forward `basis_gate_ack = rp.basis_gate_ack`, which justifies nothing new, while a literal true or a value it cannot trace does. Nine fixtures prove it catches each bypass and does not fire on the guard's own comparison, on prose, or on propagation. Confirmed to bite by adding a site in the no-space form. Its scope is stated rather than implied: this is static, so a runtime value splatted in via `RadiolysisParams(; kw...)` is not detectable and the test says so instead of claiming a bound it does not have. Julia 2/34/51/68/48/9/14/22. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review
Provenance is now declared at the installation site and sticky, serialised and restored rather than recomputed, so neither the One thing I did not close, stated in the test rather than papered over: a runtime value splatted through |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 207f2925fe
ℹ️ 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".
Codex on 207f292. `RadiolysisParams(; basis_gate_ack)` passes a same-named binding as a bare Symbol under an AST :parameters node, so a walker examining only :kw and :(=) never counted the site. The distinction matters and Codex drew it: the field name is STATICALLY PRESENT here, so this is not the runtime-splat residual the last commit disclaimed. I had scoped the limitation to values I could not see, and this one was in plain sight. Four fixtures, including Codex's own `function build(basis_gate_ack); RadiolysisParams(; basis_gate_ack); end`. Confirmed to bite by adding a real site in that form, which the census names. The :parameters branch errs toward NAMING a site: a keyword parameter merely declared with this name fires too. A census that fails loudly on something it cannot classify is right in the direction rule 3 asks for. Julia 2/34/55/68/48/9/14/22. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f49fe94051
ℹ️ 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".
Two of Codex's three on f49fe94. The third (the census collapsing sites to filenames) is deferred to an issue. MISSING PROVENANCE IS UNKNOWN, NOT FALSE. restore_restart_checkpoint defaulted absent rd/basis_from_occupancy to false, which is rule 3 inside a gate: a checkpoint written before the field existed carries no provenance, and if its occupancy-derived X_total happened to land on 1.0 the restored state read as standalone and resumed integrating gated c/s. It now refuses and makes the caller establish the basis, the same shape as the existing allow_version_mismatch. Six assertions: the refusal, both explicit declarations honoured, and a current checkpoint still restoring untouched, so the refusal is not simply blocking everything. HYBRID PARAMETER LISTS. bc_coef dispatched on geom while the interior rows consumed whatever face weights parms carried, and nothing tied the two together. Codex's example: take a default_parms() result, set geom = "slab", supply k_L, and get a slab boundary over cylindrical diffusion -- a plausible operator rather than a refusal. This is the same defect as the original bc_coef finding one layer down; validating half a contract proves nothing about the other half. The weights are now validated against face_weights(r_grid, geom) rather than recomputed, because recomputing would discard what the caller wrote and hide the mistake instead of naming it. geom is checked first so an unrecognised value still gets the named refusal rather than face_weights()'s generic one. Check 16 covers both directions and a single corrupted weight, not only a wholesale swap, and requires both presets to still run. Confirmed to bite by deleting the guard: all three bad lists go FALSE while presets_still_run stays TRUE. R 16/16, Julia 2/34/55/68/54/9/14/22, numpy 3/3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GATE-06. scripts/preflight_merge.sh decides on select(.isResolved | not) and
nothing else, so resolved-because-fixed and resolved-because-deferred produce
one signal. Demonstrated on this PR: thread PRRT_kwDONEeyC86dXE6w ("Count
acknowledgement sites rather than files") is resolved as deferred to #21, not
fixed, and the gate now reads that as addressed. Filed as #22, with a register
that only downgrades an OPEN thread so `resolved` reverts to meaning one thing
and an unverifiable deferral refuses by construction rather than by an else
branch.
Verdict restate, not requalify: the claim becomes false as written rather than
true under a narrower scope, which is why GATE-01 used restate when the set of
blocking cases changed. The row says two unflattering things on purpose --
that #22 would be the first gate change to make it refuse LESS, and that unlike
GATE-01..05 this was not caught by external review, so it should not be dressed
as a catch.
CLAUDE.md gains a fourth section. Two commits landed on the wrong branch in one
session on 2026-08-28: 0af4505 appeared on feat/slab-depth-geometry mid-session,
and fix/ledger-guard-document-scope was checked out under the session so census
commit 5b100d7 landed there instead, recovered as f49fe94. Both times
`git push origin feat/slab-depth-geometry` printed "Everything up-to-date" while
HEAD was a commit ahead, because the branch named in the push was not the branch
checked out -- truthful and useless.
A mitigation already existed, in a plan scoped to one unrelated piece of work,
and a worktree was even open at the time (Biofilms-v11). The tooling was in use
and the practice was not, which is the same shape as the pipefail rule living in
one script while every instance of the defect was typed by hand. It is standing
practice now, and carried to other repos by a user memory.
test_claims_ledger.py 15 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review This pass is reviewing a deferral, and that is the point. What changed since
Worth checking specifically:
|
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review Re-requesting: What is unreviewed:
Worth checking: whether the PR is otherwise at zero unresolved threads with all 14 checks green. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3daef5f848
ℹ️ 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".
Codex on 3daef5f, and it is pointing at the rule itself. The closing directive said to read the branch "in the same snapshot as the commit -- `git status -sb` alongside `git log -1`, one command". Those are two git invocations. Joining them with `&&` sequences them; it does not make them atomic. Another process moving HEAD between the two -- the exact scenario the section describes -- lets `git status -sb` report the old branch while `git log -1` reports the new commit, so the check can falsely pair a commit with the intended branch. One invocation that actually is one: git status --porcelain=v2 --branch reporting branch.oid and branch.head together from a single read. The paragraph now says that an earlier draft of itself prescribed the two-command check, because a rule that quietly corrects its own history teaches less than one that says where it was wrong. The same correction is applied to the agent-sessions-use-worktrees memory, which carried the same line. Worth recording plainly: this section was written in the same session that used `git status -sb; git log --oneline -1` as its verification pattern, and neither the writing nor the using noticed. External review did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stacked on #12: this needs
e4021c0(the P_eff withdrawal), which is not onmaster. Base retargets tomasteronce #12 lands.radiodialysis_rhs()now supports biofilm depth as well as reactor radius. Geometry enters through face weights alone:w± = (r ± dr/2)/rcylindrical,1for the slab. The axis/substratum node is untouched, since the reflected ghostc₀ = c₂is identical for the L'Hôpital limit and the no-flux substratum.This is the only geometry that can be compared with micron-scale in-film measurements. The cylindrical preset lumps the biofilm into a uniform scalar sink over a 1 cm radius.
What
slab_parms()declares rather than inheritsPer rule 4, three semantics are stated by the producer:
k_L, an external liquid-film mass-transfer coefficient, notP_eff.P0andalpha_Pare absent from the preset, not defaulted.e4021c0withdrewP_efffrom every producer for carrying a fabricated unit and it must not re-enter by inheritance.k_dam = Ddot_R = 0andm(t)stays 1. There is a check on that.D_effis molecular. The cylindrical preset's1e-3 cm² s⁻¹is ~100x the self-diffusivity of water: a reactor-scale dispersion coefficient, meaningless on a 100 µm domain.X_totalhas no default andstop()s namingRADIODIALYSIS: BLOCKED. λ ~ 1/√U, so whether any gradient is resolvable acrossL_fis downstream of that gate. A caller must state the value and label it a test value.Verification
Two independent codings, both against
c(z) = c_b cosh(z/λ)/cosh(L/λ):verify_biofilm_depth_profile.Rlsodatransient, parameter gate. 13 checksverify_biofilm_depth_profile.pyThey agree to the digit on the shared quantity — max rel err 2.31e-07 at N=40 — which rules out a shared transcription error in a way neither does alone. Keep both.
Second order, tolerance on the normalised constant
C = err·(1/h)²rather than a raw error, since a fixed bound either fails a strong gradient or is vacuous for a weak one:Cholds at 0.0038 and 0.1540 to four digits across an 8x refinement. Ratios approach 4 from above. Per rule 5 the ladder moves sampling only; φ is fixed within each column.k_eff/U = 1/6exactly, soλ_steady/λ_transient = √6independent ofX_total. Checked as a length ratio, which an implementation that got only the sink right would fail.Negative controls
Rule 1, on every guard here:
face_weights: swapped,w_plus[5]perturbed 0.1%, forced to slab. Each must be caught, and each is (3.91e-01, 4.40e-04, 1.96e-01).biofilms_potts.jl:1153-1163. The slab assertions cannot protect it: therew_plus == w_minus == 1, so a weight swap is invisible, and a mutation test confirmed exactly that.CI
parse()is not execution. It passes on a file whose every numerical result is wrong, so that job was green over code that ran nowhere. The workflow now installsdeSolve, runs the R verifier, and asserts on the receipt rather than the exit status: a verifier that exits 0 having skipped itsdeSolvecheck is rule 2 moved from R into YAML. The job readschecks_run/failures/skippedfrom the JSON and fails on a count that drifts fromEXPECTED_CHECKS, so silently losing a check breaks the build.Local receipt:
{"checks_run": 13, "failures": 0, "skipped": 0, "complete": true}.Scope
No published number moves, so there is no ledger row. The
X_totalvalues in both verifiers are test values chosen to exercise the numerics; no profile number here is a claim about a biofilm, andc(0)/c(L)in particular moves with the biomass basis. The transient is covered in R vialsodabut not in the numpy file. Whether a gradient is measurable is untouched and stays downstream ofRADIODIALYSIS: BLOCKED.Reference D stays
NOT_EVALUATED,CAMPAIGN_READYstays no, δ stays unset. Nothing here approaches any of them.🤖 Generated with Claude Code