Skip to content

test(cladistics): cover the zero-read half of the vacated-clade tip rule - #246

Merged
hyperpolymath merged 4 commits into
mainfrom
arena/101b4aee-metamanifold-webui
Oct 23, 2026
Merged

hyperpolymath merged 4 commits into
mainfrom
arena/101b4aee-metamanifold-webui

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

The last uncovered piece of the CladeCumulus move logic, plus one comment that was carried across from #232.

This is what remains after #230, #232, #235 and #239 were dispositioned — all four are now closed, with every functional change they made already on main.

What this adds

A test for the second half of the tip rule. _reaggregate_after_move treats a childless clade as a tip when it holds reads or when its rank is a sequence terminal:

counts_as_tip = own_count > 0.0 || u.rank in ("tip", "ASV")

The emptied-wrapper test on main pins the first half — a clade vacated by a move stops being a tip. The second half had no test, so nothing would have caught it being dropped. The new case moves a GenusB terminal into a clade holding a read-bearing GenusA tip, a zero-read GenusA tip and an unassigned zero-read tip, and runs both "tip" and "ASV" ranks.

The two monophyly assertions are the regression guard, and they fail in the right direction. With the rule intact GenusA has two tips, so neither reads true. Drop the rank half and the zero-read tip leaves the global per-taxon tip count, GenusA looks like a singleton, the read-bearing tip flips to true, and the zero-read tip gets no verdict at all (nothing) instead of false.

Taxon purity is asserted as well, but deliberately not as the guard: it reads 0.5 either way, because the tie-break between two equally sized genera lands on the same number by a different route. That is noted in the test so a future reader does not treat it as the thing that catches the regression.

Every expected value was derived by porting _reaggregate_after_move to a scratch script and running the fixture under both the intact and the weakened rule — not by reasoning about it in my head. That mattered: my first draft of the comment claimed purity would move to 2/3, which the port showed was wrong.

One comment, from #232. Above DuckDB.unregister_data_frame in the .jplace test: the registration is a connection-local replacement scan, so leaving it in the file makes the view unreadable for every later connection. This was the only line of #232 that had no equivalent on main.

Validation

  • tree-sitter parses the touched file with no ERROR/MISSING node.
  • Julia is not installable in this environment (the pinned runtime lives on julang-s3, unreachable here, source-only on the GitHub mirror), so CI is the real check — the Julia tests job exercises exactly this file. I will follow it and fix anything it turns up.

Correction on the record

On #235 I closed with a claim that the "zero-read terminal" testset from a CodeRabbit autofix commit "could not have passed". That was wrong, and I have posted a correction there. It would have passed; the reason I reached a different outcome (taking main's side in the merge) was that main already had the equivalent rule with the emptied-wrapper assertion — not that the test was broken.

hyperpolymath and others added 3 commits October 23, 2026 07:56
…lve conflicts, fix review findings

Bring PR #224's resolved #221 changes (BigInt validated run statistics,
cladistics/CladeCumulus repairs, CI failure digest) onto current main,
handling the four overlapping files:

- validated_statistics.jl: keep SQL projection + ordering and BigInt
  accumulation; adopt #221's pre-allocation MAX_CELLS cell check moved
  inside the accumulation so an oversized result is refused before it is
  allocated.
- clade_cumulus.jl route: keep main's AbstractString boundary (accepts
  SubString request fields) and duckdb_tables() view filter; the PR's
  stricter ::String signature would MethodError on SubString study/run.
- clade_cumulus.jl analysis: keep main's correct QueryCandidate field
  names (distal_len/pendant_len/like_weight); fix the weight-sum check to
  be asymmetric per Codex P2 — tolerate a lower shortfall down to
  1 - PLACEMENT_WEIGHT_SUM_TOL but reject any overweight beyond a tight
  PLACEMENT_WEIGHT_OVER_TOL bound.
- ci.yml: publish a digest of every failing assertion (testset, expression,
  evaluated values) before the log tail so the Checks API shows the full
  failure list.
- phylogeny.jl: import ..Tools so PH.Tools._tools resolves.
- trees.jl: _tree_format/_valid_tree_file accept AbstractString for
  SubString safety.
- server.jl: include cladistics routes once.
- tests: zero count in a sample with reads is a defined 0 proportion;
  fractional JSON counts refused; helper view unregistered after use.

Files intentionally not taken from the PR head: src/core/doi.jl (PR drops
the `module DOI` wrapper, which is invalid top-level Julia) and
src/server/routes/clade_cumulus.jl (main's version is the stricter fix).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
#232 was cut from `8df1041` and never rebased, so it conflicts with `main` on
three files. Merging it here shows what it still adds: almost nothing.

Its own review-finding fixes went to `main` through #236 -- "Address PR #232
app comments: asymmetric placement tolerance + import cleanup" -- and #239
carried the corresponding Cladistics work:

| #232 change | Where it lives on `main` |
|---|---|
| `PLACEMENT_WEIGHT_OVER_TOL` + the lower-only shortfall tolerance | `src/analysis/clade_cumulus.jl:55`, applied at `:1052` |
| validated-statistics failure digest in `.github/workflows/ci.yml` | `:429` (same 3 000-byte chunks, same 8-chunk cap) |
| `_tree_format` / `_valid_tree_file` on `AbstractString` | `src/server/routes/trees.jl:20,22` |
| single `include` of `routes/cladistics.jl` | `src/server/server.jl:131` |
| `_validated_duckdb_counts` without SQL arithmetic | superseded: `main` streams `DuckDB.StreamResult` partitions and bounds the matrix as features arrive, where #232 sorted the whole table with `ORDER BY feature LIMIT MAX_CELLS + 1` |
| `unregister_data_frame` after the replacement scan | `test/unit/test_clade_cumulus.jl:575` |
| fractional-JSON 422 case | `test/unit/test_validated_statistics.jl` (already present -- the merge would have duplicated it) |
| zero-count proportions, helper-view cleanup | `test/unit/test_exact_summaries.jl`, `test_clade_cumulus.jl` |

`import ..Tools` in `src/pipeline/phylogeny.jl` is the one change `main` does
not carry, and it is not needed: nothing in `src/` reaches `Tools` through
`Phylogeny`, and the FastTree test reaches the registry as
`MetaManifold.Tools._tools` (with a comment saying exactly that). Taking the
import plus #232's `PH.Tools` test call would have meant adopting both halves
for no behaviour change, so `main`'s shape is kept.

What survives is the explanatory comment above `unregister_data_frame`: the
registration is a connection-local replacement scan, so leaving it in the file
makes the view unreadable for every later connection.

The branch tree is therefore `main` plus those three comment lines.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
`_reaggregate_after_move` treats a childless clade as a tip when it holds reads
*or* when its rank is a sequence terminal. The existing emptied-wrapper test
pins the first half (a clade vacated by a move is not a tip). The second half --
a terminal holding zero reads in this table is still a tip -- had no test at
all, so nothing would have caught it being dropped.

The new case moves a GenusB terminal into a clade holding a read-bearing
GenusA tip, a zero-read GenusA tip and an unassigned zero-read tip, and runs
both terminal ranks (`"tip"` and `"ASV"`). Two monophyly assertions are the
regression guard, and they fail in the right direction if the rule is
weakened: with the rule intact GenusA has two tips, so neither reads
`true`; drop the rank half and the zero-read tip leaves the global tip count,
GenusA looks like a singleton, the read-bearing tip flips to `true` and the
zero-read tip gets no verdict (`nothing`) instead of `false`.

Taxon purity is asserted too, but as a plain invariant rather than the guard:
it is 0.5 either way, because the tie-break between two equally sized genera
lands on the same number by a different route. Readings for both the intact and
the weakened rule were derived by porting `_reaggregate_after_move` and running
the fixture, not by guessing, so the expected values are the algorithm's.

Also documents why the `.jplace` test unregisters its `src_df` registration:
it is a connection-local replacement scan, and left in the file it makes the
view unreadable for every later connection.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-23T16:46:48.546792Z
We couldn’t complete this review. Comment @codex review to try again.
f3b0d2c PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 27 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4f92e30e-5ece-459a-be64-8f0eb50ab091
📥 Commits

Reviewing files that changed from the base of the PR and between b656d2a and ce2e33a.

📒 Files selected for processing: 1
  • test/unit/test_clade_cumulus.jl
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Fork governance

Lane Status Run
DEED syntax (repo deed) 🟢 conformant run 39989653537
Hypatia verdict grid purple (job: success) same run

Hypatia grid purple

🟣 critical · commit ce2e33a1c89d · run 39989653537

Reporter Status Critical High Medium Minor Since previous revision
Hypatia scan 🟣 critical 45 62 26 0 ↓ regressed (5 more blocking)
panic-attack assail 🔴 high 0 6 11 0 ↔ unchanged
pons asinorum 🟡 medium 0 0 1 0 ↔ unchanged
rhodibot (RSR compliance) 🟠 indeterminate 0 0 0 0 — indeterminate
panicbot (panic recovery, error handling) 🟠 indeterminate 0 0 0 0 — indeterminate
echidnabot (formal verification: proofs/) 🟠 indeterminate 0 0 0 0 — indeterminate
seambot (integration health: API routes) 🟠 indeterminate 0 0 0 0 — indeterminate
finishingbot (release readiness) 🟠 indeterminate 0 0 0 0 — indeterminate
accessibilitybot (WCAG on the React UI) 🟠 indeterminate 0 0 0 0 — indeterminate
glambot (presentation quality) 🟠 indeterminate 0 0 0 0 — indeterminate
cipherbot ⚪ not applicable 0 0 0 0 ↔ unchanged
sustainabot ⚪ not applicable 0 0 0 0 ↔ unchanged
Hypatia scan: 133 finding(s)
  • critical cicd_rules/banned_language_file — frontend/src/views/run/parseFilterYaml.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/encode.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/charts.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/export.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/figure.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/legend.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/types.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/download.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/text.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/timeago.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/text.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useUnsavedGuard.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useApi.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useApi.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useAnalysis.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useSSE.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useTabParam.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useNavRefresh.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useJobEvents.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/about.ts — TypeScript file detected -- banned language
  • 113 more; see the structured report in the artifact.
panic-attack assail: 17 finding(s)
  • high DynamicCodeExecution — src/pipeline/dada2/context.jl — eval/Meta.parse in src/pipeline/dada2/context.jl
  • high DynamicCodeExecution — src/analysis/analysis.jl — eval/Meta.parse in src/analysis/analysis.jl
  • high DynamicCodeExecution — src/analysis/differential.jl — eval/Meta.parse in src/analysis/differential.jl
  • high DynamicCodeExecution — src/core/provenance.jl — eval/Meta.parse in src/core/provenance.jl
  • high DynamicCodeExecution — scripts/check-julia-syntax.jl — eval/Meta.parse in scripts/check-julia-syntax.jl
  • high DynamicCodeExecution — test/unit/test_permanova.jl — eval/Meta.parse in test/unit/test_permanova.jl
  • medium UnsafeFFI — src/core/memory_probe.jl — 3 ccall/FFI calls in src/core/memory_probe.jl
  • medium InputBoundary — src/server/routes/runs.jl — 2 JSON3.read/JSON.parse call(s) with 0 try block(s) in src/server/routes/runs.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/composition.jl — 6 JSON3.read/JSON.parse call(s) with 2 try block(s) in src/server/routes/composition.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/studies.jl — 4 JSON3.read/JSON.parse call(s) with 2 try block(s) in src/server/routes/studies.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/analysis.jl — 9 JSON3.read/JSON.parse call(s) with 4 try block(s) in src/server/routes/analysis.jl — these throw on malformed input; wrap in try/catch
  • medium UnsafeFFI — install.jl — 1 ccall/FFI calls in install.jl
  • medium ProofDrift — test/unit/test_occupancy_grade.jl — 2 `@test x isa Y` assertion(s) with no value check — type-only tests may substitute for an unwritten formal theorem in test/unit/test_occupancy_grade.jl
  • medium ProofDrift — test/unit/test_evidence.jl — 1 `@test x isa Y` assertion(s) with no value check — type-only tests may substitute for an unwritten formal theorem in test/unit/test_evidence.jl
  • medium InputBoundary — bench/advanced_zero_handling/benchmark.jl — 1 JSON3.read/JSON.parse call(s) with 0 try block(s) in bench/advanced_zero_handling/benchmark.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — frontend/src/figure/charts.ts — 1 JSON.parse call(s) with 0 try block(s) in frontend/src/figure/charts.ts — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
  • medium InputBoundary — frontend/src/components/alphaMetrics.tsx — 1 JSON.parse call(s) with 0 try block(s) in frontend/src/components/alphaMetrics.tsx — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
pons asinorum: 1 finding(s)
  • medium unreachable-after-jump — ./frontend/src/components/PublicationTablesPanel.tsx:208 — statement is unreachable after an unconditional jump (T0, HEURISTIC)
rhodibot (RSR compliance): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
panicbot (panic recovery, error handling): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
echidnabot (formal verification: proofs/): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
seambot (integration health: API routes): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
finishingbot (release readiness): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
accessibilitybot (WCAG on the React UI): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
glambot (presentation quality): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
cipherbot: not_applicable — no cryptography is implemented in this repository
  • ⚪ not applicable: no cryptography is implemented in this repository
sustainabot: not_applicable — fleet adapter slot reserved; no adapter exists yet (gitbot-fleet README)
  • ⚪ not applicable: fleet adapter slot reserved; no adapter exists yet (gitbot-fleet README)

Scope: static findings reported by each row's tool at the named commit. Purple, red, yellow and blue grade the worst finding present; orange is evidence missing, malformed or a scanner that did not run; grey is an explicit scope exclusion with its reason. No row is a security certification. report.json, badge.json and the raw findings are in the verdict-grid artifact on the linked run.

@hyperpolymath
hyperpolymath merged commit d84400e into main Oct 23, 2026
17 checks passed
@hyperpolymath
hyperpolymath deleted the arena/101b4aee-metamanifold-webui branch October 23, 2026 16:49
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