Skip to content

feat(analysis): exact TSS/CSS/RSS offsets, and no silent substitutions - #61

Merged
hyperpolymath merged 1 commit into
mainfrom
arena/01a0da3a-metamanifold-webui
Sep 25, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
arena/01a0da3a-metamanifold-webui

Conversation

@arena-ai-coding-agent

Copy link
Copy Markdown

Work in progress. Opened early so CI runs against the scaling module while the wiring
lands; the checklist below is filled in as each part arrives.

What this closes

Issue #16 asked for TSS, CSS and RSS as offsets rather than aliases to relative,
and carried the line "Deferred — DO NOT IMPLEMENT IN MILESTONE 3". That line is a scope
decision; it is lifted explicitly at the top of the new conditions document.

What the code did before:

Declared What ran
TSS + nb_glm counts kept, offset log(lib_sizes) (the one honest case)
TSS + anything else proportions — the compositional transform the issue existed to avoid
CSS, RSS aliased to relative, with a warning; under nb_glm a count model was fitted with fractions
size_factors described as "DESeq2 median-of-ratios", computed as library size divided by its own geometric mean

What replaces it

src/analysis/scaling.jl (conditions published first, in
docs/statistics/method-conditions/scaling-and-offsets.md):

  • TSS — log(library size).
  • CSS — cumulative sum of counts at or below the declared per-sample quantile
    (Paulson et al. 2013), css_quantile default 0.75. Refused, by name, when the
    cumulative sum is zero: log(0) is not a small number. cumNormStatFast's data-driven
    choice of the quantile is deliberately not implemented — a parameter chosen from the
    data is a decision the run has to record.
  • RSS/TMM — trimmed weighted mean of log-ratios to a reference sample
    (Robinson & Oshlack 2010), tmm_log_ratio_trim 0.3, tmm_sum_trim 0.05,
    tmm_ref_column optional, reference otherwise chosen the way edgeR chooses it.
  • size_factors — median-of-ratios (RLE), which is what the name claimed.

All four return factors centred to geometric mean 1, the log-offset, the declared
parameters, the uncentred quantities and a SHA-256 of the offset vector. Refusals are
unsuccessful states: a zero-total sample, an all-zero cumulative sum, a sample with no
positive feature in common with the reference, or no feature positive in every sample all
raise with the reason attached, and none of them is replaced by a factor of 1.

Evidence

test/unit/test_scaling.jl: known answers whose arithmetic is written next to the
expected number, properties a wrong implementation fails (feature-permutation
invariance, a sample that is a multiple of another, the CSS outlier property that TSS
does not have), negative controls for every refusal, and the integration path through
prepare_analysis_table down to the offset in the manifest.

Stated limit, not hidden: parity with metagenomeSeq::cumNorm and
edgeR::calcNormFactors at 1e-6 is not verified here. Neither package is in the pinned
R environment, and this repository does not add an unpinned R dependency to make a test
pass. That acceptance item is recorded as outstanding rather than claimed.

Closes #16 (with the residual condition recorded in the issue thread).

…ditions

Library size, CSS, TMM/RSS and median-of-ratios size factors, each returning factors
centred to geometric mean 1 and the log-offset a count model is fitted with, plus the
declared parameters, the uncentred quantities and a SHA-256 of the offset vector for the
manifest. The conditions this code is held to are published first in
`docs/statistics/method-conditions/scaling-and-offsets.md` (issue #16).

Nothing consumes the module yet: `prepare_analysis_table` still sends `TSS` through the
proportional branch and aliases `CSS`/`RSS` to `relative`, and `size_factors` still
computes library size divided by its own geometric mean. The wiring and its tests follow
in the next commit, in which the current behaviour is deleted rather than replaced
silently.

Also: the CI test step now posts the tail of a failing `test/runtests.jl` as a check
annotation. The job log is served from a storage host that is not reachable from every
environment that needs to read a failure, so the failure message effectively had no
reader.

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

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 6368eaea-f4d8-42a8-8bb1-d7135c78227e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@arena-ai-coding-agent
arena-ai-coding-agent Bot force-pushed the arena/01a0da3a-metamanifold-webui branch from 122eefd to 2280808 Compare September 25, 2026 20:40
@hyperpolymath
hyperpolymath merged commit 2f66051 into main Sep 25, 2026
5 of 6 checks passed
@hyperpolymath
hyperpolymath deleted the arena/01a0da3a-metamanifold-webui branch September 25, 2026 20:41
hyperpolymath added a commit that referenced this pull request Sep 25, 2026
)

Two things in one PR, because they have one cause: the layer that
claimed to compute
TSS/CSS/RSS offsets did not, and the layer that accepted them refused
them.

## 1. The refusal — main is red because of this

`NormalizationConfig` canonicalises its method to lower case (`"TSS"` →
`"tss"`), while
`VALID_NORMALIZATION_FOR_METHOD` listed `"TSS"`, `"CSS"`, `"RSS"`. The
membership test
therefore failed for those three spellings and `AnalysisConfig` raised
`normalization.method 'tss' incompatible with method 'nb_glm'` —
including for the
existing `test/unit/test_execution.jl` testset *TSS offset for NB_GLM*,
which constructs
`method="TSS"` exactly as the documentation instructs. That is the
failure behind the red
`Julia tests` job on the TSS-offsets commits; it is not a flaky test and
it is not the
new estimation layer.

The table and both comparisons (constructor, `validate_config`) are
lower case now, the
JSON-schema enum still accepts the upper-case spellings for existing
documents, and
`test/unit/test_scaling.jl` asserts that every admissible spelling of
every method name
is accepted.

## 2. The wiring — issue #16's offsets are computed, not aliased

`prepare_analysis_table` now computes scaling factors through
`src/analysis/scaling.jl`
and hands the offset to the fit:

| declared | response | offset |
|---|---|---|
| `tss` / `none` (count model) | counts | `log(library size)` |
| `css` | counts | `log` cumulative sum at the declared `css_quantile` |
| `rss` (TMM) | counts | `log` trimmed weighted mean of log-ratios to a
reference sample |
| `size_factors` | counts | `log` median-of-ratios (RLE) — the estimator
the name claimed |
| `relative` | proportions | none (a transform, not an offset) |

Previously `css`/`rss` were aliased to `relative` under `@warn`, `tss`
did that for every
method but `nb_glm`, and `size_factors` computed library-size centring
wearing DESeq2's
name. `css`/`rss` are now refused for a response with no counts to
offset — in the
configuration layer and again in the execution path.

`css_quantile` (0.75), `tmm_ref_column` (chosen the way edgeR chooses
it, and recorded),
`tmm_log_ratio_trim` (0.3) and `tmm_sum_trim` (0.05) are validated in
the constructor,
included in the config hash and canonical JSON, round-tripped through
`to_json`/`from_json`/Nickel/DEED, mirrored in the JSON schema and
Nickel contracts,
exposed through the server's configuration route, and explained in
`context_help` and the
frontend help. A run that declared a different quantile is a different
run.

`diagnostics.checks["scaling"]` and the manifest provenance record the
kind, the
definition, the parameters, the raw quantities before centring and a
SHA-256 of the
offset vector.

## Evidence

- `test/unit/test_scaling.jl`: known answers with the arithmetic written
beside them;
properties a wrong implementation fails (feature permutation, a sample
that is an exact
multiple of another, invariance to a shared constant); the CSS
outlier-robustness
property; negative controls for every refusal (zero-total sample, zero
CSS cumulative
sum, unknown reference, disjoint supports, out-of-range trims); the
integration path
through `prepare_analysis_table`; and a base-R transcription of the same
conditions,
  which skips **loudly by name** where R is unreachable.
- Conditions document, published before the implementation and merged in
#61:
  `docs/statistics/method-conditions/scaling-and-offsets.md`.
- Local gates: `check-spdx.sh` OK (284 files), `check-format.sh` OK (331
files),
  `check-lint.sh` OK (`tsc --noEmit` clean over `frontend/src`).

## Not claimed

Parity with `metagenomeSeq::cumNorm` and `edgeR::calcNormFactors` at
1e-6 is **not**
verified: neither package is in `renv.lock`, and this PR does not add an
unpinned R
dependency to make a test pass. The base-R transcription catches
implementation slips, not
specification errors. That outstanding condition stays recorded in issue
#16 and in the
conditions document rather than being asserted as met.

Closes #16.

Co-authored-by: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Sep 25, 2026
…salvageable delta of #71) (#72)

<!--
SPDX-License-Identifier: CC-BY-SA-4.0
-->
## Summary

Re-anchors **the one genuinely missing piece** of PR #71 onto `main`,
and documents why everything else in that PR is already here in newer
form.

## Why PR #71 cannot merge (measured, 2026-09-26)

1. **Wrong lineage → `mergeable: CONFLICTING` (DIRTY).** The PR branch
is built on the **upstream parent's history** (`JoshuaJewell:main` =
`ecefb1c` is an ancestor; the PR = upstream + 3 commits). `main` here
shares only the **initial commit** `7884553` with it — the exact
fork↔upstream divergence documented in
`docs/integration/conflict-map-2026-09-25.md`. A test merge surfaces
**38 conflicting files** (add/add: ci.yml, ui.yml, package.json,
MetaManifold.jl, Execution.jl, tests, docs, binary `bun.lock`).
2. **Content is ~99% superseded.** Of the PR's 319 changed files, **281
are byte-identical with `main` already** (the migration was re-anchored
earlier via `0e61f4f` and siblings). Net of `main`, the PR branch has
only **+398 lines**, and after filtering to lines the PR itself authored
that `main` truly lacks:
- **`src/analysis/Execution.jl` (124 lines): stub/mock code** (“Mock
p-value”, “Real implementation would call R…”) — replaced on `main` by
the real statistics layer (#57–#66).
- **Schema/doc notes “TSS/CSS/RSS deferred alias”** — obsolete: `main`
implemented exact TSS/CSS/RSS offsets in #61.
- **CI action pins** — older than `main`'s (Dependabot keeps `main` at
checkout v7.0.1 / setup-julia v3.0.2 …).
- **Escape-key dialog handlers** — superseded by native `<dialog>` with
`showModal()` (#32), which supplies Escape/role/focus-trap natively.
- **`lint_source.jl` undeclared-deps, Justfile bootstrap/setup-full,
coupling test** — all present on `main` in deliberately newer forms.
- **The 2 CodeQL alerts (“Workflow does not contain permissions”)** —
the author already fixed these on their branch (`6b481a3`, `eab8ea0`),
and *that* piece is what `main` was still missing.

## Changes

- `.github/workflows/ci.yml`: top-level `permissions: contents: read`
plus job-level blocks on `repo-hygiene` and `test` (`cicd-squabbler`
already declares its own). Mirrors `ui.yml`, which already has a
top-level block, and the fix landed on PR #71's branch.

## Recommendation

**Close #71** in favour of this PR (its remaining unique content would
regress `main`); carry any future application changes to `JoshuaJewell`
per the PR template's Base check.

## Testing

- `npx js-yaml .github/workflows/ci.yml` parses; `permissions` present
at workflow level and on all jobs.
- `scripts/check-spdx.sh` OK (291 files), `scripts/check-format.sh` OK
(339 files), `scripts/check-lint.sh` advisory (bun not installed in
sandbox).

Co-authored-by: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Oct 17, 2026
… work (#178)

## Purpose

Make the fork's anticipated upstream state concrete while Joshua's
reviews/merges remain pending. This is a **draft baseline proposal**,
not evidence that upstream has merged anything.

The proposed file tree is exactly JoshuaJewell/main at
`52feb605176a4b140f4b40c2b74d2f18adec1f72` plus:
- upstream #59 at `3c9d352e1f6c85353c2d78ef1ebf28e5fb64c263`
- upstream #60 at `ea974a2328f7f44ad9afeb2f10e5803cd4e31e62`
- upstream #61 at `d9ea982dc97c958405e1ff6f57f1b0d8d9b221d1`

Resulting tree: `b27daa7f1aa5fa84f320ddae522fa5f2a15ebef5`.

## Effect on fork main

This deliberately removes fork-only content from the proposed active
baseline: 74 files differ from current main. The existing main commit
`491a3e0702ca8ca40e411860d6166523b298b3ac` is the first parent, and all
three repair heads are additional parents, preserving the work in
history. No existing branch is deleted or rewritten. Do not submit this
integration branch upstream.

**Before merging:** decide whether main should be the upstream-aligned
baseline, with unfinished work isolated onto feature branches. This PR
removes real fork features from the active tree, not just stale merge
metadata. Recheck upstream and repair heads after Joshua's final
decisions; their eventual file tree may change.

## Branch audit

There were seven total branches when inspected: main and six additional
branches.

| Branches | Finding | Disposition |
| --- | --- | --- |
| repair/permanova-proofs | Clean pending upstream #59 | Keep until
merged |
| repair/documentation | Clean pending upstream #60 | Keep until merged
|
| repair/dada2-base-r | Clean pending upstream #61 | Keep until merged |
| upstream/permanova-proofs, upstream/docs-community,
upstream/dada2-base-r | All three have identical tree
2361ab8; old contaminated submissions,
not independent WIP | Retire after baseline decision; no unique feature
work needs completing on these branches |
| main | Contains additional work beyond the repaired upstream
contributions | Preserve and isolate selected changes |

Compared with the old three branches, main changes only .gitignore, the
differential fixture, the restored deferred-issues specification and
removal of the duplicate root code of conduct. Their substantive work is
preserved.

## Genuine work to isolate and finish

These are code-review findings, not claims that the features have passed
a fresh full validation.

| Work | Evidence / remaining work |
| --- | --- |
| glmGamPoi dispersion | Actual implementation and configuration exist,
but glmGamPoi is absent from renv.lock. Existing tests do not establish
successful reference fits through this path. Pin dependencies and
validate against trusted R fits before treating it as complete. |
| Significance proof model | The Agda model makes noncomputed numbers
unrepresentable, while the Julia documentation explicitly permits
retained pairwise rows after an omnibus failure. Reconcile model and
implementation; do not claim the proof establishes the Julia
representation's behavior. |
| Canonical JSON/JCS | Utility and fixtures exist; no production callers
found beyond module inclusion/export. Establish conformance and choose
an actual integration point before claiming end-to-end provenance. |
| Memory occupancy | Measurement, algebra/proof integration, tests and
scripts exist. Isolate the work, verify sibling dependency pins and real
runtime measurements. This is distinct from biological occupancy
modelling in issue #18. |
| Wildcard-IP origin handling | Separate behavioral change in server
middleware. Review address/port parsing and trust assumptions, then run
host/origin, DNS-rebinding and CSRF regressions before submission. |
| Plot title markup stripping | Small independent helper and usage
change; suitable for a focused bugfix after checking its tests and
behavior. |

Repository layout changes, frontend major upgrades, community policies,
automation and other tooling are separate fork-maintenance decisions,
not prerequisites for the three upstream repairs. Do not carry forward
the obsolete claim in fork AGENTS.md that locked ggplot2 requires
tibble: the retained tibble dependency is used by installation/CI
tooling.

Open planning issues are not evidence of implemented branch WIP; several
overlap or describe functionality already upstream.

## Validation

- All six repair commits applied together without conflicts.
- git diff --cached --check passes.
- Local combined tree and GitHub-created tree match exactly.
- Latest individual PR CI runs passed: #59 run 38976014775; #60 run
38998820680; #61 run 38998823755.
- Combined baseline CI is separate and must be inspected before merging.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Joshua Jewell <developer@joshuajewell.dev>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.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.

feat(analysis): TSS/CSS/RSS offsets — exact normalization with DESeq2-style offsets, not alias to relative

1 participant