A valid chain can still have a hole in it

Measured against real production history while validating the ancestry
surface: `aj ewing / total_bases / over / 0.5` on 2026-08-28 carries a valid
ORIGIN from the 18:10 cohort AND a row from the 19:03 cohort that was stamped
for publication and never recorded an action.

The chain is genuinely valid and the partial row is correctly excluded from it
— that part of the contract held. But answering LINEAGE_AVAILABLE and nothing
else presents an incomplete chronology as a complete one, which is the exact
failure this surface exists to make visible, one grain finer than a whole Read.

The response now carries `chronology_complete` and `unrecorded_publications`.
The state is unchanged, because the chain that exists really is valid; what
changes is that the gap is disclosed instead of smoothed over.

Suite 397/5,498/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
Kev
2026-08-30 22:19:50 -04:00
parent 930d526b01
commit 1bdc3baec7
3 changed files with 36 additions and 0 deletions
+13
View File
@@ -136,9 +136,22 @@ function classifyAncestry(rows) {
});
}
const recaptures = chain.filter((r) => r.lineage_action === readLineage.LINEAGE_ACTION.RECAPTURE);
// A VALID CHAIN CAN STILL HAVE A HOLE IN IT.
//
// Measured on production: `aj ewing / total_bases / over / 0.5` on 2026-08-28
// carries a valid ORIGIN from the 18:10 cohort AND a row from the 19:03
// cohort that was stamped for publication and never recorded an action. The
// chain is genuinely valid and the partial row is correctly excluded from it
// — but answering LINEAGE_AVAILABLE alone would present an incomplete
// chronology as a complete one. The gap is disclosed instead of smoothed
// over; it is the same failure this surface exists to make visible, just at
// a finer grain than a whole Read.
const unrecorded = attempted.filter((r) => !isValidAction(r)).length;
return Object.freeze({
state: ANCESTRY_STATE.LINEAGE_AVAILABLE,
reason: null,
chronology_complete: unrecorded === 0,
unrecorded_publications: unrecorded,
read_id: chron.read_id,
read_natural_key: chain[0].read_natural_key,
revision_count: chron.revision_count,