Skip to content

fix(stats): derive the published totals from the ledger instead of publishing a false zero - #9967

Merged
JSONbored merged 1 commit into
mainfrom
fix/public-stats-ledger-totals
Jul 30, 2026
Merged

fix(stats): derive the published totals from the ledger instead of publishing a false zero#9967
JSONbored merged 1 commit into
mainfrom
fix/public-stats-ledger-totals

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #9963

The root cause is not what the issue assumed

The issue attributed this to a D1/Postgres store split. It isn’t — I traced it end to end on edge-nl-01 and the store is fine.

totals.* is gated on LOOPOVER_PUBLIC_STATS_REPOS, which is unset on the Orb, so projects is [] and getPublicStats takes its own early-return branch and sums nothing. The ledger-derived blocks (reviewParity, automation-rate) honour no such allowlist. Two halves of one payload, two different gates.

I ruled out both portability traps that produce exactly this symptom:

  • instr() genuinely does not exist in Postgres (ERROR: function instr(text, unknown) does not exist) — but pg-dialect.ts rewrites it. Verified by running the real translateInstr/translateFunctions over PUBLISHED_PR_KEYS.
  • The unaliased subquery in FROM (...) is accepted by PG16.

The disposition query returns 9,657 against the live store. Nothing was broken about the query, the dialect layer, or the database.

The fix

Publishing a number known to be false is the one option that is definitely wrong, so the aggregate now comes from the same ledger the parity block reads when no repo is allowlisted.

The privacy intent is untouched, because it is about naming repos: byProject still requires the allowlist and stays empty, so no repo that was not opted in is identified. totals carries no repo identity at all.

DISTINCT (repo, number) deliberately — decision_records holds one row per verdict, 2,354 rows over 987 pull requests on the production Orb. Counting rows would overstate handled by more than 2× and reintroduce the same class of cross-surface disagreement this removes. Live figures: 987 handled / 488 merged / 497 closed.

An existing test that was passing dishonestly

"skips the own-ledger queries ... when the allowlist is empty" threw on any non-Orb query. My change deliberately makes one — and the test still passed, because safeAll swallowed the throw and the result degraded to the same zeros.

Narrowed to the contract that actually matters: the per-project queries still must not run without an allowlist, and the aggregate read is asserted to carry no GROUP BY, which is what keeps repo identity unpublished.

Tests

The regression (ledger-backed handled, byProject still empty), the verifier’s own invariant across several ledger sizes (handled can never be 0 while the ledger holds verdicts), no-ledger keeps its zeros rather than inventing a figure, and SUM()NULL from an empty join reading as 0 rather than NaN.

handled is typed non-nullable because COUNT(*) cannot be NULL — that removed a ?? 0 no input could reach.

Full suite: 25,894 passing, zero failures. Changed-line coverage: zero uncovered statements, zero uncovered branches.

…blishing a false zero

An Orb reported `totals.handled: 0` beside a `reviewParity` block describing
2,123 verdicts from the same deployment's ledger. The public verifier caught it
within minutes of the flag going on.

The cause is NOT the D1/Postgres store split the issue hypothesised -- I traced
it end to end on edge-nl-01 and the store is fine. `totals.*` is gated on
LOOPOVER_PUBLIC_STATS_REPOS, which is unset there, so `projects` is empty and
getPublicStats takes its own early-return branch and sums nothing. The
ledger-derived blocks (reviewParity, automation-rate) honour no such allowlist.
Two halves of one payload, two different gates.

Ruled out both portability traps that produce the same symptom: `instr()` does
fail on Postgres, but pg-dialect.ts rewrites it (verified by running the real
translator over PUBLISHED_PR_KEYS), and PG16 accepts the unaliased subquery. The
disposition query returns 9,657 against the live store.

Publishing a number known to be false is the one option that is definitely
wrong, so the aggregate now comes from the SAME ledger the parity block reads
when no repo is allowlisted. The privacy intent is untouched, because it is
about NAMING repos: `byProject` still requires the allowlist and stays empty, so
no repo that was not opted in is identified. `totals` carries no repo identity.

DISTINCT (repo, number) deliberately -- decision_records holds one row per
VERDICT, 2,354 rows over 987 pull requests on the production Orb, so counting
rows would overstate `handled` by more than 2x and reintroduce the same class of
cross-surface disagreement this removes.

The pre-existing "must not query an unscoped own-ledger" test encoded the old
contract and passed only because safeAll swallowed its throw. Narrowed honestly:
the per-PROJECT queries still must not run, and the aggregate read is asserted
to carry no GROUP BY.

Closes #9963
@JSONbored JSONbored self-assigned this Jul 30, 2026
@loopover-orb

loopover-orb Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.00%. Comparing base (b2d02dd) to head (efb4b59).
⚠️ Report is 6 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/review/public-stats.ts 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9967      +/-   ##
==========================================
- Coverage   91.87%   91.00%   -0.88%     
==========================================
  Files         928      928              
  Lines      113697   113707      +10     
  Branches    27421    27425       +4     
==========================================
- Hits       104460   103476     -984     
- Misses       7936     9125    +1189     
+ Partials     1301     1106     -195     
Flag Coverage Δ
backend 94.10% <90.00%> (-1.56%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/review/public-stats.ts 97.61% <90.00%> (+0.20%) ⬆️

... and 3 files with indirect coverage changes

@JSONbored
JSONbored merged commit dcbfe2a into main Jul 30, 2026
7 of 8 checks passed
@JSONbored
JSONbored deleted the fix/public-stats-ledger-totals branch July 30, 2026 22:41
JSONbored added a commit that referenced this pull request Jul 31, 2026
#9963 was an Orb publishing `totals.handled: 0` in the same payload as
`reviewParity.verdicts: 2123`, both derived from the same ledger. #9967 fixed
the aggregate and covered it well -- but per surface, which is the blind spot
the defect lived in. Both halves were individually correct and individually
green while production served the contradiction, and the verifier's own source
says why per-surface testing cannot catch this class:

  "the two are computed by different code over the same ledger, which is the
   divergence no amount of per-surface testing catches, because each surface is
   individually self-consistent."

`getPublicStats` and `loadReviewParityRollups` remain separate reads with
separate gating, so nothing stopped the next change to either from
reintroducing the disagreement with every existing test still green.

These build BOTH halves over one seeded ledger and compare them through the
verifier's own `checkStatsParity` -- the function that printed the original FAIL
against the Orb -- rather than restating its rule, so the test cannot drift from
the tool that decides whether production is publishing a contradiction.

Covers the case a hand-written equality assertion would get wrong: a re-decided
pull request appends a verdict row, so parity counts 3 while `handled` counts 2
pull requests. That is consistent, not a contradiction, and only parity
EXCEEDING all-time handled is the accounting error.

Mutation-tested against #9967's fix: reverting the ledger fallback fails exactly
the two behavioural assertions, while the empty-ledger case and the
failing-direction guard correctly stay green.

Closes #9971
loopover-orb Bot pushed a commit that referenced this pull request Jul 31, 2026
… it (#9972)

#9963 was an Orb publishing `totals.handled: 0` in the same payload as
`reviewParity.verdicts: 2123`, both derived from the same ledger. #9967 fixed
the aggregate and covered it well -- but per surface, which is the blind spot
the defect lived in. Both halves were individually correct and individually
green while production served the contradiction, and the verifier's own source
says why per-surface testing cannot catch this class:

  "the two are computed by different code over the same ledger, which is the
   divergence no amount of per-surface testing catches, because each surface is
   individually self-consistent."

`getPublicStats` and `loadReviewParityRollups` remain separate reads with
separate gating, so nothing stopped the next change to either from
reintroducing the disagreement with every existing test still green.

These build BOTH halves over one seeded ledger and compare them through the
verifier's own `checkStatsParity` -- the function that printed the original FAIL
against the Orb -- rather than restating its rule, so the test cannot drift from
the tool that decides whether production is publishing a contradiction.

Covers the case a hand-written equality assertion would get wrong: a re-decided
pull request appends a verdict row, so parity counts 3 while `handled` counts 2
pull requests. That is consistent, not a contradiction, and only parity
EXCEEDING all-time handled is the accounting error.

Mutation-tested against #9967's fix: reverting the ledger fallback fails exactly
the two behavioural assertions, while the empty-ledger case and the
failing-direction guard correctly stay green.

Closes #9971
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.

selfhost(stats): an Orb reports totals.handled = 0 while its ledger holds 2,123 verdicts

1 participant