Skip to content

Read the mosquito bundle's renamed effort field (unblocks the weekly refresh) - #68

Merged
tgilbert14 merged 7 commits into
masterfrom
claude/mosq-effort-schema-adopt
Aug 9, 2026
Merged

Read the mosquito bundle's renamed effort field (unblocks the weekly refresh)#68
tgilbert14 merged 7 commits into
masterfrom
claude/mosq-effort-schema-adopt

Conversation

@tgilbert14

Copy link
Copy Markdown
Owner

The incident

Scheduled refresh 31298345511 (2026‑08‑09) failed in rebuild with:

ABBY mosquito effort table lacks required field(s): trap_nights

validate-artifacts and publish skipped, so no artifact byte was written. Canonical SHA‑256 remain cascade 47b98e48… / search a11a072d… / meta 00120c52…, and both live surfaces stayed up on the last good family. The fail-closed guard did its job.

Not related to the cover release — docs/ is outside the build surface.

Root cause

The Mosquito release merged upstream as fdb9aa1 (2026‑08‑05) rebuilt every site bundle under R/mos_bundle_contract.R, whose effort_week now emits effort_days, opportunities, and zero_catches — no trap_nights. Every mosquito site has the new schema; ABBY is just alphabetically first. Timeline fits exactly: 08‑02 refresh succeeded on old bundles → release merged 08‑05 → 08‑09 refresh failed.

This is not a pure rename

old new
column trap_nights effort_days
value ifelse(trapHours > 0, trapHours/24, 0) ifelse(valid_effort, trapHours/24, 0)
population every trap row only occurred & duration_ok & identity_ok

Same unit, QC-screened population. The refreshed denominator can only shrink, so mosq_activity (catch ÷ trap-nights) can only rise, on site-years that contained unusable deployments. Mosquito rows are descriptive context and enter no pooled vote, so no inferential claim changes — but published descriptive values will move, which is why the delta preview below exists.

What this PR changes (source only)

  • cascade_mosq_effort_field() resolves whichever field the bundle publishes, preferring effort_days exactly as the producing app does; fails closed when neither exists, and refuses a bundle set that mixes bases.
  • cascade_mosq_effort_note() appends the QC-screened sentence to the published mosq_trap_nights codebook entry only when the build actually read effort_days — so the caveat travels with the number, and the byte gate stays meaningful under pinned siblings.
  • The independent recomputation in test_helpers.R resolves the same two names literally rather than calling the build helper, keeping that adversarial check independent.
  • Report-only scripts/report_mosq_effort_delta.R, wired into the refresh job summary, plus a publish dispatch input (default true) that can only ever narrow publication — so a dry run shows the value deltas before anything reaches master.

Expected CI state — please read before judging the red

data/cascade_meta.rds records build_script_md5 and source_adapters_md5, so editing the builder or the adapters necessarily changes the regenerated meta. The exact-reproduction gate is expected to fail on this source head.

Under CI's pinned siblings the adapter falls back to trap_nights, so cascade.rds, search_index.rds and the codebook text should be byte-identical and only those two recorded hashes should move. Please confirm that from the run log before promoting.

Next step (needs R — I don't have it in this container)

  1. Rscript --vanilla scripts/rebuild_all.R with the seven sibling clones present.
  2. Confirm only the two build-code hashes moved under pinned siblings.
  3. Commit the regenerated family as a direct child of this head, merge.
  4. Dispatch refresh-data.yml with publish=false and read the mosquito delta report before letting a scheduled run publish.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q


Generated by Claude Code

claude added 2 commits August 9, 2026 14:23
The mosquito release merged upstream on 2026-08-05 rebuilt every site
bundle with an effort_week table that emits effort_days instead of
trap_nights, so the 2026-08-09 scheduled refresh failed closed at the
first site (ABBY) and published nothing. Canonical artifacts and both
live surfaces were unaffected.

This is not a pure rename: old trap_nights summed trapHours/24 over
every trap row, new effort_days sums it only over rows the source marks
valid (occurred, usable duration, usable identity). Same unit, QC
screened population, so a refreshed denominator can shrink and
mosq_activity can rise where unusable deployments existed. Mosquito rows
are descriptive context and enter no pooled vote, so no inferential
claim changes.

cascade_mosq_effort_field() resolves whichever field the bundle
publishes, preferring effort_days exactly as the producing app does,
failing closed when neither exists and refusing a bundle set that mixes
bases. cascade_mosq_effort_note() appends the QC-screened sentence to
the published mosq_trap_nights codebook entry only when the build
actually read effort_days, so the caveat travels with the number. The
independent recomputation in test_helpers.R resolves the same two names
literally rather than calling the build helper, keeping that check
independent.

Also adds report-only scripts/report_mosq_effort_delta.R and wires it
into the refresh job summary, plus a publish dispatch input (default
true) that can only narrow publication, so a dry run can show the value
deltas before anything is committed to master.

Note for review: data/cascade_meta.rds records build_script_md5 and
source_adapters_md5, so the exact-reproduction gate is expected to fail
on this source head until the artifact family is regenerated with
scripts/rebuild_all.R and promoted as a direct child. Under CI's pinned
siblings the adapter falls back to trap_nights, so only those two
recorded hashes should move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q
The rebuild-contracts red on this head is global.R's build-logic
lineage guard firing on source("global.R"), not the byte-reproduction
gate; every later step skipped, so the run yields no evidence about
whether any value moved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q

Copy link
Copy Markdown
Owner Author

CI blocker — not fixable from a source-only change

rebuild-contracts failed on c838b9e4 (run 31318332872), and the PR description named the wrong gate. Correcting that here.

It failed at the first step, Validate committed snapshot and extract immutable source lock:

Error: cascade.rds build-logic lineage differs from the running source; run scripts/rebuild_all.R.

That is global.R's boot-integrity guard comparing the artifact's recorded build_script_md5 / source_adapters_md5 against the running files. It fires on source("global.R") — long before the git diff --exit-code byte gate I predicted. Right about why (editing the builder or adapters changes the recorded lineage), wrong about where.

Consequence for reviewing this PR: every later step skipped — sibling fetch, rebuild, byte gate, manifest compare. So this run produced no evidence about whether any value moves. My expectation that only the two hashes move under pinned siblings is still just a code-reading claim; treat it as unverified until a regeneration says otherwise.

Why I am not pushing a fix

The guard is fail-closed by design and there is no source-only way past it — the artifact family has to be regenerated. That needs Rscript --vanilla scripts/rebuild_all.R, and this agent container has no R. Installing one here would build against an unpinned toolchain, violating the R 4.5.2 / Haswell / one-thread / 2026-07-15-snapshot contract and producing artifacts that could not reproduce deterministically. Weakening or bypassing the guard to get a green check would defeat exactly the mechanism that kept last night's bad build from publishing. So this stops here rather than manufacturing a pass.

To unblock

  1. Rscript --vanilla scripts/rebuild_all.R with the seven sibling clones present, on the pinned toolchain.
  2. Confirm from that run whether anything beyond build_script_md5 / source_adapters_md5 moved. Under pinned siblings the adapter falls back to trap_nights and cascade_mosq_effort_note() returns the identical sentence, so cascade/search/codebook bytes are expected unchanged — please verify rather than assume.
  3. Commit the regenerated family as a direct child of this head; CI should then pass exact-head.
  4. After merge, dispatch refresh-data.yml with publish=false to read the mosquito delta report against fresh siblings before any scheduled run publishes.

The weekly refresh stays red until step 3 lands. Nothing is at risk in the meantime: the live app and Pages continue serving the last good family, and the failed refresh wrote no bytes.


Generated by Claude Code

claude added 3 commits August 9, 2026 15:19
Rebuilt with scripts/rebuild_all.R on the pinned contract (R 4.5.2,
OpenBLAS 0.3.26 Haswell/one thread, RSPM noble 2026-07-15 closure
installed with pak) against the seven sibling commits recorded in the
artifact's own source lock. All 9/9 stages passed and promoted.

Every scientific component is byte-identical to the committed baseline:
annual (510x54), pooled (12x20), priors, signals, codebook (52x7),
site_meta (46x24), suite_links (552x33). Only the recorded build-code
lineage moved - build_script_md5, source_adapters_md5, and the
local_build_inputs row carrying them - which propagates into
cascade.rds$meta, search_index.rds$source_bundle_md5, and manifest.json.
data/neon-cascade-codebook.csv is unchanged, confirming the conditional
effort note kept the base sentence because the pinned mosquito bundle
still publishes trap_nights.

This clears the boot-integrity guard that failed run 31318332872
("cascade.rds build-logic lineage differs from the running source").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q
Promoting the regenerated family tripped the registered-baseline
tripwires: phenology-v2-seal1-synthetic and discharge-feasibility-f1-
offline both assert the five canonical SHA-256 values, and
rebuild-contracts asserts them inline. Re-registers cascade, search,
meta and manifest (codebook unchanged) across ci.yml,
discharge-f1-inventory.yml, test_phenology_adapter_v2.R,
discharge_f1_contract.py, verify_discharge_f1_inventory.py and the
three live spec tables.

Editing DISCHARGE-FEASIBILITY-SPEC.md changed its own registered
identity, so SPEC_SHA256 and SPEC_BLOB are re-registered in the three
discharge files as well.

Dated handoff receipts and prototypes/ are deliberately untouched:
the former are history, the latter records the artifact it was derived
from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q
The agent container is content-correct but not byte-compatible with
CI. Decisive test: a clean worktree at unmodified master 0f61ee6,
rebuilt here with the same pinned siblings, failed to reproduce
master's own committed artifacts. readRDS comparison shows identical
content with a 9-byte compressed-stream difference (110113 CI vs
110122 here); no gzip/xz/bzip2 level reproduces CI's bytes.

Restores data/, manifest.json, the five gate registries and the three
spec tables to the master values. The hashes announced in the two
preceding handoff entries are withdrawn - they came from a
non-conforming builder and must not be registered.

The source change stands: under pinned siblings the full contract
suite passed here, including the mosquito effort-calendar
recomputation at 416 site-years, and the codebook regenerated
byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q

Copy link
Copy Markdown
Owner Author

I tried the regeneration here. It doesn't hold up — reverted.

Correcting my earlier comment: I said this container couldn't regenerate the family because it had no R. It turned out to be the same OS as CI (Ubuntu 24.04.4 noble), so I installed the pinned contract — R 4.5.2 from Posit r-builds, OpenBLAS 0.3.26 Haswell/one-thread, the closure installed with pak from the 2026-07-15 snapshot — cloned the seven siblings at the commits in the artifact's own source lock, and ran scripts/rebuild_all.R. All 9/9 stages passed and promoted (467a400), and I re-registered the eight canonical-hash registries (0e4a3f9).

CI then failed at the byte gate, and it was right to. 33d0e50 backs both commits out.

The decisive test

rebuild-contracts reported all three RDS differing while data/neon-cascade-codebook.csv — plain text — matched exactly. That's an encoding signature, not a content one. So I rebuilt unmodified master 0f61ee63 in a clean worktree here, with the same pinned siblings, and diffed against its own committed artifacts:

NOT REPRODUCED — container is not byte-compatible with CI

The delta is environmental and pre-existing, nothing to do with the mosquito change. Comparing CI's cascade.rds against this container's rebuild of identical code:

identical content: TRUE
CI-built: 110113 bytes    container: 110122 bytes

Byte-identical content, a ~9-byte difference in the compressed stream. saveRDS uses default compression (build_cascade.R:1250); sweeping gzip/xz/bzip2 at levels 1–9 reproduced neither CI's size nor its bytes. It's a zlib/runtime-level deflate difference that can't be dialled in from R. The hashes in my two previous handoff entries are withdrawn — they came from a non-conforming builder and must not be registered anywhere.

What the exercise did prove

The adapter change is correct. Under the pinned siblings the full contract suite passed here, including mosquito index matches effort-calendar source recomputation at 416 site-years and mosquito effort-only zero-catch years remain explicit zeroes. Every scientific component compared byte-identical at the content level, and the codebook regenerated byte-identical — independently confirming cascade_mosq_effort_note() held the base sentence on the trap_nights path. Those are content facts and don't depend on the byte environment.

The real blocker is a process gap, and it's your call

A build-code change necessarily changes the recorded lineage, so rebuild-contracts cannot go green on a source-only head. The artifacts must come from a byte-conforming builder, and the eight registries then re-registered to whatever that builder emits. Today there's no in-CI path that produces and commits such a family from a PR branch — refresh-data.yml builds on ubuntu-24.04 and would be byte-correct, but its gate and publisher are restricted to master.

Three options:

  1. Regenerate on your pinned machine and commit the family plus registry updates as a direct child of this head. This is the documented entry point in AGENTS.md.
  2. Merge with rebuild-contracts knowingly red, then run refresh-data.yml on master to produce a conforming family — following it immediately with the registry update, since the registries will be stale the moment it publishes.
  3. Add a PR-scoped regeneration workflow that uploads a CI-built family for promotion. The durable fix, and it removes this trap for every future build-code change.

I'd suggest (1) now to unblock the weekly refresh, and (3) when there's time.

Nothing is degraded meanwhile: the failed refresh wrote no bytes, the canonical family is untouched, and both live surfaces serve it normally.


Generated by Claude Code

A build-code change necessarily rewrites the recorded build lineage, so
rebuild-contracts cannot go green on a source-only head: the family must
be regenerated by a builder whose bytes match this CI runner. There was
no in-CI way to obtain one from a PR branch, since refresh-data.yml is
restricted to master.

This workflow rebuilds on the same runtime as rebuild-contracts (same
runner, R version, dependency pins, action SHAs, OpenBLAS kernel and
thread width) and uploads the four data artifacts, manifest.json and a
SHA-256 receipt for reviewed promotion.

It is read-only: contents: read, no persisted credentials, and no
commit, push or deploy step. It omits only the global.R boot guard
(which fails by design on the head being regenerated) and the byte
gate (bytes are expected to move); every other contract still runs
inside rebuild_all.R.

The siblings input selects pinned (lineage-only, values must not move)
or current (adopts upstream data, exercises the effort_days path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q
Regenerated by regenerate-artifacts.yml run 31323917044 on this head
with siblings=pinned, so the bytes come from the CI runner and satisfy
the byte contract the agent container could not. Verified against the
run's own SHA-256 receipt before promotion.

Values did not move: annual, codebook, pooled, priors, signals,
site_meta and suite_links are identical to the committed baseline;
meta differs only in build_script_md5, source_adapters_md5 and the
local_build_inputs row carrying them, and search_index only in
source_bundle_md5. The codebook is byte-identical, confirming the
conditional effort note held the base sentence on the trap_nights path.

Re-registers the canonical set across the eight live registries and
moves the spec authority chain that follows from editing
DISCHARGE-FEASIBILITY-SPEC.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP23xFrMeWj7PS4i19aH7Q
@tgilbert14
tgilbert14 marked this pull request as ready for review August 9, 2026 16:44
@tgilbert14
tgilbert14 merged commit c4fff67 into master Aug 9, 2026
7 checks passed
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.

2 participants