Skip to content

Make "I want to add an example" a documented path, not a reverse-engineering exercise - #203

Merged
lucapinello merged 1 commit into
mainfrom
docs/2026-08-14-contributing-an-example
Aug 14, 2026
Merged

Make "I want to add an example" a documented path, not a reverse-engineering exercise#203
lucapinello merged 1 commit into
mainfrom
docs/2026-08-14-contributing-an-example

Conversation

@lucapinello

Copy link
Copy Markdown
Contributor

You asked about a developer who wants to contribute new models, examples, or just use chorus without friction. The model path is now well covered (#201 closed the last gap there). The example path was not covered at all.

Sentence one turned them away

CONTRIBUTING.md opened with "this guide will walk you through the process of implementing a new oracle", and ## Current Priorities listed three kinds of contribution — all of them models. Someone with a variant they care about, who has no intention of porting a neural network, is told immediately that they are in the wrong document. Adding a worked example is the cheapest useful contribution chorus can receive, and nothing invited it.

It was also genuinely undocumented

Four regeneration scripts own different example types, and the matrix mapping example → script → valid --oracle → conda env lived in CLAUDE.md (a file written for Claude sessions), the CHANGELOG, and a May audit report. None of those is where a contributor looks.

That matrix exists because getting it wrong is the most common way a regeneration silently does nothing:

  • regenerate_multioracle.py accepts chrombpnet / cherimoya / legnet / alphagenome and pointedly not enformer — whose report comes from regenerate_examples.py
  • it also has no --gpu, while the other two do; passing one is an argparse error that scrolls past in a tailed log
  • the wrong conda env does not fail fast — a missing oracle package logs Failed to load <track> once per track and carries on, so a run can spend an hour loading nothing

What changed

  • Routing table in the first screen — oracle / example / bug-fix-or-doc-fix, each with an honest size estimate, so a reader self-selects instead of bouncing
  • New ## Contributing an example or walkthrough — the verified matrix, the real entry shape copied from ENFORMER_EXAMPLES, the strict_ref warning about ref, the four traps, and the six guard tests to expect. The documented pytest command collects 138 tests — verified, unlike the browser recipe that shipped collecting 0
  • Priorities broadened — examples, "documentation that was wrong when you read it" (including a repro with no patch), and tests, alongside the three model categories
  • examples/walkthroughs/README.md and examples/notebooks/README.md point at the one canonical section rather than restating it

Guard

8 tests in tests/test_contributing_example_path_matches_the_scripts.py pin the table to the scripts' argparse definitions rather than to a proofread:

  • every documented --oracle value must be a real choices= entry — negations like no `enformer` are stripped and covered separately
  • the no-enformer exception must hold in both directions: if that script ever gains enformer, the test demands the note be removed
  • the --gpu asymmetry must stay correctly described
  • generate_walkthrough_notebooks.py must stay argument-free (documented as codegen-only)
  • every tests/*.py the section names must exist — a documented command that errors is worse than none

Confirmed the choice-guard bites: a fabricated sei in the walkthrough row is flagged, while the legitimate no `enformer` is not.

Verification

  • Fast suite 1,993 passed / 30 skipped (up 8)
  • test_doc_links_resolve.py 70 passed — the four new in-page anchors and both cross-file links resolve
  • The documented guard-test command collects 138 tests
  • Matrix re-verified against each script's add_argument calls, not against CLAUDE.md

🤖 Generated with Claude Code

lucapinello added a commit that referenced this pull request Aug 14, 2026
The [0.7.3] section covered #186-#198 and stopped there, while four more PRs landed under the same
release. Everything in this cycle goes in 0.7.3, so the notes have to carry it.

Two entries under Fixed — the README defects a new user hits first, including the ref allele that
raised and took two later recipes down with it (3 of the 9 advertised recipes), and the
HuggingFace gate that reads as global but is scoped to AlphaGenome in the code. Two under Added —
the documented path for contributing a worked example, and the escape hatches that let an outside
contributor develop an oracle without our HuggingFace dataset.

Numbers in the entries are the measured ones: 114,688 bp for the Enformer mean, 64 s GPU / 59 s CPU
for the quickstart, 13.7 GiB for a single-oracle install, 0.9811 for the effect percentile that had
been documented as 0.962, and 138 tests collected by the guard command CONTRIBUTING now names.
`chorus/cli/main.py:93` re-verified as the gate after the merges.

#199 gets no entry of its own: it was the changelog PR for #196-#198, so its content is these notes.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
lucapinello added a commit that referenced this pull request Aug 14, 2026
* Stop the browser job going red when UCSC does not answer

The browser smoke job failed on #203, a CONTRIBUTING-only change, having passed on #202 minutes
earlier against the identical corpus. Nothing about the reports had changed:

    IGV error: Error accessing resource:
    https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit Status: 0
    Failed to load resource: net::ERR_CONNECTION_REFUSED

All 19 committed reports resolve their reference sequence from `hgdownload.soe.ucsc.edu`, because
igv.js requires a sequence source and hg38 is ~3 GB — bundling it in the repo is not an option, so
this is a documented limitation rather than a defect. What was not intended is the consequence: the
verdict of this file depended on a third party being up. When UCSC refused, both smoke reports
reported `canvases 0/0 painted (NOT converged)` and blew the 30 s budget at the 60 s timeout — four
failures, none of them about chorus.

A suite that goes red when someone else's server hiccups teaches people to re-run CI until it is
green, which is exactly how a real blank-panel regression gets waved through. So the two rendering
checks now separate "this report is broken" from "this render could not be judged": if the *only*
console errors are unreachable-host signatures **and** an external host was actually contacted, the
report is skipped with the host named and the errors quoted.

Deliberately narrow, because the risk here is masking a real defect:

* any console error outside the signature list disqualifies the skip, so a genuine JS fault
  alongside an outage still fails — an outage grants no amnesty;
* blank canvases with no console error still fail, which is the blank-panel regression this file
  exists to catch;
* errors with no external request attempted are never an outage.

Five offline guards pin all of that, using the failing run's error strings verbatim rather than a
paraphrase. `route.abort()`'s `ERR_FAILED` is deliberately **not** in the list: that is our own
blocking in the offline test, where failure is the assertion.

Full browser suite with UCSC reachable: 51 passed, 0 skipped — the guard changes nothing when the
host answers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Move the outage guard's own checks where they will actually run

The five checks on the skip logic were written next to the tests they protect, and that file carries
`pytestmark = pytest.mark.integration` because it drives Chromium over a 19-report corpus. A
module-level mark applies to everything in the file, so all five were deselected from the fast suite:
`pytest tests/` collected none of them, and they would only have run when someone opted into the slow
suite — needing a browser they do not use. Measured before the move: the browser module went 46 -> 51
collected, all 5 integration-only.

That function decides whether CI is *allowed* to fail, so it should be checked on every push. So the
predicate moves to `browser_harness.unreachable_external_host` (where it belongs — the harness
already owns `RenderResult` and `external_hosts`), and its checks move to
`tests/test_browser_outage_guard.py`, which has no module mark.

This is the same trap as the marker a docstring edit stole earlier in this release: a mark that is
correct for a file is not automatically correct for everything added to it.

Browser module back to 46 collected, 46 passed. The 5 guards now run in the fast suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e-engineering exercise

CONTRIBUTING opened with "this guide will walk you through the process of implementing a new
oracle", and Current Priorities listed three kinds of contribution, all of them models. Read
literally, someone with a variant they care about and no intention of porting a neural network is
told in sentence one that they are in the wrong document — and adding a worked example is the
cheapest useful contribution chorus can receive.

It was also genuinely undocumented. Four regeneration scripts own different example types, and the
matrix mapping example -> script -> valid --oracle -> conda env lived in **CLAUDE.md** (a file for
Claude sessions), the CHANGELOG, and a May audit report. None of those is where a contributor looks.
That matrix exists because getting it wrong is the single most common way a regeneration silently
does nothing:

* `regenerate_multioracle.py` accepts chrombpnet / cherimoya / legnet / alphagenome and pointedly
  **not** enformer, whose report comes from `regenerate_examples.py` instead;
* it also has no `--gpu`, while the other two do — passing one is an argparse error that scrolls
  past in a tailed log;
* the wrong conda env does not fail fast. A missing oracle package logs `Failed to load <track>`
  once per track and carries on, so a run can spend an hour loading nothing.

So: a routing table in the first screen (oracle / example / bug fix, with sizes), a
"Contributing an example or walkthrough" section carrying the verified matrix, the real entry shape
copied from `ENFORMER_EXAMPLES`, the four traps, and the six guard tests to expect — all of which
collect (138 tests), unlike the browser recipe that shipped collecting 0. Priorities now name
examples, doc corrections and tests alongside models. `examples/*/README.md` point at the one
canonical section rather than restating it.

Guard: 8 tests pinning the table to the scripts' argparse definitions rather than to a proofread —
every documented `--oracle` value must be a real choice (negations excluded and covered separately),
the no-enformer exception must remain true in both directions, the `--gpu` asymmetry must stay
correctly described, the notebook generator must stay argument-free, and every test file the section
tells a contributor to run must exist.

Fast suite 1,993 passed / 30 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lucapinello
lucapinello force-pushed the docs/2026-08-14-contributing-an-example branch from 030c513 to d02abae Compare August 14, 2026 18:52
@lucapinello
lucapinello merged commit ecfae35 into main Aug 14, 2026
2 checks passed
@lucapinello
lucapinello deleted the docs/2026-08-14-contributing-an-example branch August 14, 2026 18:58
lucapinello added a commit that referenced this pull request Aug 14, 2026
Found by reviewing this session's merges rather than by anything failing.

**The itemised install size did not add up.** README stated "2.4 GiB base env + 5.9 GiB oracle env +
3.1 GiB hg38 + 1.9 GiB weights + 0.5 GiB backgrounds = 13.7 GiB", and those five figures sum to
**13.8**. The total was right — the measured components are 2.41 + 5.89 + 3.05 + 1.87 + 0.52 = 13.74,
which rounds to 13.7 — so the error was in the display precision, not the measurement. Rounding each
component to 1 dp and then showing a total derived from the unrounded values gives a sum a reader can
check and find wrong. Now stated to 2 dp, where the arithmetic closes. Whoever verifies a number I
publish should be able to add it up.

**CLAUDE.md still held a second, unguarded copy of the regeneration matrix.** #203 moved that matrix
into CONTRIBUTING precisely because two copies drift, then left CLAUDE.md's copy in place, and the new
guard pinned only CONTRIBUTING's. So the release ended with the same defect it had just fixed, one
file over.

CLAUDE.md's inline copy is worth keeping — it is auto-loaded into every session, so replacing it with
a pointer costs an extra read on exactly the work where getting the matrix wrong is the documented way
a regeneration silently does nothing. What is not worth keeping is an unchecked copy. Both are now
parametrized over the same argparse-derived assertions: every documented `--oracle` value, the
no-enformer exception in both directions, and the `--gpu` asymmetry.

Pinned to `argparse`, deliberately not to each other — two copies checked against one another can
agree and both be wrong. Verified that deleting "no enformer" from CLAUDE.md fails
`[CLAUDE.md]` while `[CONTRIBUTING.md]` still passes, and that restoring it clears both.

Fast suite 2,008 passed / 30 skipped (13 in this file, up from 8).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
lucapinello added a commit that referenced this pull request Aug 14, 2026
Everything in this cycle goes in 0.7.3, and the notes stopped at #203 while seven more PRs landed —
most of them corrections to this release's own work, which is exactly the part a reader should not
have to reconstruct from git log.

Under Fixed: the five wrong published numbers and the two guards too weak to catch them; the strata
table that asserted a uniformity the shipped provenance contradicts; the two documents that disagreed
about whether the history was rewritten; and the audit runbook that still asked for the artefacts
#198 removed.

Under Changed: the browser job's narrow tolerance for an unreachable reference host, including the
three ways the first version of it was too generous; pytest.ini no longer claiming an exact
integration count that was never a property of the repository; and CLAUDE.md's copy of the
regeneration matrix now being checked rather than merely present.

The entries state how each defect happened, not just what it was — the weights figure came from a
glob matching two HF mirrors, the null floor was one oracle's value read as the global minimum, and
the stratification guard was defeated by prose 1,045 characters past the table it meant to check.
Those are the reusable parts.

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