Skip to content

osprey: Made the Stage-7 join fold run by run on every route, and retired --input-scores - #4646

Merged
brendanx67 merged 33 commits into
masterfrom
Skyline/work/20260908_osprey_stage7_stream_and_scores_retirement
Sep 9, 2026
Merged

osprey: Made the Stage-7 join fold run by run on every route, and retired --input-scores#4646
brendanx67 merged 33 commits into
masterfrom
Skyline/work/20260908_osprey_stage7_stream_and_scores_retirement

Conversation

@brendanx67

@brendanx67 brendanx67 commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Derived CanStreamStage7Join's admission from the reconciled parquets on disk, retiring the --task SecondPassFDR proxy that made the ordinary run resident
  • Gave both straight-through arms - the cold Run and the resume Rehydrate - a per-run source, so the bounded Stage-7 join is the DEFAULT rather than a property of one CLI invocation
  • Built each source from the whole-run loops' own per-file halves, so run-at-a-time is the same work in the same order and the two arms cannot drift
  • Keyed the O(files) warning on the milestone rather than the predicate, and asserted the per-run fold PER LEG in regression.ps1
  • Added ParquetScoreCache.IsCurrentReconciledSurvivorSubset, so the Stage-7 staleness refusal and the streaming admission ask one question
  • Retired --input-scores: every task takes -i / --input-list and derives each run's parquets from the input stem, collapsing the membership predicates onto --task

Part of #4486. Follows #4642, which made the streamed join exist and proved it at 446 files.

The two halves are on one branch because the second needs the first: deleting --input-scores
before the streaming admission was derived would have returned every Stage 7 to the O(files)
resident pool, which is the regression #4642 existed to remove.

Why this was still open

#4642 admitted only one route to the streamed join. ScoringTaskShared.CanStreamStage7Join
opened on config.ExpectReconciledInput, which only --task SecondPassFDR sets - the shape the
repro used - so the ordinary run an operator types stayed on the O(files) resident pool.

Measured cost of that gap on a 446-run CHS cohort (2026-09-08): 91.1 GB private on a 63.7 GB
box, reached inside Rebuilding first-pass survivors from 446 file(s) and paging from there. It
matches the cost model regression.ps1 publishes to within 1.3%, so the model is confirmed
rather than projected.

The flag was a proxy for a disk question

Does every run have a .scores-reconciled.parquet in the survivor-subset shape? A
straight-through run's Stage 6 has just written exactly those, so the requirement held while the
proxy was false. ScoringTaskShared.AllReconciledParquetsCurrent asks it directly, ordered last
because it is the only term that opens a file per run.

Both straight-through arms then hand the fold the per-file half of the whole-run loop they
would otherwise have run
- MaterializeRescoredFile for the cold arm,
MaterializeFileSurvivors + OverlayReconciledIntoFile for the resume. Nothing in either half
reads another run's entries, which is why the streamed and resident arms write byte-identical
output, and why this is a call-shape change rather than a second implementation.

A source is offered only when a dropped run can be rebuilt. The cold arm additionally refuses at
fold time for a run whose reconciled parquet never reached disk: ExecuteRescore KEEPS such a
run's entries because they are the only copy of the rescore, and a fold drops every run it hands
over.

The fallback the retirement left behind

Reviewing the above, Brendan asked why SecondPassFDR reads two parquets per data file when
the architecture says it reads only the PerFileRescoring one - then corrected the premise:
there is no fallback. A missing .scores-reconciled.parquet means it was not calculated,
and the Stage 4 file is not a substitute for it.

He was right, and the doc line that said otherwise was stale. Stage 6 has written a reconciled
parquet for every run since WriteUnchangedReconciled (2026-08-26); principle P13 and the
team-wide rule make absence a reported fault, not a run shape. Five live sites had not been
told, and the resume arm's two-parquet read was not an I/O trade - it was the retired fallback
still being the main road, reading Stage 4 for ROWS because the overlay-restores-VALUES shape
existed to serve no-work files that no longer occur.

  • A missing reconciled parquet now FAILS on every arm (ReconciledPathOrFail,
    UnusableReconciledParquets), where the refusal previously exempted absence with a continue
    and the resident arm degraded silently.
  • Both resume arms share one per-file half that reads one parquet, the reconciled one. The
    row order works out because FirstPassSurvivorLoader already sorts canonically - which is why
    the cold arm could always pass canonicalize: false.
  • EffectiveScoresPathFromScoresPath is deleted. It was File.Exists(reconciled) ? reconciled : scoresPath - right only because the stages run in order, and wrong the moment
    both files exist. Which parquet a task reads is now a property of the task
    (ScoringTaskShared.ReadsReconciledScores), which is also what each node is shipped: the
    mode-3 chain deletes the Stage 4 originals before staging phase 4, so a probe cannot tell
    "the artifact for my pass" from "the only artifact here".

Brendan's framing of why this mattered even though nothing was broken:

It is still only right as far as the pipeline itself makes it right... The usage is confusing
if harmless under correct pipeline functioning.

00-pipeline-architecture.md, Osprey-workflow.html and the code now agree, and the retired
form is named in the doc so it is not re-derived.

Review round: 14 of 15 findings closed

/code-review max findings, each verified before acting. Two blockers could corrupt a cohort's
artifacts while exiting 0:

  • F1 - --task FirstPassFDR re-run over a completed directory satisfied every disk-side
    term of CanStreamStage7Join, published one empty list per run for a fold that never comes,
    and rewrote both boundary sidecars and the retained base_id summary as empty. The predicate
    described the SHAPE of a Stage 7 join but never asked whether this process runs one -
    ExpectReconciledInput had been answering that incidentally, and nothing took over when the
    proxy went. New RunsStage7Join term, first and free, naming the admitted set so it fails
    closed. This also closes F9: a rescore worker no longer builds a source nothing pulls.
  • F2 - fixed by the task-dependent resolution above.

Also fixed: F4 (a test helper built a config the CLI cannot produce, and the same false
truth-table row appeared in a doc and two comments), F5, F6, F7 (the gate asserted a
source was offered, not that a fold ran - it now also asserts nothing pulled the whole pool),
F8, F10, F11 (one parquet open instead of two, answering instead of throwing past
the named refusal), F12, F13(b,c), F14 (a ~10 GB deferred read is no longer forced on
a branch that never uses it), F15.

F3 is open, and its direction is inverted by the above. It called the fold arm's throw a
defect for refusing runs the resident arm handles; under the contract the fold arm was right and
the resident arm's silence was the defect, so both now fail. Its substance survives - runs whose
parquet IS correctly written but is absent from CurrentReconciledPaths - and the fix is to
reconcile the validity map with the artifact, NOT to restore a Stage 4 substitution.
F13(a) (refuse the resident guard in ValidateArgs rather than hours later) is backlogged:
real, but an env-var-only A/B path, and not worth CLI-validation risk.

What is deliberately NOT in scope

OSPREY_PASS2_QVALUE=transfer still computes its per-file half in Stage 7, over the whole pool.
Moving TransferOneFile into Pass2PerFileWorker is #4642's own step 2b and is a redesign of
that arm, not a call-shape change. Until it lands:

  • Stage7ResidentGuardError keeps its streamingAvailable exemption - a run with no streamed
    alternative has no choice for a token to record;
  • the #4486 row stays in $knownResidentGaps, with its scope corrected from "every leg except
    mode 3's join phase" to "only a pass-2 mode with no per-file worker".

Test plan

  • Build-Osprey.ps1 -RunTests -RunInspection - 593 tests, inspection zero-warning
  • regression-parallel.ps1 -Dataset All - 95 PASS / 0 FAIL / 1 SKIP, both lanes exit 0, Tokens REQUIRED: 0
  • mode1 (vs golden) on all four datasets - no golden moved by any of this
  • mode3 (HPC chain==straight) on all four - the derived parquet paths resolve to the files --input-scores used to name
  • mode3 (per-run hydrate) on all four - the rescore workers took the per-run hydrate
  • mode1 / mode2 / mode5 (streamed join) on all four - cold run and both resumes fold run by run, asserted per leg, and now also asserted NOT to have pulled the whole pool
  • mode8 / mode9 - the partial and crash-shaped resumes, the closest coverage to the missing-artifact case the new fault guards
  • New: TestOnlyStage7JoinTasksAdmitTheStreamedJoin, TestScoresPathsDependOnTaskNotDisk, TestValidateRejectsDuplicateInputStems
  • TeamCity Perf/Regression on pull/4646 - build 4169631 on 3cc4fa7956, 95 PASS / 0 FAIL / 1 SKIP in 01:16:34
  • 446-file straight-through acceptance run - expect the streamed marker, no Rebuilding first-pass survivors, and a 20-30 GB band rather than 91 GB

A defect in the gate itself was found and fixed on the way: regression-parallel.ps1 counted
its own warnings as failures, because Select-String is case-insensitive by default and
': FAIL' matched the ': fail' inside WARN: failed to prune .... A lane that exited 0 with
all 23 legs passing was reported as 1 FAIL, failing the whole gate. Counted case-sensitively
now, with warnings surfaced separately.

See TODO-20260908_osprey_stage7_straightthrough_stream.md in pwiz-ai/todos

Co-Authored-By: Claude noreply@anthropic.com

brendanx67 and others added 24 commits September 6, 2026 13:18
* Replaced File.ReadAllBytes in FdrScoresSidecar.TryRead and TryReadOverlay with
  TryWalkRecords, a shared header validation plus a 2,048-record buffered walk of
  the body. Same bytes, same order, same result, no format change
* Removes a 106 MB large-object allocation PER FILE from Stage 7's pool rebuild,
  which reads every run's pre-compaction 1st-pass sidecar from inside a
  Parallel.For - 47 GB of LOH arrays at 446 runs to land the ~648 K records per
  run that survive compaction. Stage 7's band is Server-GC retained COMMITTED
  memory rather than live data, so burst allocation of that shape inflates it
  directly
* Added TestFdrScoresSidecarChunkBoundaries over counts 0, 1, 1023, 1024, 1025,
  2047, 2048, 2049, 4096 and 4103. Every other sidecar test writes a handful of
  records and would pass against a reader that dropped or misaligned everything
  past the first buffer

NOT YET GATED: regression.ps1 cannot run while the 446-file measurement run holds
the box, so this has build + 603 unit tests + zero-warning inspection only. Run
-Dataset Stellar then -Dataset All before merging.

See TODO-20260901_osprey_stage5_reload_materialization.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
…ing the run

* Extracted BuildRescoredPool's loop body as MaterializeRescoredFile, which brings ONE
  file's list to its post-rescore state. Nothing in it reads another file's entries, so
  per-file calls in buffer order are the same work in the same order as the whole-run
  build - the equivalence a byte-identical streamed Stage 7 rests on
* Added RescoredEntries.StreamFiles: materializes each file on arrival, yields it, then
  clears and trims it. Peak is one file's survivors instead of every file's - measured
  2026-09-06 at 446 CHS runs, the resident pool IS the Stage 7 peak (381 files x ~648 K
  survivors x 274 B = 68 GB, against a 68.0 GB managed peak). Re-enumerable, because a
  fold-then-apply consumer needs two passes
* Made Value THROW after a stream. The lists are empty rather than unbuilt at that point,
  and PerFileEntries already names the failure a silent read would cause: one empty list
  per file, no exception, and a blib with no precursors
* Handed the per-file source over only when a survivor loader exists. A run that kept its
  resident buffer cannot rebuild a file it dropped, so streaming there would destroy the
  only copy on its way past - StreamFiles walks the resident buffer instead, which is what
  the OSPREY_STAGE6_STREAM_SURVIVORS=0 A/B oracle needs
* Added FileNames / FileCount that do not pull, and moved the stale-parquet scan onto them
  so a directory Stage 7 refuses outright no longer builds 289 M survivors it will discard

No consumer streams yet, so this run's output is unchanged; the Stage 7 middle is
converted next. TestStreamFilesDropsEachFileAndRefusesALaterValueRead pins the drop, the
re-enumeration and the refusal.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Added the two experiment-wide artifacts #4633 introduced to every list that owes them.
  <blib-stem>.1st-pass.retained_base_ids.bin is written by FirstPassFDR when Stage 6
  planning ends and read by every run's compaction; <stem>.1st-pass.stratum.json is the
  protein-compact stratum, split out because a different phase produces it (P7). Both were
  absent from Osprey-workflow.html, which #4633 did not touch, and from doc 00's Boundary
  2 -> 3 and 3 -> 4 lists
* Added <stem>.2nd-pass.fdr_scores.bin to SecondPassFDR's INPUTS on the workflow page. It
  was listed only as an output "(where no worker ran)", which leaves out the ordinary case
  where the rescore worker produced it and the join folds it instead of recomputing
* Cleared doc 00's in-flight item 2 and the Boundary 2 -> 3 caveat that told a reader to
  stage the whole cohort's envelopes because retained_base_ids.bin "does not exist on
  master". It does; the caveat was steering people around a fixed problem
* Corrected two relay counts that named a fixed number of experiment-wide files. There are
  four now, and a count in prose is what went stale here in the first place

Documentation only - no code, no behaviour change.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
…two pass-2 modes

* Removed OSPREY_PROTEIN_COMPACT_RETRAIN. It was the last way to reach a second-pass
  retrain, and the question it existed to measure is settled: retraining on the
  compaction-depleted pool is anti-conservative (1.57% true FDP at a nominal 1% on Stellar
  libdecoy entrapment against 0.92% for the pass-1 q, ~9% at 82 files). Issue #4484, closed
* Removed OSPREY_PASS2_QVALUE=transfer-compete for a related but distinct reason, recorded
  in doc 12 rather than only here: its competition ran over a TARGET-CONDITIONED subset -
  survivors selected by per-run q on the target side, decoys admitted only by base_id
  pairing - which strips the decoys that WON the first-pass competition and so improves
  pass-2 q with no added evidence. 1.96% true FDP accepting 34,325 on 82-file SEA-AD,
  against 1.53% accepting 37,624 for protein-compact: dominated on both axes
* Left protein-compact and transfer as the only modes. protein-compact carries the same
  paired-subsetting bias (#4581, open, with #4560 alongside) but also brings real
  protein-level evidence, which transfer-compete did not
* Collapsed the full-population arm of ComputePass2TransferCompeteFull: its stratum is now
  required rather than optional, so the mode ternaries and the null-guard on the stratum
  lookup go with it
* Recorded the consequence that simplifies what follows: THERE IS NO SECOND-PASS MODEL. The
  linear model the first pass trained is the pass-2 model unchanged - only the score
  distributions differ, because pass 2 runs on a subset

Also cleared the #4633 staleness these files carried: doc 12 still said the protein stratum
was "moving out of the model sidecar" and credited .1st-pass.model.json with carrying it.

No behaviour change on either surviving mode; the removed paths were unreachable without
setting the removed variables. 605 unit tests, zero-warning inspection.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Lifted TransferPerRunQ's loop body out as TransferOneFile, unchanged. It builds that run's
  own score->q tables from that run's own .1st-pass.fdr_scores.bin and classifies that run's
  survivors; the only state crossing runs is the analysis-wide experiment map every run
  shares, and a handful of counters. That is a fan-out computation running in a join, which
  is the thing to fix - #4438 already made the algorithm per-run, only its home is wrong
* Replaced six counter locals with a TransferTally struct, so the extraction did not need
  six ref parameters to be worth doing

Behaviour is unchanged, including one place it nearly was not: the whole-run loop counted a
run as done at the END of its body, AFTER the continue for an unreadable 1st-pass sidecar,
so such a run was never counted. Incrementing at the call site instead would have quietly
started counting it. The increment sits inside the method, where the loop had it.

The mode still runs in SecondPassFDR; only the seam exists. Moving the call into
Pass2PerFileWorker is next.

NOT COVERED BY THE GATE, and this is worth knowing before reading the green above:
regression.ps1 never sets OSPREY_PASS2_QVALUE, so all 15 legs run the default and no leg
exercises transfer at all. What is covered is the algorithmic core this extraction reuses
untouched - BuildScoreToQTable and AssignPerRunQ both have unit tests. The end-to-end oracle
is the 82-file SEA-AD arm, run last; the numbers it must reproduce are in the TODO. The
codebase has already paid for this gap once: transfer silently wrote no experiment sidecar
at all until 2026-09, and nothing caught it.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Added mode 10: transfer, and mean-best-2 + transfer, each run straight through and asserted
  to PRODUCE - output.blib, the analysis-wide output.2nd-pass.fdr_experiment.bin, and a
  per-run .2nd-pass.fdr_scores.bin for every input. Neither arm had ever run under the gate:
  every OSPREY_PASS2_QVALUE mention in regression.ps1 was a comment, so all legs ran the
  default. The cost of that is already recorded in the code - transfer reached production
  writing no experiment sidecar at all, and nothing could see it
* Asserted the artifact contract rather than values, on purpose. These two arms are still
  moving - protein-compact has improvements pending and the 82-file comparison needs
  re-running against #4593 - so a golden would freeze a number nobody has agreed on. What
  must not change silently is that a supported mode still runs and still produces
* Opted in ONE dataset, via AltPass2 on StellarLibDecoy. A leg written into the per-dataset
  loop inherits a 4x multiplier - four configs, not two acquisitions - so this would have
  added eight straight-through runs and wrecked the 1:05 that -Dataset All was just tuned to.
  StellarLibDecoy because library-SUPPLIED decoys are what the pass-2 comparison runs on real
  cohorts, so the arms meet the decoy provenance they are used with, at Stellar speed and off
  Astral's critical path
* Added -SkipAltPass2 for local iteration, alongside the existing -Skip* switches

Measured: 253.3s and 255.0s, so 8.5 min for both arms on StellarLibDecoy. That exceeds the
~5 min the second lane finishes early by, so under -Dataset All two arms move lane 2 onto the
critical path. One arm fits free, and the mean-best-2 arm runs transfer for pass 2 anyway
(protein-compact refuses a mean(best-N) first pass), so one leg does exercise both ideas.

regression.ps1 -Dataset StellarLibDecoy: PASSED, 23 legs, both new arms green on first run.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Root cause corrected first. The 446-file wall is NOT the Stage 6 -> 7 pool rebuild the
  earlier measurement was attributed to: on the --task SecondPassFDR leg the run dies inside
  `--input-scores: loading 446 per-file score parquet(s)`, at run 381 of 446, before Stage 7
  computes anything. The merge materializes every run's post-compaction survivors up front,
  and THAT is the 68.0 GB. BuildRescoredPool is never reached
* So the fix is at the hydrate, not at the pool build. CanStreamStage7Join admits the
  reconciled-input leg to the same per-run shape CanHydratePerRun already gives the rescore:
  the load publishes one EMPTY list per run and reads no rows, and Stage 7 rebuilds a run
  from its own .scores-reconciled.parquet + 1st-pass sidecar, folds it, and drops it
* RescoreHydration.RefillOneRunSurvivors is that rebuild - the three steps HydrateOneRun
  takes (load, overlay, compact) and not the fourth. A join runs no rescore, so reading each
  run's reconciliation.json to plan actions nobody executes would push ~6 MB of join-wide
  first_pass_base_ids through the call 446 times
* Every Stage 7 consumer now folds through RescoredEntries.StreamFiles: the fragment
  release, the pass-2 competition, protein FDR, the experiment-q re-clamp, all three blib
  gates and the FDRBench writer. Each was already a fold to O(distinct); none of them ever
  needed the pool, which is why the conversion is a change of source and not of algorithm
* The two facts a resident pool carries BETWEEN passes - the 2nd-pass sidecar overlay and
  the experiment-q floors - are re-applied per run instead, in that order, through
  AddPostMaterialize. A floor raises a value the overlay has just written, so the
  composition order is the correctness argument
* ClampExperimentQToBestRun is now fold-then-apply over the stream, which is the shape
  PercolatorEngine already split its two halves for

OSPREY_STAGE7_STREAM=0 keeps the resident pool as the byte-identity oracle, and carries a
validity-key term so an in-place A/B cannot satisfy itself by adopting the other arm's .blib.

NOT converted, and named rather than left to be discovered: --model-diagnostics. Its pass-2
builders index their files by position and revisit a file across two loops, so they need a
list. CanStreamStage7Join declines that leg outright rather than letting it stream and then
pull the whole pool back through .Value - the same peak by a longer route, with nothing in
the log to say so. The fold it wants is the accumulator the pass-1 report already uses.

Also fixed a defect this branch introduced: TransferOneFile's missing-features branch was a
`return` where the loop it was extracted from had a `continue`, so one entry without
reconciled features abandoned the rest of that run's survivors AND skipped the run's
FilesDone count.

605 tests (604 pass, 1 pre-existing skip), inspection zero-warning.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* OspreyReportWriter.WriteSummary still walked the pool, so a 446-run fold ran for 40 minutes
  and then threw at the per-replicate summary. The guard did its job - "RescoredEntries.Value
  was read after StreamFiles dropped the survivor pool", naming the consumer - rather than
  writing a report over 446 empty runs. Its loop consumes one run and retains nothing, so it
  folds unchanged
* Straight-through Stage 7 keeps its resident pool, and this is a property of the materializer
  rather than a preference. MaterializeRescoredFile is ONE-SHOT: it overlays the reconciled
  parquet and appends gap-fill rows, so a re-enumerating fold duplicates them - the same
  "run-once, and a failed build stays failed" rule RescoredEntries states for the whole-run
  build. Handing it over produced a straight-through Stellar run that exited 1. The leg with a
  repeatable source is the reconciled-input merge, and only it streams
* The pass-2 competition reported progress from ReadFile, which runs ONLY where a file has to
  be recomputed - so on the path this work exists to produce, every file answered by the
  worker, the counter never moved and the phase was one silent block: 654 s at 446 runs,
  starting immediately after the line announcing the fold. Moved to BeginFile, which is the
  call that always happens once per file
* StreamFiles takes a label and reports per run. On a streamed source every pass REBUILDS each
  run from disk, so folds that used to walk memory in seconds now run for minutes; unreported,
  they are silences in the middle of a multi-hour stage

Measured at 446 runs, same bed and recipe as the "before", through protein FDR:

  before  peak 68.0 GB managed / 70.5 GB private   floor RISING  +71 MB/file   killed at 381/446
  after   peak 18.5 GB managed / 33.5 GB private   floor FALLING  -4 MB/file   runs to completion

The floor is the result, not the peak: rising was O(files) accumulation, and it is gone.

605 tests (604 pass, 1 pre-existing skip), inspection zero-warning.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* The second-pass sidecar overlay writes an experiment q and the experiment-q floor raises
  it, so the two must run in the order they were added. Composing them the other way round
  applies a floor to a value about to be overwritten and reports q-values no run computed -
  a wrong number rather than a failure, which is why it needs a test and not a comment
* Asserts the other half too: a milestone with no per-file source REFUSES an overlay rather
  than accepting one nothing will ever invoke. That is the silent case - the stage believes
  it applied something it did not
* Covers MaterializeFile as well as StreamFiles, so the by-name accessor the streamed
  competition is driven through is not a second path with its own overlay semantics

606 tests (605 pass, 1 pre-existing skip).

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
…rew wrongly

* CanStreamStage7Join now also requires protein-compact. Every other pass-2 mode still
  computes its per-file half in Stage 7, over the whole pool - RestorePass1Scalars, the
  resident second pass, the projection sink's protein-q map all index it. Streaming
  underneath them does not make them per-run, it takes their input away: the fragment
  release streams first and drops the pool, and ComputeAndPersist then throws hours into
  Stage 7. Same predicate ComputeAndPersist branches on for frozenCompetition, so the two
  move together when transfer's per-run half moves to the worker
* ReadRetainedBaseIdsOrFail: the Stage 7 source THROWS instead of returning null. Returning
  null published a source-less milestone over the empty per-run lists the loader had already
  created, so the stage folded 446 empty runs, logged "No entries pass FDR threshold.
  Creating empty blib." and EXITED 0. The two sites decide on different evidence - the
  loader header-probes, this reads the body, minutes apart on a large cohort - so it is
  reachable with nothing wrong upstream. The sidecar's own reader documents absence as fatal
* The resident 2nd-pass write loop is skipped on the streamed arm, and this is a
  correctness skip. It is reachable on the DEFAULT mode when nothing was recomputed, and a
  streamed run with a worker answer is rebuilt WITHOUT the 1st-pass overlay - that answer is
  where its scalars come from - so serializing those entries would overwrite every correct
  sidecar in the cohort with Score 0.0 and default experiment values. Skipping writes nothing
  that is missing: on this leg the file is PerFileRescoring's output and this task's input
* haveSecondPass now reads the SAME evidence the fold uses - a PerFileRescoring validity
  stamp - instead of a format probe. They disagree exactly where it hurts: a sidecar written
  by a previous Stage 7 is format-current but stamped SecondPassFDR, so a probe said "skip
  the 1st-pass overlay" while the competition said "no worker answer, recompute", and it
  recomputed from blank rows. That is the OSPREY_STAGE7_STREAM=0 -> 1 A/B this branch adds
* The per-run rebuild resolves the RECONCILED sibling. perFileParquetPaths holds whatever
  --input-scores named, and the documented fallback form names <stem>.scores.parquet - so
  rebuilding verbatim gave this arm pre-reconciliation rows while the resident arm on the
  same command line read the reconciled file. Two .blib files from one command line, and the
  byte-identity oracle could not see it because both arms were self-consistent
* The 1st-pass experiment map is Lazy and goes through LoadPass1ExperimentRecords. On the
  default path RefillOneRunSurvivors never reads it, and it was ~400 MB resident for the
  whole stage; the raw ReadMap also returns null on an unreadable file, where the wrapper
  stops
* StreamFiles marks _streamed BEFORE the yield. A consumer that breaks out of the walk left
  it false with runs already materialized, so a later Value read sailed past the guard and
  returned one populated run plus N-1 empty lists
* regression.ps1 mode 10 SAVES and restores OSPREY_PASS2_QVALUE / OSPREY_EXPERIMENT_AGG
  instead of deleting them, and records a non-zero exit as a mode-10 failure. It sits before
  modes 4, 2, 5-9 and every later dataset, so a developer's exported arm was silently
  deleted and the rest of the suite ran the default while reporting the arm as passing
* Restored the UTF-8 BOM that sed stripped from three files, and the three em dashes this
  branch introduced are ASCII hyphens

606 tests (605 pass, 1 pre-existing skip), inspection zero-warning.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
…ial sidecar walk

* mode 3 now asserts the marker line for the per-run fold. A resident Stage 7 and a streamed
  one produce identical bytes BY DESIGN - that is the whole claim - so nothing this gate
  compares can tell them apart, and doc 00's rule for exactly that case is to assert the path
  rather than trust the bytes to reveal it. Without it, a change that silently disqualifies
  the streamed arm leaves every leg green while the O(runs x entries) peak returns
* Scoped to the datasets that can stream, which is the finding as much as the fix:
  CanStreamStage7Join declines under --model-diagnostics, and this suite sets it on every
  dataset but plain Stellar - so three of four exercise only the resident arm. Recorded in
  the TODO rather than papered over by widening the assertion into a failure
* FdrScoresSidecar.TryWalkRecords no longer returns false after handing records to its
  caller. Chunking the read (4b9df2a) moved the file access INSIDE the walk, and with it
  the guarantee the OOM-filtered catch had rested on: File.ReadAllBytes and the header checks
  used to complete before any entry was touched, so `false` could only ever mean "nothing
  applied". A mid-walk IO fault now leaves half a file's values on the caller's entries, and
  OverlayPass2SidecarOntoFile's caller treats false as non-fatal ("protein FDR will use stale
  1st-pass q-values") - shipping a run that is half pass-1 and half pass-2. It throws instead,
  naming the file and the count

regression.ps1 -Dataset Stellar PASSED, 16 legs - the new one reads
"mode3 (streamed join): PASS (per-run fold, no all-runs pool)".
606 tests, inspection zero-warning.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Dropped the standalone transfer arm. protein-compact REFUSES a mean(best-N) first pass, so
  the mean-best arm runs transfer for pass 2 regardless - one leg exercises both ideas, and
  the standalone arm bought a second run of the same pass-2 code while varying the first-pass
  aggregation away from the one that needs covering. Measured 223.1s of a 01:19:30
  Perf/Regression wall that has to come in under 75 minutes. It stays reproducible by hand
  (OSPREY_PASS2_QVALUE=transfer, OSPREY_EXPERIMENT_AGG unset), which is what you want when the
  leg reds and you need to know which of the two ideas moved
* The leg now asserts the arm ENGAGED before asserting anything about what it produced. It
  did not, and that was a hole in exactly the shape this leg exists to close: every file check
  in it passes on a run with both variables ignored, because the DEFAULT mode writes the same
  set of files. A green test for an arm that never ran is what let `transfer` reach production
  writing no experiment sidecar in the first place
* Marker lines, not values, per doc 00's rule for a contract output cannot distinguish - the
  banners Program.cs and ComputeAndPersist already print. BOTH halves, because the arm is the
  pair: a run that took transfer but ignored OSPREY_EXPERIMENT_AGG would satisfy a one-line
  check and cover half of what this protects
* Negative control, so the assertion is not vacuously true: neither marker appears in a
  default run's log (0 and 0 across the 446-run CHS run), which instead reports
  "Experiment aggregation: max (default - best observation per unit)"

regression.ps1 -Dataset StellarLibDecoy PASSED, 22 legs, mode 10 green with both markers.
The leg is now 216.9s where the two arms were 446.8s.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Added ModelDiagnosticsData.Accumulator.BuildPass2, so SecondPassFDR builds the report from folded reductions instead of the resident survivor pool
* Split the co-assignment panel into caller-driven phases so its cutoff pass rides along with the fold, costing two stream passes rather than three
* Removed the ModelDiagnostics term from CanStreamStage7Join, so mode 3 now exercises the streamed join on all four datasets instead of one

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Checked for the pass-1 sidecar before folding rather than leaving it to the
  report writer, which is called after both stream passes have already run
* Without this a bed with no pass-1 product rebuilt all 446 runs twice and
  then discarded the result; the resident path already checked first

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Removed an orphaned comment left behind when the chain-vs-straight
  diagnostics comparison was dropped, and recorded why it was dropped
* Reverted OSPREY_STAGE7_STREAM from the gate's A/B token set: it arms no
  resident-pool guard, so admitting it weakened the named-token ratchet
* Scoped the streamed-join assertion to CanStreamStage7Join's own terms,
  so a projection-off or transfer run is no longer failed for complying
* Narrowed the diagnostics try/catch to the co-assignment panel, so a
  refused panel no longer discards the eight correctly folded cards
* Verified run order in the fold pass, which indexes the per-file counts
  and cross-run streams and not only the panel's boundary
* Reused BuildModelDiagnosticsAccumulator and FeedModelDiagnostics instead
  of re-implementing them, and read the pass-1 product once
* Guarded the co-assignment builder against post-seal mutation and a
  second seal, both newly reachable once the phases became public
* Covered the protein-compact stratum in the byte-identity test, the only
  configuration the streamed arm runs in production
* Corrected the doc claim that OSPREY_STAGE7_STREAM is an in-place A/B,
  and a stale resume error blaming --model-diagnostics

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Made a callback rejection mid-walk throw like a mid-read failure, so
  TryWalkRecords no longer returns false with records already overlaid
* Cached the experiment sidecar load per generation, keyed on the file's
  identity, so the streamed pass-2 overlay stops deserializing it once per
  run on a resume that publishes no experiment scope

Both arrived on this branch rather than from master, so merging would have
shipped them.

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Stated that the diagnostics report must be derivable from a pass's own
  sidecars alone, by a task that runs no analysis
* Named the three costs of welding a report to its producing phase, one of
  which is inheriting that phase's memory shape instead of the reduction's
* Required the two passes be independently derivable

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* Stated the requirement as re-running with the flag doing only diagnostics
  work, which is P15's forward scan rather than a special task
* Added the corollary that diagnostics work belongs in the FDR tasks, since
  anything captured in a fan-out task is lost to the pay-later path
* Made completeness half of the test: the same report, not just a report

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* The retrain fallback in ComputePass2Resident is now an error naming #4484.
  It shipped output by the method the project removed for correctness, on a
  warning, where the standing rule is hard-fail
* Deleted the projection 2nd-pass branch, unreachable since the retrain went,
  and the !config.ModelDiagnostics term that read as though the flag reroutes
  the analysis
* pass2Contributions is null in every configuration, so it deletes end to end.
  BuildPass2 keeps the parameter: its cards are re-sourced from the frozen
  pass-1 model next, and the contract stays under test
* SecondPassFDR folds its diagnostics product from a completed second pass,
  symmetric with pass 1, ahead of the marker wipe that would clear the stamps
  entitling it to adopt that pass
* --task ModelDiagnostics invokes whichever folds are missing instead of
  refusing and naming a producer, which is P15's resume applied to the
  diagnostics outputs (P16)
* Resolved the pass-2 experiment records once per overlay instead of once per
  run per pass, lazily so the join still reads them after the competition

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* regression.ps1 mode 11 deletes both diagnostics products from a completed
  run and asserts the folds run, the join's markers do NOT, and the products
  come back identical - the P16 scenario no other leg presented
* It caught a real defect on its first run: the pass-2 fold reported pass-1
  q-values under a pass-2 heading whenever the pool was resident, because the
  streamed overlay is a no-op there and the join's compute had always stamped
  the pool itself
* The pass-1 model/CAL views cannot survive the pay-later path at all; they
  are excluded BY NAME so the leg reds on any other divergence

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* On --task SecondPassFDR the product was not a declared output, so a
  completed cohort had nothing outstanding, the driver skipped the task, and
  the pay-later fold could never run - measured as "skipping (outputs valid)"
* The reason it was undeclared has expired: declaring it used to force a
  pass-2 re-join on every resume, and the fold arm makes it a bounded
  reduction instead
* The predicate now excludes its own product, as pass 1's does; without that
  it reads its own missing product as an outstanding input and always declines
* Verified at 446 files: exit 0 in 18 min at 14.0 GB managed / 22.1 GB
  private, below the 18.2/29.0 the same work costs inside the join, with the
  streamed marker and no analysis of any kind

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* It said "every leg of every dataset" while this gate's own mode-3
  SecondPassFDR phase now takes the streamed join - the file already said so
  twelve lines below, so it contradicted itself and printed the stale half
* The projection is now confirmed rather than projected: the model predicts
  92.3 GB at 446 and 91.1 GB private was measured

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
* OSPREY_STAGE7_STREAM=0 now requires OSPREY_ALLOW_UNFIXED_RESIDENT=
  stage7-stream-off, like the two sibling A/B oracles. It was untokened only
  because there was no alternative to choose; the streamed join made it a
  choice, and a choice is what the ratchet exists to record
* The guard refuses ONLY the chosen case. A run that could not stream anyway
  is not refused - demanding a token there would make the default path
  unusable, which is the blanket amnesty the named-token scheme replaced
* That case now WARNS instead, naming the shape, the issue and the cost model
  for its own file count. The deficiency was previously stated only in
  regression.ps1's summary, which prints for us and never for the operator
  whose run is about to take it
* Guard placed first in Run: the diagnostics fold returns early and is itself
  a resident path when the join cannot stream, so a later placement silenced
  the warning on exactly the run that measured 91.1 GB at 446 files
* streamingAvailable is a parameter so the refusal is unit-testable; computing
  it internally makes every test process answer false and pass vacuously

See TODO-20260906_osprey_stage7_lean_row.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnrzSdBPwztyJw1hfErF55
@brendanx67 brendanx67 added the osprey Osprey / OspreySharp DIA proteomics search tool label Sep 8, 2026
* Derived CanStreamStage7Join from the reconciled parquets on disk, retiring
  the --task SecondPassFDR proxy that made the ordinary run resident
* Gave both straight-through arms a per-run source, built from the whole-run
  loops' own per-file halves so the arms cannot drift
* Keyed the O(files) warning on the milestone rather than the predicate, and
  asserted the per-run fold per leg in regression.ps1

See TODO-20260908_osprey_stage7_straightthrough_stream.md in pwiz-ai/todos

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QwgkvKC6UwFUNieMDvpw57
@brendanx67
brendanx67 changed the base branch from Skyline/work/20260906_osprey_stage7_lean_row to master September 8, 2026 21:19
brendanx67 and others added 2 commits September 8, 2026 15:15
…08_osprey_stage7_stream_and_scores_retirement

# Conflicts:
#	pwiz_tools/Osprey/Osprey.Core/ResidentPaths.cs
#	pwiz_tools/Osprey/Osprey.Tasks/PerFileRescoreTask.cs
#	pwiz_tools/Osprey/Osprey.Tasks/ScoringTaskShared.cs
#	pwiz_tools/Osprey/Osprey.Tasks/SecondPassFdrTask.cs
#	pwiz_tools/Osprey/Osprey.Test/ResidentPoolGuardTest.cs
#	pwiz_tools/Osprey/docs/00-pipeline-architecture.md
#	pwiz_tools/Osprey/regression.ps1
* Deleted the flag, OspreyConfig.InputScores and Program.ResolveInputScores
* Made every task take -i and derive each run's parquets from the input stem
* Collapsed the membership predicates onto --task, which was the only seam deciding them
* Accepted an absent input whose scores parquet is on disk - a staged join node's state

See TODO-20260908_osprey_input_scores_retirement.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 22:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core CLI semantics and Stage-7 streaming admission across multiple execution paths, so it needs careful human validation beyond what can be confidently inferred from the diff.

Pull request overview

This PR makes the streamed (per-run) Stage-7 join the default across straight-through and resume paths by deriving CanStreamStage7Join admission from on-disk reconciled parquet state instead of the --task SecondPassFDR / ExpectReconciledInput proxy, and retires --input-scores in favor of a single “data-file-stem” input shape (-i / --input-list). It also adds/adjusts regression gates and unit tests to assert the streamed join marker per leg.

Changes:

  • Make Stage-7 streaming admission depend on a single disk predicate (AllReconciledParquetsCurrent + ParquetScoreCache.IsCurrentReconciledSurvivorSubset) and provide per-run sources for cold + resume paths.
  • Retire --input-scores; all tasks now take -i / --input-list and derive artifact paths from the input stem.
  • Update tests/docs and regression.ps1 to assert the streamed Stage-7 join marker across in-process legs and the HPC chain.
File summaries
File Description
pwiz_tools/Osprey/regression.ps1 Updates HPC chain invocation to use -i/--input-list semantics and asserts streamed Stage-7 join markers per leg.
pwiz_tools/Osprey/Osprey/RescoreWorker.cs Updates worker entry-point documentation to match the --task-driven model.
pwiz_tools/Osprey/Osprey/Program.cs Unifies input existence validation for all tasks and allows “artifact-only” inputs (scores/reconciled parquets) when raw data is absent.
pwiz_tools/Osprey/Osprey/OspreyCommandArgs.cs Removes --input-scores and documents/implements --input-list as the scalable alternative to long -i lists.
pwiz_tools/Osprey/Osprey/AnalysisPipeline.cs Removes worker-mode normalization that synthesized InputFiles from InputScores (now obsolete).
pwiz_tools/Osprey/Osprey.Test/ResidentPoolGuardTest.cs Adds assertions that Stage-7 streaming admission is not vacuously true and remains disk-derived.
pwiz_tools/Osprey/Osprey.Test/ProgramTests.cs Updates CLI plumbing tests to reflect single input kind (-i/--input-list) and retired --input-scores.
pwiz_tools/Osprey/Osprey.Test/PipelineMembershipTest.cs Updates membership truth-table setup to be task-flag derived (no input-kind seam).
pwiz_tools/Osprey/Osprey.Test/LibraryFragmentReleaseTest.cs Aligns test legs with --task-derived membership, removing --input-scores-based cases.
pwiz_tools/Osprey/Osprey.Test/IOTest.cs Adds coverage for the new reconciled-survivor-subset currency predicate against real artifacts.
pwiz_tools/Osprey/Osprey.Tasks/SecondPassFdrTask.cs Keys resident-join warnings to the actual streaming milestone and removes duplicated staleness checks.
pwiz_tools/Osprey/Osprey.Tasks/ScoringTaskShared.cs Adds “starts after Stage 4” membership helper and disk-based Stage-7 streaming admission logic.
pwiz_tools/Osprey/Osprey.Tasks/RescoreHydration.cs Documents the reduced role of SyntheticInputFromParquet now that --input-scores is retired.
pwiz_tools/Osprey/Osprey.Tasks/PipelineContext.cs Updates config mutation-contract commentary (minor doc mismatch noted in review).
pwiz_tools/Osprey/Osprey.Tasks/PerFileScoringTask.cs Switches join-only hydration to use stem-derived scores paths and task-based “starts after Stage 4” detection.
pwiz_tools/Osprey/Osprey.Tasks/PerFileRescoreTask.cs Adds per-run sources for straight-through run and straight-through resume so Stage-7 fold can stream by default.
pwiz_tools/Osprey/Osprey.Tasks/FirstPassFdrTask.cs Simplifies inclusion predicate after removing the input-kind seam.
pwiz_tools/Osprey/Osprey.Tasks/BlibOutputWriter.cs Always uses InputFiles for SpectrumSourceFiles rows (no parquet->synthetic input conversion).
pwiz_tools/Osprey/Osprey.IO/ParquetScoreCache.cs Adds IsCurrentReconciledSurvivorSubset to unify “readable survivor-subset reconciled parquet” checks.
pwiz_tools/Osprey/Osprey.Core/ResidentPaths.cs Updates documentation around the shrinking “no-stream alternative” exemption set.
pwiz_tools/Osprey/Osprey.Core/OspreyConfig.cs Removes InputScores and updates the “task vs input kind” narrative for membership.
pwiz_tools/Osprey/Documentation/Help/en/CommandLine.html Removes --input-scores help and updates HPC examples to --input-list + -i.
pwiz_tools/Osprey/docs/README.md Updates documentation index wording to remove --input-scores references.
pwiz_tools/Osprey/docs/DIVERGENCES.md Updates divergence notes to reflect “effective parquet prefers reconciled” wording.
pwiz_tools/Osprey/docs/20-command-line.md Updates CLI docs and HPC examples to -i/--input-list and stem-derived artifacts.
pwiz_tools/Osprey/docs/19-testing.md Updates docs to reflect ProgramTests no longer covers --input-scores directory expansion.
pwiz_tools/Osprey/docs/16-determinism.md Updates determinism notes to reflect caller-specified ordering via --input-list.
pwiz_tools/Osprey/docs/15-hpc-scoring-split.md Updates operational contract: single input kind, stem-derived artifacts, and ordering responsibility.
pwiz_tools/Osprey/docs/11-boundary-overrides.md Updates boundary override docs to reflect -i (stem-derived artifacts) instead of --input-scores.
pwiz_tools/Osprey/docs/00-pipeline-architecture.md Updates architecture docs to reflect Stage-7 streamed join being route-independent and default where admissible.
Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pwiz_tools/Osprey/Osprey.Tasks/PipelineContext.cs Outdated
* Corrected the PipelineContext mutation contract - nothing writes config at pipeline entry now
* Fixed three more comments the retirement had left stale, found by re-checking for the same claim
* Marked ResolveSidecarBasePath's parquet fallback unreachable rather than deleting it mid-review

See TODO-20260908_osprey_input_scores_retirement.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
@brendanx67 brendanx67 changed the title osprey: Made the straight-through Stage-7 join fold run by run osprey: Made the Stage-7 join fold run by run on every route, and retired --input-scores Sep 9, 2026
brendanx67 and others added 5 commits September 8, 2026 22:06
* Made a missing .scores-reconciled.parquet a hard failure on every arm, and
  deleted EffectiveScoresPathFromScoresPath so the parquet a task reads is
  decided by the task rather than by a disk probe
* Refused --task FirstPassFDR admission to the Stage 7 streamed join, which a
  re-run over a completed directory silently satisfied
* Refused duplicate input stems, gated the parquet stand-in on tasks that start
  after Stage 4, and made the reconciled-shape probe answer instead of throwing
* Asserted in regression.ps1 that no consumer pulled the whole pool

See TODO-20260908_osprey_stage7_straightthrough_stream.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
* The doc block described the deferred whole-run build and its [STAGE-WALL]
  line, but had been left attached to BuildRunPerRunSource, which carries its
  own summary and does neither

See TODO-20260908_osprey_stage7_straightthrough_stream.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
* Select-String is case-insensitive by default, so ': FAIL' matched the
  ': fail' in a 'WARN: failed to prune' line and reported a lane that exited 0
  with all 23 legs passing as 1 FAIL, failing the whole gate
* Counted the leg lines case-sensitively and surfaced WARN lines separately

See TODO-20260908_osprey_stage7_straightthrough_stream.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
* The unreachability argument conflicts with the SUPPORTED state where a run's
  file_name has no input_files stem, which is the case that branch answers
* Led the flag-retirement rationale with the basenames framing: every per-run
  artifact is <stem>.<suffix>, so --input-scores named the same set redundantly

See TODO-20260908_osprey_input_scores_retirement.md in pwiz-ai/todos

Co-Authored-By: Claude <noreply@anthropic.com>
@brendanx67
brendanx67 merged commit 794cb6a into master Sep 9, 2026
17 checks passed
@brendanx67
brendanx67 deleted the Skyline/work/20260908_osprey_stage7_stream_and_scores_retirement branch September 9, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

osprey Osprey / OspreySharp DIA proteomics search tool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants