Skip to content

Add the slab depth geometry, and two verifiers that can fail - #19

Open
aurascoper wants to merge 13 commits into
fix/codex-review-round-twofrom
feat/slab-depth-geometry
Open

Add the slab depth geometry, and two verifiers that can fail#19
aurascoper wants to merge 13 commits into
fix/codex-review-round-twofrom
feat/slab-depth-geometry

Conversation

@aurascoper

Copy link
Copy Markdown
Owner

Stacked on #12: this needs e4021c0 (the P_eff withdrawal), which is not on master. Base retargets to master once #12 lands.

radiodialysis_rhs() now supports biofilm depth as well as reactor radius. Geometry enters through face weights alone: w± = (r ± dr/2)/r cylindrical, 1 for the slab. The axis/substratum node is untouched, since the reflected ghost c₀ = 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 inherits

Per rule 4, three semantics are stated by the producer:

  • The Robin coefficient is k_L, an external liquid-film mass-transfer coefficient, not P_eff. P0 and alpha_P are absent from the preset, not defaulted. e4021c0 withdrew P_eff from every producer for carrying a fabricated unit and it must not re-enter by inheritance.
  • Radiation is off. No membrane exists at a biofilm/liquid interface, so k_dam = Ddot_R = 0 and m(t) stays 1. There is a check on that.
  • D_eff is molecular. The cylindrical preset's 1e-3 cm² s⁻¹ is ~100x the self-diffusivity of water: a reactor-scale dispersion coefficient, meaningless on a 100 µm domain.

X_total has no default and stop()s naming RADIODIALYSIS: BLOCKED. λ ~ 1/√U, so whether any gradient is resolvable across L_f is 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/λ):

covers
verify_biofilm_depth_profile.R the shipping R: assembled operator, steady profile, lsoda transient, parameter gate. 13 checks
verify_biofilm_depth_profile.py a numpy re-coding of the same scheme, no R required

They 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:

N φ=0.306 err φ=3.7 err ratio p
20 9.752e-07 5.831e-03
40 2.315e-07 1.385e-03 4.213 2.075
80 5.641e-08 3.377e-04 4.103 2.037
160 1.393e-08 8.338e-05 4.051 2.018

C holds 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/6 exactly, so λ_steady/λ_transient = √6 independent of X_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:

  • Cylindrical weights run against the slab solution must fail the slab bound, and do, by 3 to 5 orders (2.30e-02 vs bound 1.84e-05; 1.31e+00 vs 2.70e-03). Without this the suite would pass on a stencil that ignored geometry entirely.
  • Mutation controls on 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).
  • The baseline gates the controls. A wrong reference makes every mutant read as caught, which is what a harness measuring nothing looks like. It happened here before: an omitted Robin term put the baseline at 9.8e-01 and three mutants read as caught. So the baseline must be clean (2.28e-16) before any mutant verdict is honoured, and "the baseline gate itself fires on a broken reference" is its own passing check rather than a remembered convention.
  • The cylindrical path is byte-equivalent to the previous inline stencil (2.28e-16 R, 1.41e-15 numpy), which protects its cross-validation against biofilms_potts.jl:1153-1163. The slab assertions cannot protect it: there w_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 installs deSolve, runs the R verifier, and asserts on the receipt rather than the exit status: a verifier that exits 0 having skipped its deSolve check is rule 2 moved from R into YAML. The job reads checks_run / failures / skipped from the JSON and fails on a count that drifts from EXPECTED_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_total values in both verifiers are test values chosen to exercise the numerics; no profile number here is a claim about a biofilm, and c(0)/c(L) in particular moves with the biomass basis. The transient is covered in R via lsoda but not in the numpy file. Whether a gradient is measurable is untouched and stays downstream of RADIODIALYSIS: BLOCKED.

Reference D stays NOT_EVALUATED, CAMPAIGN_READY stays no, δ stays unset. Nothing here approaches any of them.

🤖 Generated with Claude Code

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread biofilms_radiodialysis.R Outdated
Comment thread .github/workflows/model-contracts.yml
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

New since your last pass on e7b1090: commit 23d4533 fixes the CI install step. apt-get install r-cran-desolve exited 0 while leaving deSolve absent, because setup-r@v2 runs its own R reading R_LIBS_USER and the apt package lands in the distro R's site-library. Now installed through R with use-public-rspm, and stopifnot(requireNamespace(...)) because install.packages() only warns on an unavailable package.

Please review that commit specifically, plus whether the receipt assertion in model-contracts.yml can still be satisfied by a run that did not do the work.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread .github/workflows/model-contracts.yml Outdated
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

b31faa6 closes all three, including your P1 on the gate itself. Replies are on each thread. Specifically worth re-checking:

  • The receipt assertion now parses with jsonlite and requires isTRUE(complete). Exercised against seven fixtures (valid, complete=false, absent complete, count drift, a skip, a failure, truncated JSON, absent file), each refusing with its own message. Is there still a receipt shape that satisfies it without the work having been done?
  • radiodialysis_rhs() now dispatches with switch() over an unnamed stop() default, asserted in both directions by check 14.
  • The numpy verifier runs in radiodialysis-stability and is receipt-asserted on case count and per-case verdicts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread biofilms_radiodialysis.R Outdated
Comment thread .github/workflows/model-contracts.yml Outdated
Comment thread .github/workflows/model-contracts.yml Outdated
aurascoper and others added 2 commits August 28, 2026 20:54
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

6a7d592 closes your P1. X_red is now derived: U = X_total * (k_ads + k_red * f_red_active) with f_red_active in [0,1], in one helper shared by radiodialysis_rhs() and penetration_depth(). X_red <= X_total is structural, and U inherits the X_total gate rather than needing a second one. Check 15 asserts scaling, the √10 λ ratio, and refusal outside [0,1]; it fails on the restored additive form.

Worth re-checking: whether any path still reaches uptake without going through the helper, and whether the [0,1] validation can be bypassed by a hand-built parameter list.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread biofilms_radiodialysis.R
Comment thread biofilms_radiodialysis.R Outdated
Comment thread scripts/assert_numpy_receipt.py Outdated
Comment thread .github/workflows/model-contracts.yml
aurascoper and others added 3 commits August 28, 2026 21:18
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

301951a and 2c93d1e address your P1 by refusing rather than conforming, on the maintainer's call.

The arithmetic in both Julia ports stays non-conformant deliberately: the X_red reaching them is one species' occupied sites over all interior sites, so mirroring R's derivation would make them conformant without making them correct, turning a visible defect into an invisible one.

What your finding exposed is that the block was never enforced — the additive form merely happens to misbehave at non-unit X_total, and that side effect was standing in for a guard. step_radiolysis! now refuses outright, and the exemption is bounded by a census test.

Worth re-checking:

  • Can the gate be reached by any path that does not go through step_radiolysis!?
  • The ack census greps for basis_gate_ack = true — can a site open the gate without matching that literal (aliasing, a computed value, a struct copy)?
  • rd/basis_gate_blocked is derived from X_total != 1.0 at write time. Is there a state where the arrays are gated but that expression is false?

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread biofilms_potts.jl Outdated
Comment thread tests/radiodialysis_basis_gate.jl Outdated
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

207f292 fixes both of yours on 2c93d1e, and both were defects in guards I wrote.

Provenance is now declared at the installation site and sticky, serialised and restored rather than recomputed, so neither the mean(X_tot) == 1.0 collision nor the path-dependence of c/s slips it. The census parses instead of grepping, and distinguishes opening from propagation.

One thing I did not close, stated in the test rather than papered over: a runtime value splatted through RadiolysisParams(; kw...) is not statically detectable, so the census establishes only that no site opens the gate by writing the field directly, in any spelling. Tell me if you think that residual is worth a runtime counter.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread tests/radiodialysis_basis_gate.jl
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

f49fe94 covers the keyword shorthand. You were right that it was distinct from the residual I disclaimed: the field name is statically present, so scoping the limitation to values I could not see did not cover it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread export_checkpoint.jl Outdated
Comment thread tests/radiodialysis_basis_gate.jl
Comment thread biofilms_radiodialysis.R
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>
@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

This pass is reviewing a deferral, and that is the point. 3daef5f moved the head because a deferral was recorded, which tripped the staleness check and forced this re-review. That property is exactly why the design in #22 wins over marking deferrals in a resolving reply — a reply changes no sha and is never reviewed.

What changed since ba5c424:

  • GATE-06 in data/claims_ledger.csv. The gate decides on select(.isResolved | not) (preflight_merge.sh:183) and nothing else, so resolved-because-fixed and resolved-because-deferred are one signal. Your finding "Count acknowledgement sites rather than files" is now resolved as deferred to Ack census counts files, not sites: a second exemption in an allowlisted file stays green #21, not fixed — so the gate currently reads an outstanding finding as addressed. Verdict restate, and the row deliberately records two unflattering things: Preflight collapses resolved-because-fixed and resolved-because-deferred #22 would be the first gate change to make it refuse less, and unlike GATE-01..05 this was not caught by external review, so it should not be dressed as a catch.
  • A fourth CLAUDE.md section, on working in a git worktree. Two commits landed on the wrong branch during this PR (0af4505, and 5b100d7 recovered as f49fe94), both times with git push reporting Everything up-to-date while HEAD was a commit ahead.

Worth checking specifically:

  • Does the GATE-06 claim text state what is actually false, or does it overreach? The claim I ledgered is "A resolved review thread means the finding was addressed" — narrower than GATE-01/02's "the gate refuses a merge while any review thread is unresolved," which remains true.
  • restate vs requalify. I argued the claim becomes false as written rather than true under a narrower scope, following GATE-01's precedent when the set of blocking cases changed. Disagree if that reads wrong.
  • Preflight collapses resolved-because-fixed and resolved-because-deferred #22's proposed register only downgrades an open thread, so resolved reverts to meaning one thing and an unverifiable deferral refuses by construction. Is there a shape where a malformed register still clears a merge?

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

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".

@aurascoper

Copy link
Copy Markdown
Owner Author

@codex review

Re-requesting: 3daef5f has been head for over an hour with no pass. Your last three came within 20–320s of asking, so this one looks dropped rather than queued — possibly because the diff is docs and CSV with no code change.

What is unreviewed:

Worth checking: whether the GATE-06 claim text overreaches (I ledgered "A resolved review thread means the finding was addressed" — narrower than GATE-01/02's claim, which stays true); whether restate is right against GATE-01's precedent; and whether #22's register, which only downgrades an open thread, has a shape where a malformed row still clears a merge.

PR is otherwise at zero unresolved threads with all 14 checks green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CLAUDE.md Outdated
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant