feat(apollo): rebuild the Apollo release package as a tested CLI - #224
Open
jbrestel wants to merge 43 commits into
Open
feat(apollo): rebuild the Apollo release package as a tested CLI#224jbrestel wants to merge 43 commits into
jbrestel wants to merge 43 commits into
Conversation
jbrowseOrganismList did not compile: line 11 passed an undeclared $organismAbbrev to the JBrowseUtil constructor under use strict, so /service/jbrowse/organismList returned a Perl error with HTTP 200. getDbh only needs projectName. Two further defects in that script are documented but not yet fixed (they need the same pass): $historySql joins to a nonexistent alias `nt`, and addHistoryToOrganism reads Oracle-cased hash keys that DBD::Pg returns lowercase -- so HISTORY is silently always absent. Spec covers rebuilding createApolloReleasePackage against the UniDB portal on cedar, seeded from live prod Apollo rather than a frozen build-68 organism list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
yew's faToTwoBit is a 2016 build against libssl.so.10 / libpng15, none of which exist on cedar (Rocky 9), so it cannot be copied. Installed UCSC's current linux.x86_64 build to ~/bin. Verified the two produce byte-identical output (md5 a8b9cb95a7794c1ed7dcc3568edeaff9) over the cneoJEC21/cdenJEC21 genome, which also confirms the rename at the byte level. Measured 0.17-0.74s per genome, so the spec now says always regenerate rather than copying .2bit forward between releases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Release-68 is not a valid baseline. Between b68 and b71 the track layer moved REST -> flat files, the database moved Oracle -> Postgres, and many track definitions changed; output matching release-68 would indicate a bug, not correctness. Verification is now equivalence with what the portal serves today for the same organism, where every difference must fall into one of four declared classes (absolutization, refseq track swap, include rewriting, deliberate removals). Adds a track-count floor and URL liveness sampling, since empty-but-valid JSON is the characteristic failure of the flat-file migration. Release-68 is retained only as the file-name inventory and for the faToTwoBit reproducibility check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
14 tasks, TDD throughout, against the 2026-08-21 design spec. Reconciliation is pure and fixture-tested; only Portal, Apollo, Generate and Commands touch the world. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The history query joined to an alias 'nt' absent from its FROM clause, so it threw while the script still exited 0 and printed JSON with no history. addHistoryToOrganism then read Oracle-cased keys that DBD::Pg returns lowercase. Together these meant HISTORY was always missing, and with it the annotation version every Apollo organism name depends on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The string "0.0" is true under Perl's boolean rules but zero numerically, so '? 1 : 0' on a flag would silently invert it. Coerce via looks_like_number and die on a non-numeric flag rather than guess. Compare build_number numerically by intent, skipping a non-numeric row loudly (stderr is fatal for the caller). Expose normalise() as the public seam so tests exercise the real rules without a fixture, subprocess or database. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The synthetic history used 19.9 and 71, but "71" gt "19.9" lexically too, so the old string comparison would have passed it. Add a build_number of 9, which is lexically larger than both and numerically smaller: lexical ordering now picks 'older' and only numeric ordering yields 'newest'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Organisms are keyed by the abbrev parsed from Apollo's directory field, never by commonName, which curators can edit in the GUI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured against prod: 17 organisms have publicMode=false, and 16 of them are on the portal with 15 qualifying as reference+annotated. They are curator-hidden, not retired -- the workflow Paul's notes describe for hiding an organism from users while admins retain access. Three carry annotations (iscaPalLabHiFi 20, treeQM6a 11, etenHoughton2021 2). Paul's updateOrganismInfo curl hardcodes "publicMode":"true", so running the generated commands re-publishes all 17. The plan reproduced that bug faithfully. Update and rename now echo back the visibility Apollo currently holds, and refuse to run when it is unknown rather than defaulting. Only an approved prune may change visibility. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers the third hardening behaviour, which had no test. Verified by mutation: deleting the looks_like_number guard makes test 13 fail. The obvious form of this test does not discriminate. A bad row among good rows passes with the guard deleted too, because 'not-a-number' + 0 is 0 and any real build number beats it -- row order does not change that. So the case is pinned with a second organism whose only history row is the bad one: with the guard it yields no latest version, without it the row becomes $best and returns 'bogus'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every entry requires a reason. The file it replaces was a hardcoded __DATA__ block that froze two builds ago, where decisions and staleness had become indistinguishable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e test The warning capture apparatus asserted nothing, so deleting the warn in Apollo.pm would not have failed the suite. Assert the offending Apollo id appears -- the actionable content -- rather than the surrounding prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
loadFromCommand treats any stderr byte from its child as proof the output is untrustworthy, while the module itself warned to stderr from the build-number skip path. A caller applying this module's own policy to it would read a handled skip as a release-breaking error. Warnings are now collected and exposed via warnings(), reset per normalise() call; the caller decides whether to print. Matches the pattern specified for ApolloRelease::Rename. Also: capture jbrowseOrganismList's stderr to a File::Temp rather than a fixed /tmp path (concurrent runs could truncate each other, and the size check is only meaningful if nothing else can write the file), keeping it on the failure paths for inspection; shell-quote the project name, which reaches the shell from a --project flag; die on a duplicate organism_abbrev rather than silently discarding an organism, as the sibling Apollo module does; and warn rather than die on a missing flag, since a NULL column should not stop a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Update and rename entries carry the organism's current publicMode so the command generator can echo it back; 17 live organisms are deliberately hidden and must not be re-published by a routine update. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reconcile now reports overlay entries that no longer have any effect -- an add for an organism already live, or a remove naming an abbrev in neither Apollo nor the portal. Fatal would break a release over file hygiene; silence would discard a recorded human decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s itself A stray trailing byte on Apollo's directory field yielded the abbrev 'tgonME49 ', which matches no portal organism -- reconciliation would then report the real genome as an add and the tainted one as a prune, the add-plus-prune-for-one-genome case this project exists to prevent. Trim surrounding whitespace along with trailing slashes, and validate the abbrev's shape so interior junk is skipped rather than admitted. Guard decode_json so an HTML login page served with a 200 status reports its source and likely cause instead of a bare 'malformed JSON string'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…test
Parse failures now name the actual defect (missing organism vs missing
reason vs unparseable) instead of always blaming the reason. Duplicate
and contradiction errors cite the earlier line as well as the current
one. The shipped-overlay test reads the repo copy, so it no longer
depends on a prior bld into a shared GUS_HOME.
Return shape unchanged: {add,remove}{abbrev} stays a reason string,
which Reconcile.pm prints directly. Line numbers live in a private hash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…verlay Add treeQM6a (real prod data: publicMode false, 11 annotations, on the portal) to both fixtures. Every previous publicMode assertion used a visible organism, so hardcoding public_mode => 1 -- the bug that would re-publish 17 deliberately hidden organisms -- passed all 26 tests. Both the update and rename paths are now mutation-checked. Also: refuse a rename onto an abbrev Apollo already holds (a merge, not a rename, and the same duplicate-directory corruption the two-sources check prevents), and consult the rename map when reporting redundant overlay lines -- a rename consumes both abbrevs and silently disarmed a line naming either end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Species taxon ID cannot be used: it changed in both known renames. Strain abbrev narrows candidates, identical sequence names and lengths decide, and an ambiguous match produces no rename rather than a guess. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…check A text pass rather than a typed transformation: ~25 of these URLs live inside HTML blobs and JavaScript function bodies where a typed getApolloObject cannot reach. The previous script did the same rewrite with no verification, so a missed URL became a silently empty track. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hanged genome Three review findings: - readFai warned only on a malformed line, not on an unopenable file. A permissions problem or a missing directory degraded silently to "no match", which sameAssembly cannot distinguish from a different assembly. - The ambiguity test alternated "names both candidates" with "says it is refusing", so a generic refusal naming neither would have kept it green. Split into two assertions and confirmed by mutation. - A candidate skipped for a missing current index fell through to "no portal organism shares its assembly", which is false and invites approving a prune over a build problem. Checked and uncheckable candidates are now counted and reported separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion shape Absolutize now validates the base so its idempotency guarantee is true rather than approximately true. Rename test 6 required the abbrev but not the reason. Reconcile exception entries now carry public_mode for symmetry with update and rename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ects Task 9 measured every producing script against three organisms on build 71. The five track producers are sound -- clean exits, zero stderr, plausible counts across two component databases including partitioned pfal3D7. Build on them. Two defects found: 16. jbrowseRefSeqs is dead for every organism. e0e9a61 commented out the getCacheFile/setCacheFile accessors but left two callers. Served through responseFromCommand, so it corrupts the response body rather than just logging. 17. The two script families disagree about which organism abbrev they take. apidb.organism holds abbrev=cneoJEC21, public_abbrev= cdenJEC21; jbrowseTracks consumes the public one, the five track producers consume the internal one. Also measured: no organism carries a [tracks.refseq] stanza, so the planned strip step currently has nothing to strip. Findings salvaged from a stub Task 9 created in the wrong repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…efSeqs e0e9a61 ("comment out stuff to do with CACHE") commented out the getCacheFile/setCacheFile accessors and the setCacheFileName() call in new(), but left printFromCache() and setCacheFileName() calling them. Perl resolves methods at run time, so nothing complained until the script ran: jbrowseRefSeqs died at its first statement with Can't locate object method "getCacheFile" via package ApiCommonModel::Model::JBrowseUtil at .../JBrowseUtil.pm line 419 exit 255, 211 bytes on stderr, for every organism and both types since 2025-02-26. These scripts are served through responseFromCommand, which merges stderr into the JSON response body, so this was corrupting the live payload rather than only logging. Removed rather than restored. The disable was deliberate; the only caller of printFromCache is jbrowseRefSeqs, and the only caller of setCacheFileName was the line new() already commented out -- so the whole path is unreachable and restoring an accessor would merely re-arm dead code. Worse, the cache it would re-arm has no invalidation: it wrote _refSeqsCache.json into GUS_HOME once and returned it forever, outliving the build whose sequence names and lengths it held. The queries are two indexed lookups; there is nothing here worth caching incorrectly. Model/t/jbrowseutil.t pins it: every $jbrowseUtil->method() call in the shipped Model/bin/jbrowse* scripts must resolve against the package (scanned, not hand-listed, so a new script is covered for free), and the four cache subs must stay absent. Measured after installing, model UniDB, build 71, zero bytes on stderr and exit 0 in all six runs: tgonME49 genomic 2265 entries protein 8204 entries pfal3D7 genomic 16 entries protein 5389 entries cneoJEC21 genomic 14 entries protein 6862 entries Each entry is {name, start, end, length}. Suite: 188 tests, all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… one apidb.organism holds two identifiers and the jbrowse scripts disagree about which they consume: jbrowseTracks selects `where public_abbrev = ?` and returns the internal one, while the five track producers (jbrowseRnaAndChipSeqTracks, jbrowseRNASeqJunctionTracks, jbrowseOrganismSpecificTracks, jbrowseDNASeqTracks) take the internal abbrev and key auto_generated/<abbrev>/ off it. Feeding a producer the public abbrev gives exit 2 on a missing datasetAndPresenterProps.conf. jbrowseOrganismList now also selects `o.abbrev as internal_abbrev` and Portal carries it through normalise, so the release tool can resolve public -> internal once and hand each script the abbrev it expects. organism_abbrev stays the PUBLIC abbrev and remains the hash key: the Apollo roster, the overlay file and reconciliation all key on it, and it is what the portal shows. Nothing existing moved. Measured on live UniDB, build 71, through Portal->loadFromCommand: 831 organisms, exit 0, zero bytes on stderr, zero warnings, and internal_abbrev defined for all 831. **37 organisms have internal_abbrev ne abbrev** -- that is the live population where the trap bites, not just the one Cryptococcus. Several are cross-genus renames (nglaCBS138 -> cglaCBS138, pory70-15 -> mory70-15) and one is mere case (scerS288C -> scerS288c), which no eyeball would catch. Model/t/fixtures/portal.json regenerated from the live command rather than hand-edited, same nine organisms. Two of them now exercise the split: cdenJEC21 (cneoJEC21) and nglaCBS138 (cglaCBS138). portal.t pins cdenJEC21's internal_abbrev and that its public abbrev is unchanged. Suite: 191 tests, all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured: 37 of 831 organisms have an internal abbrev differing from their public one, and of Apollo's 459 directories, 457 name a public abbrev, 2 name an internal abbrev whose public has changed, and 0 name neither. Both live renames therefore fall out of the database exactly, with no file I/O. Assembly identity stays as the fallback for an orphan the database cannot explain -- an organism whose internal abbrev also moved. The Rename module is unchanged; the caller resolves the database renames first and passes only the residue to it. Also recorded: two case-insensitive collisions exist in the abbrev namespace (scerS288C/scerS288c, and one under bnonp57), so every comparison must be case-sensitive. A lc() here merges two organisms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both JBrowseService endpoints that called jbrowseRefSeqs are commented out, with the note "THIS SHOULD BE REPLACED BY INDEXED FASTA IN WEBSERVICES", and nothing else references the script. It was already dead when it broke -- which is why nobody noticed for six months. Corrects an earlier claim in this branch that the breakage was corrupting the live site's response payload. It was not; the endpoint serves nothing. The package does not need it either: release-68's trackList already pointed refSeqs at seq/<abbrev>.fa.fai with an IndexedFasta store, and the refSeqs.json in seq/ is referenced by nothing. The tool now derives it from the .fai it already copies -- same shape, no database round trip, no dependency on a dead script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ip and has junk
20. Apollo's Organism record points at seq/<abbrev>.fa relative to its own
data dir and derives its sequences count from that index, while genomic
sites fetch the reference through the store service. The store location
genuinely differs, so the package must ship its own copy.
21. The refseqs track is no longer in tracks.conf -- it is in
organismSpecific.json, pointing at the store URL, and must be stripped
there instead or Apollo ends up with two reference tracks.
While measuring that: addChipChipTracks pushes the return value of
makeChipChipPeak/makeChipChipSmoothed, which already push and so return
the new array length. 56 bare integers among 158 track entries for
tgonME49, 28 of 103 for pfal3D7. That endpoint is live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both fixes were made off-branch and pushed to master; bringing them in so the branch builds against them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves the public/internal abbrev split -- the track scripts take the internal abbrev, output paths and Apollo directories use the public one. Strips the store-URL reference track from organismSpecific.json so Apollo uses its own local copy, and derives refSeqs.json from the .fai rather than the orphaned jbrowseRefSeqs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
JBrowse/bin/dumpConfigurationsForApollo.pl is the 2021 ancestor of the script being replaced; Paul's copy forked from it into a home directory and drifted there while the in-repo original rotted. That is precisely the failure section 8 corrects by putting the tool back in the repo. Its history also settles apollo_gene_tracks.conf: the in-repo version includes it unconditionally, and the 2021 "only dump annotated genomes" commit is the TODO being satisfied. The gate now lives in Portal::qualifies. The file is static, checked in, and required -- it defines the Draggable Annotation track curators drag genes into. Also marks the organismSpecific track counts in the section 10 table as superseded by the addChipChipTracks fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements the [tracks.refseq] strip that spec section 6 states and section 10 warned not to assume away. Census on cedar (build 71, 2026-08-21): 0 of 835 auto_generated tracks.conf files carry the stanza, so it is a no-op today -- shipped anyway, with the census recorded in the code, because an unimplemented stated requirement reads to the next person exactly like a forgotten one. Line based rather than the previous regex, whose character class stopped at the first comment inside the stanza. Covers generateOrganism, including the public/internal abbrev split that affects only 37 of 831 organisms and so survives any spot check. Verified by mutation: feeding the track producers the public abbrev, and writing output under the internal one, each fail the suite. Tests assertToolsAvailable, and records in a comment that nothing here calls it by design -- the CLI does, once, before the loop. Replaces a vacuous assertion: the bad-entry fixture had its index equal to the bad-entry count, so qr/\b1\b/ matched the count and deleting the whole 'at index(es)' clause left it green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Visibility is echoed from what Apollo currently holds, never defaulted; a rename targets the existing organism id so its annotations survive; prune unpublishes rather than deletes; passwords are emitted as a shell variable, never interpolated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…vy URLs The command files now carry a header and a WARNING line above each annotated prune, so the plan's line-count check must ignore comments. Also records a pre-flight check: add_organism.groovy is invoked with -url and alter_group_permissions.groovy with -destinationurl plus a trailing slash. Both came from the legacy script, which is inconsistent the same way, and neither groovy script is in these repos. An add is the one non-idempotent command, so this is worth confirming before the first real one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Designed for the real asymmetry: 457 routine updates against 2 renames and a handful of decisions that need a human. The rows that matter must not be buried by the rows that do not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Strain and isolate names carry punctuation and the portal is the source of that string, so an apostrophe reaching a --data line is reachable, not hypothetical. Pins that a single quote, a double quote and a backslash each refuse to emit, that the error names the organism and shows the offending character, and that the shell-quoting and JSON diagnoses are distinguishable. Also pins that ordinary punctuation is still accepted, so the guard cannot widen into general distrust. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pendingDecisions derived annotated_prune from the already-filtered unapproved list, so the field disagreed with the report it summarises: the text flagged two organisms losing annotations while a caller reading the field was told one. Approval gates the action, not the consequence. The banner warning moves out of the pending branch for the same reason -- an approved annotated prune is exactly the case where the release looks ready to run unread. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves renames from the database first and falls back to assembly identity, refuses absurd input rather than producing an empty release, and will not generate while human decisions are pending. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both gaps were invisible to a green suite: disabling the assembly-path merge decline, or hardcoding narrowResult to drop every add candidate, left all 97 cli tests passing. Also removes an unreachable guard in resolveRenames rather than pinning it with a test that could only assert an equivalence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A gate that dies must not swallow the evidence for its own message. assertPortalSane, assertApolloSane and assertUpdateBucketSane all ran before _printWarnings, so a NULL is_reference column -- one warning per organism, then an empty update bucket -- died telling the engineer to check --project while the warnings naming the real cause went unprinted. Portal.pm collects rather than writing to stderr precisely because its caller distrusts a child's stderr byte, which makes printing them this script's job and the order load-bearing. Also gives Apollo the single apiUrl() the CLI labels its report from. The default was written twice -- once to fetch, once to label -- so the two could drift and the report would name a host it never read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
assertUpdateBucketSane's own error recommends --force, and that override carried all the way through: generationRoster returned nothing, the run wrote empty command files and exited 0. A wrong --project that still clears the portal floor reached it. That is the silent empty release this tool was written to replace, reachable from the message suggesting the flag. assertRosterNonEmpty takes no force argument, unlike the two gates either side of it. The asymmetry is the fix, so a test pins the signature: a later consistency edit that adds one turns red rather than reopening the hole. Called before make_path so a refused run leaves no half-created release directory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comments only -- every changed line in this commit begins with '#', and the suite is unchanged at 508 tests. Three passes: drop what the code already says, compress multi-paragraph rationale to the two or three lines that carry the reason, and strip measured-today specifics (organism counts, roster sizes, dated censuses, named ids and annotation counts) that were accurate when written and go stale silently. Where a number justified a decision -- the portal floor sitting just above the live Apollo roster -- the relationship is kept and the figure dropped. Rationale that stops a later edit from quietly weakening a guard is kept: the two-abbrev split, the shared Absolutize pattern, why assertRosterNonEmpty takes no force argument, why several tests assert a pair rather than a branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the hand-maintained script that built the Apollo release package. That
script kept its thresholds, argument handling and rename detection in a file in
a home directory, so nothing ever exercised them — it produced an empty release
for build 71 and exited 0.
Model/bin/createApolloReleasePackageis now thin wiring overApiCommonModel::Model::ApolloRelease::*, where every rule with a wrong answerworth catching is a testable class method.
Two phases
--report— minutes, changes nothing. Portal + Apollo + overlay + renameresolution + reconciliation, printed. This is what goes to the curation team.
--generate— hours. Builds the package for the approved roster and writesthe command files a human then runs against Apollo.
It never calls a mutating Apollo endpoint. The step between "computed" and
"applied" is always a file a person reads.
Modules
PortalApolloOverlayReconcileRename.fai, for what the database cannot explainAbsolutizeGenerateCommandsReportCliVerified against production
A real
--reportreconciled the whole roster exactly: 453 update / 34 add /1 prune / 1 rename / 4 exception. It detected the
cneoJEC21 → cdenJEC21renamefrom the database with its 14 annotations preserved, and declined the
cglaCBS138 → nglaCBS138merge —nglaCBS138is already in Apollo, andperforming that rename would leave two Apollo organisms sharing one directory,
which
Apollo.pmrefuses to load on the next release.508 tests across 11 files.
The failure modes this is built around
and orphans the curation work, from the same API and the same inputs as the
correct behaviour.
Reconcile::assertInvariantsguards it and is deliberatelynoisy.
hardcoded
publicMode: true. Visibility is now echoed back from Apollo, andabsent it, the emitter dies rather than defaulting.
assertPortalSane,assertApolloSane,assertUpdateBucketSaneandassertRosterNonEmpty. The last one takes noforceargument, deliberately:--forceaccepts omitted candidates and anempty update bucket, but never licenses a package with nothing in it.
appears, and is empty.
Absolutize::assertNoRelativeis the post-conditionfor
rewrite, sharing one pattern so the two cannot drift.Known open, not addressed here
From a CLI review, all pre-existing and none blocking a run:
Portal::qualifies, so an organism demoted this build wouldstill be generated and repointed rather than surfacing as an
exception.report.txt/report.tsvare written after generation, so an hours-longrun that dies late leaves no paper trail.
narrowResult's prune filter is unreachable from the CLI —generationRosterrejects a prune abbrev first — so a partial
--generatealways emits zeroapproved prunes.
PORTAL_FLOORmakes--projectunusable for non-UniDB models._writeuses>:raw, so a non-ASCII organism name lands as invalid UTF-8 inthe archival report.
Note for the reviewer
e8670e13a("fix simple error in TranscriptsFromGenes SQL", one line ingeneQueries.xml) is the base commit of this branch and is unrelated to Apollo.It rides along because the branch was cut on top of it. Say if you want it
split out.
🤖 Generated with Claude Code