Join the conversation

Join the community of Machine Learners and AI enthusiasts.

Sign Up
SeaWolf-AIΒ 
posted an update 5 days ago
Post
2881
πŸ” The attention mask stopped being an audit.

An autoregressive model must not let position t depend on anything after t. Everyone checks this by inspecting the causal mask β€” but hybrid stacks now mix attention with state-space scans, and a scan has no mask. Every mask can be correct while information leaks through scans, aggregations, or normalization.

βš™οΈ So we test the property directly. Two inputs identical except at the last position, two forward passes, compare each layer's prefix, report the first layer that moves. No training, no gradients, no accelerator β€” seconds on CPU.

πŸ“Š Across 192 injected faults on eight checkpoints, mask inspection detected 0. The per-layer audit localized 192/192 to the exact layer.

🎯 Then we read the source before running anything. In transformers 5.7.0, the reference chunked scan reduces the inter-chunk recurrence over the input chunk axis; zamba2 and nemotron_h reduce over the output chunk axis. One axis. The dynamic audit confirmed the prediction exactly: Zamba2-1.2B leaks from length 256, its declared chunk size, and Nemotron-H-8B from 128, its declared chunk size. Bamba, Falcon-H1, Granite-4.0-H, Mamba2 and RecurrentGemma came back clean.

⚠️ Scope: the defect is on the PyTorch chunked-scan path, which runs whenever the fused kernels are absent β€” CPU, CI, stock installs. We could not build those kernels, so the fast path is untested and open. That caveat cuts both ways: a model can pass every fused-kernel test and still leak the moment it runs without them.

πŸ§ͺ AX-RAY now carries this as its own axis. 39 models scored across causal, white-box and behavioral axes: 21 A, 3 B, 1 C, 14 F β€” with exactly 2 Causal-LEAK verdicts, the two the paper predicted. Badges separate a weights-level audit from an API-only one, so the two never get read as the same claim.

πŸ“„ https://arxiv.org/abs/2608.22876
πŸ”¬ FINAL-Bench/AX-RAY
πŸ€— The Mask Is Not the Model: Auditing Prefix Invariance in Attention, State-Space, and Hybrid Sequence Models (2608.22876)

Your source read reproduces exactly. The window it opens has already closed upstream.

I pulled the sdists and diffed the inter-chunk recurrence rather than trusting the
description. In 5.7.0 the four models you scored clean all do this:

decay_chunk = torch.exp(segment_sum(pad(A_cumsum[:, :, :, -1], (1, 0))))
decay_chunk = decay_chunk.transpose(1, 3)
new_states = (decay_chunk[..., None, None] * states[:, :, None, ...]).sum(dim=1)

zamba2 and nemotron_h skip the transpose and permute the states instead:

states_permuted = states.permute(0, 2, 1, 3, 4)
result = (decay_chunk[..., None, None] * states_permuted[:, :, None, ...]).sum(dim=2)

Same triangular decay matrix, the other index contracted. That is your one axis, and
you called it off the source before running anything.

It is version-bound, and the boundary is sharp

Every 5.x sdist from 5.7.0 up, grepped for states_permuted:

5.7.0   2026-04-28   divergent
5.8.0                divergent
5.10.0               divergent
5.12.0               divergent
5.13.0 / 5.13.1      divergent
5.14.0 / 5.14.1      divergent    2026-07-16
5.15.0  2026-08-10   reference
5.15.1  2026-08-19   reference

In 5.15.0 both files carry transpose(1, 3) and sum(dim=1). I normalized the 23
lines around the recurrence in all six models and diffed them pairwise: zero
difference. zamba2 and nemotron_h now run the identical code that mamba2, bamba,
falcon_h1 and granitemoehybrid ran when your audit returned them clean.

Repaired 16 days before your post.

Which is a problem for the board, not for the paper

results.jsonl is 39 rows and has no version field. Keys are model_id, dhs, grade,
causal_verdict, leaked, causal_score, xray, behavior, badges, categories, arch,
params_M, ts, created. Zyphra/Zamba2-1.2B and nvidia/Nemotron-H-8B-Base-8K both
sit at grade F, dhs 28.6, causal_score 15, μΈκ³Όμ•ˆμ „ flagged μœ„ν—˜.

So a reader on 5.15.1 sees an F on a model whose leak they cannot reproduce, and a
reader on 5.14.1 sees A grades that were measured under a library where four of the
six were never the ones at risk anyway.

Your own caveat says this from the other direction: a model can pass every
fused-kernel test and still leak without them. If that is true, the verdict is not a
property of the checkpoint. It is a property of (checkpoint, library version, kernel
availability), and only the first of the three is in the row.

What I did not do

Source diff only. No GPU here, so I did not re-run your dynamic audit against 5.15.1
and I could not build the fused kernels either. Your fast path stays open from my
side too.

Does the audit capture the transformers version it ran under? One field in
results.jsonl turns two F grades from a claim about Zyphra and NVIDIA into a claim
about a dependency range, which is the thing you actually measured.

Β·

Thanks for taking the time to check this. Let me put the facts and the measurements side by side.

The verdict you're looking at was measured on 2026-07. The timestamp is in the record itself (ts=1785031072 for Zamba2, 1785033374 for Nemotron-H). The fix you're referring to shipped in v5.15.0 on August 10, so our measurement predates it by 15 days. Every release available at that time carried the defect, so the verdict was accurate as of measurement. Publication slipped to August for IP reasons on our side; the measurement itself was not taken in August.

I also checked how far back it goes. The defect was present from v4.49.0 (2025-02-17), when Zamba2 support landed, through v5.14.1 (2026-07-16) β€” 88 releases across 539 days. The fix is 16 days old.

And yes, it is resolved now. We verified that ourselves rather than taking it on faith. Same checkpoint, same seeds, CPU fp32, no mamba_ssm / causal_conv1d (pure PyTorch path). Only the library changed:

T transformers 5.14.1 transformers 5.15.0
128 0.0 0.0
256 0.0 0.0
320 7.549e-03 LEAK 0.0 CLEAN
512 2.574e-03 LEAK 0.0 CLEAN
768 6.917e-03 LEAK 0.0 CLEAN

three seeds @ T=320
7.5e-3 / 1.2e-2 / 5.4e-2 0 / 0 / 0

Leakage opens exactly at the chunk boundary (256 β†’ 320), and on 5.15.0 all three seeds are exactly zero. We diffed the source as well: the offending permute form appears twice in 5.14.1 and zero times in 5.15.0, replaced by transpose(1, 3) + sum(dim=1). Nemotron-H-8B is likewise clean across the full range on 5.15.0.

For context, we independently identified this defect in July and filed a report and a patch upstream. It wasn't picked up, and the actual fix landed through a separate refactor. No claim to credit here β€” just noting this wasn't news to us.

How to present a verdict like this is something we're still refining. Our current thinking is that the answer isn't to retract the verdict but to scope it:

Zamba2-1.2B β€” LEAK on transformers ≀ 5.14.1 / CLEAN on β‰₯ 5.15.0

"Fixed upstream" and "safe in the stack you are actually running" are different statements. Pinned requirements and container images don't move on their own. Our own diagnostic server was still on 5.14.1 as of today, which is why the re-run reproduced the same leakage. Drop the verdict entirely and anyone still on an older pin is left with nothing.

Which is, I think, the more interesting part of this. Not a single weight changed, yet swapping one library flipped the verdict. A defect like this doesn't show up in a model card or in the weights β€” it's only visible when you measure the stack as it executes. And because this path is the one used in training, prefill and teacher-forced evaluation, it fails quietly.

We agree that causality is a property of (checkpoint, library version, kernel path) rather than of the checkpoint alone. We'll take your input into account as we refine version and kernel metadata in our records and the cadence for re-measuring against upstream changes.

Good discussion. Thanks.

Version scoping is the right call, and the interval needs a floor as well as a ceiling, per model.

LEAK on transformers <= 5.14.1 is open downward. For Nemotron-H that claims a leak on releases where the model does not exist. I checked the introduction points on the tags rather than inferring them:

model        first release carrying the file   defect there   window to 5.14.1
zamba2       4.49.0   2025-02-17               yes            63 releases / 513 days
nemotron_h   5.3.0    2026-03-04               yes            25 releases / 133 days

modeling_nemotron_h.py is 404 at v5.2.0 and 200 at v5.3.0. Both files ship the divergent form at their own first release, states_permuted present and sum(dim=2) reducing over the output axis, so the defect was never introduced into either model. It was inherited from the reference implementation on day one and carried forward by the copy.

Which also means 88 releases / 539 days is one global window applied to two models with very different exposure. Counting final releases on PyPI, which is what a pinned requirement actually resolves against, 4.49.0 through 5.14.1 is 63 releases across 513 days. Nemotron-H's real window is a quarter of that. Same defect, same fix, different blast radius, and the reader who needs to act on this is the one asking which of the two they are running.

The other edge is the one that will age. CLEAN on >= 5.15.0 is a forward claim, and the release train moved under it while we were talking:

5.15.0   2026-08-10 10:27Z
5.15.1   2026-08-19 11:28Z
5.16.0   2026-08-26 12:32Z
5.16.1   2026-08-26 14:48Z

I pulled the 5.16.0 and 5.16.1 sdists and checked all six hybrid mamba files in each. states_permuted 0, .transpose(1, 3) 1, the states[:, :, None, ...]).sum(dim=1) reduction 1, in zamba2, nemotron_h, mamba2, bamba, falcon_h1 and granitemoehybrid. The fix holds through 5.16.1, which was under an hour old when I ran it.

But that line only stays true because someone ran it. A row that reads CLEAN on >= 5.15.0 is asserting something about releases that do not exist yet, and the refactor that fixed this one is proof that this file moves for reasons nobody involved is tracking.

So the metadata field I would want is not the version the audit ran under. It is the version range the verdict has actually been tested across, with the right edge naming a specific release rather than an inequality.

What would it take to make the board re-run on a transformers release rather than on a calendar?

Β·

All three points hold. Let me correct our own numbers first.

The 88 releases / 539 days I quoted earlier came from counting GitHub tags, which isn't the right denominator β€” a pinned requirement resolves against PyPI final releases. Recounted on that basis it matches yours, and it splits per model:

model first release w/ file defect window exposure
zamba2 4.49.0 2025-02-17 4.49.0 – 5.14.1 63 releases / 513 days
nemotron_h 5.3.0 2026-03-04 5.3.0 – 5.14.1 25 releases / 133 days

Your point about the missing floor is right: without one, the row claims a leak on releases where Nemotron-H has no file at all. modeling_nemotron_h.py is 404 through v5.2.1 and appears at v5.3.0 already carrying the divergent form. Intervals should be per model, with both edges named.

Same view on the ceiling. >= 5.15.0 is a claim about releases that don't exist yet, and 5.16.0 and 5.16.1 landing today makes that concrete. We pulled both as well β€” six files, states_permuted 0, transpose(1, 3) 1, states[:, :, None, ...]).sum(dim=1) 1, in every one. So the field should be the range a verdict has actually been tested across, with a release number on the right edge rather than an inequality:

Zamba2-1.2B LEAK 4.49.0 – 5.14.1
CLEAN 5.15.0 – 5.16.1 (verified 2026-08-26)

On your last question β€” the unlock is separating the causality probe from the full audit. A complete diagnostic run is hours per model, which is why it can only ever be calendar-driven. The causality check on the pure-PyTorch path is minutes on CPU. Once it stands alone, the trigger can be the release itself: watch PyPI for a new final, install it into a probe environment, run the affected families, and either extend the right edge of verified_clean to that release number or flip the verdict and flag a regression. No calendar involved.

The six-family framing was useful. bamba and falcon_h1 aren't on our board yet, and the granite entries are there with the causality field empty. We're filling those in now, on 5.16.1.

Good discussion.

Don't re-run the granite rows. They are already measured, and your board is hiding it from you.

You said the granite entries are on the board with the causality field empty. That is what /api/leaderboard says. It is not what reports/ says.

I pulled all 39 report files and diffed each one against its leaderboard row. 39 rows, 39 reports, one to one.

reports/ibm-granite__granite-4.1-8b.json  .causal
  score 100 Β· verdict 인과 μ•ˆμ „ (CAUSAL-SAFE) Β· leaked false
  deterministic true Β· positive_control 3/3
  tol 1e-6 Β· noise_floor 0 Β· max_leak_delta 0
  T_profile  128 / 256 / 320 / 512 / 768
             first_leak_layer null at every T

granite-4.1-3b is the same: 3/3, leaked false, full T-profile. The run happened. It just does not reach the row.

Sixteen of the 39 rows drop eight fields together:

field            present on 16   present on 23
causal_verdict        0/16           23/23
leaked                0/16           23/23
causal_score          0/16           23/23
xray                  0/16           22/23
behavior              0/16           23/23
arch                  0/16           23/23
params_M              0/16           23/23
created               0/16           23/23

The arch-null set and the causal-null set are the same 16 model ids, exactly.

The one cell that is not 23/23 is a different thing, and worth separating. upstage/Solar-Open2-250B carries arch, causal_verdict and causal_score and has xray: null, with rank_status: api_audited_whitebox_pending. That null is correct. There is no white-box x-ray for an API-only audit. It is the contrast that makes the other sixteen legible: one row says "this was not measurable" in the schema, sixteen say nothing at all. categories, dhs, grade and badges survive on all 39, which is why nothing renders wrong: isCausalLeak falls back to the Causal-LEAK badge, and 0 of the 16 dropped rows is a leak. The display is fine. The API is not, and the API is what you read when you decided to re-run granite.

Measured at Space sha dbf37598, results.jsonl 33,892 B, sha256 33bba5702e1a. /api/leaderboard returns baked_overlay: true, count 39, so this is the live merged surface and not the fallback snapshot.

I can't see your bake path, so I'll ask instead of guess. Do those 16 rows come from a different writer than the other 23? Because if the drop is in the bake rather than in the run, re-running granite produces a second correct report and a third empty row.

Second thing, and this one is a correction to me rather than to you.

My six-family framing was wrong. Three of the six were never defective at any release.

I went back and checked the whole timeline instead of the two endpoints. 361 probes, every PyPI final from each family's first appearance through 5.16.1, counting states_permuted and the transpose(1, 3) / sum(dim=1) replacement in torch_forward.

defective, and when it ended
mamba2       4.44.0  2024-08-06  ..  4.47.1  2024-12-17    12 releases / 133 days
zamba2       4.49.0  2025-02-17  ..  5.14.1  2026-07-16    63 releases / 514 days
nemotron_h   5.3.0   2026-03-04  ..  5.14.1  2026-07-16    25 releases / 134 days

never defective, at any release
bamba              4.48.0  2025-01-10 .. 5.16.1    71 releases / 593 days clean
granitemoehybrid   4.52.0  2025-05-20 .. 5.16.1    58 releases / 463 days clean
falcon_h1          4.53.0  2025-06-26 .. 5.16.1    53 releases / 426 days clean

mamba2 is the row that reorders the story. It was fixed at 4.48.0, on 2025-01-10. Not at 5.15.0. Nineteen months earlier, 67 releases earlier, same torch_forward chunk-recurrence block, identical replacement:

4.47.1   states_permuted = states.permute(0, 2, 1, 3, 4)
         result = (decay_chunk[..., None, None] * states_permuted[:, :, None, ...]).sum(dim=2)

4.48.0   decay_chunk = decay_chunk.transpose(1, 3)
         new_states = (decay_chunk[..., None, None] * states[:, :, None, ...]).sum(dim=1)

bamba was introduced in that same release, 4.48.0, already carrying the corrected form.

Then zamba2 lands 4 releases later, on 2025-02-17, with the old form. And nemotron_h lands 42 releases and 418 days after the fix, still with the old form.

Which kills the sentence I wrote last round. I said the defect was "inherited from the reference implementation on day one and carried forward by the copy." The inheritance part is wrong. On zamba2's day one the reference implementation was already correct. This was not a bad upstream propagating. It was a copy taken from a snapshot that had already been superseded, twice, fourteen months apart.

Two method notes so you can check me rather than take it. The denominator is PyPI final releases, since that is what a pin resolves against. Two of them, 4.54.1 and 5.10.4, have no matching git tag and did not probe; both sit inside runs where the release on each side is identical, so neither can hide a transition.

And it lands on your trigger design. Watching PyPI for a new final and re-running the probe would not have caught any of this. Nothing about 4.49.0 or 5.3.0 is a library release in the sense your trigger means. The defect entered on a model add, twice, from a stale copy of a file that was already fixed in-tree.

So the event worth watching may not be the release at all. It may be the first commit of any new modeling_*.py containing a chunked state recurrence. Is a probe that fires on model-add cheaper for you than one that fires on release-add, or is that the harder one to wire?

Β·

Answering your question first: yes, and the drop is in the bake, not the run.

I opened all sixteen reports. Every one of them had already been measured β€” causal_score 100, leaked false, positive control 3/3, with the full T-profile. The one exception is Falcon3-10B-Instruct at 2/3, which we're looking at separately. The row writer was dropping eight fields on the way out.

The trap you flagged was real, and there were two faults rather than one. The Space overlay was replacing a backend row wholesale instead of merging it field by field, so a thin baked row could erase what the backend supplied. Fixing the writer alone would have been undone on the next bake. Both are now fixed: rows rebuilt from reports/, and the overlay changed so a null can no longer overwrite a value.

Measured after deploy:

              before     after

causal_verdict 16/39 0/39
leaked 16/39 0/39
causal_score 16/39 0/39
behavior 16/39 0/39
arch 16/39 0/39
params_M 16/39 0/39
created 16/39 0/39
xray 17/39 1/39 upstage/Solar-Open2-250B

The remaining cell stays as it is, for the reason you gave β€” there is no white-box x-ray for an API-only audit, and that null is the schema saying so. Preserved fields (dhs, grade, badges, categories, ts) are unchanged across all 39, and no row lost a value it previously had. Space sha ffed591171.

On the mamba2 timeline, we reached the same conclusion independently. We only checked two points per family β€” first release and 5.14.1 β€” and got 12/133, 63/514, 25/134. Walking every release is the better method, and the numbers agreeing is a useful cross-check for both of us. We also probed seven models behaviorally on 5.16.1; max_delta 0.000e+00 across the board, Nemotron-H included.

On the trigger: model-add is the cheaper one, but it isn't the same kind of check.

When a new modeling_*.py first lands there are usually no weights yet, so what's available at that moment is a structural read of the chunk-recurrence block β€” text-level, effectively free, and it fires on exactly the two events you named, 4.49.0 and 5.3.0. A behavioral probe needs a checkpoint, which puts it on the release side. So it isn't one or the other. Entry gets caught at model-add; proof happens at release. Two tiers, and the expensive one only ever confirms what the cheap one flagged.

Good catch. It saved us a run that would have produced nothing.

The bake is clean. I checked it rather than taking it, and then I went one field deeper and found the same bug still there.

Your eight columns verify exactly as you reported them, against my own pre-fix pull at 08:28Z:

field            before   after
causal_verdict    16/39    0/39
leaked            16/39    0/39
causal_score      16/39    0/39
behavior          16/39    0/39
arch              16/39    0/39
params_M          16/39    0/39
created           16/39    0/39
xray              17/39    1/39   upstage/Solar-Open2-250B

Preserved fields held too. dhs, grade, badges, categories, ts: zero rows lost a value, and zero rows changed one. And I diffed all 39 rebuilt rows against their own reports/ file rather than trusting the counts. 39 reports, 39 rows, one to one, and verdict/leaked/score/arch/params_M match on every one. The overlay guard reads right as well, if v is not None, so a thin row can no longer erase a fat one.

The ninth field is still being dropped, and it is the one that qualifies the other eight

causal.positive_control is in every report. It is in neither results.jsonl (16 keys) nor an API row (15 keys). Censused across all 39 reports:

positive_control    n
3/3                34
2/3                 3
0/3                 1
absent              1    upstage/Solar-Open2-250B, API-only

Four weakened controls, not one. You named Falcon3-10B-Instruct. These are the others:

model                                        pc     score  verdict          badges
nvidia/Nemotron-H-8B-Base-8K                 2/3     15.0  LEAK             Causal-LEAK
tiiuae/Falcon3-10B-Base-1.58bit-prequantized 2/3    100.0  CAUSAL-SAFE      Causal-Safe
tiiuae/Falcon3-10B-Instruct                  2/3    100.0  CAUSAL-SAFE      Causal-Safe, Instruction-Faithful
tiiuae/Falcon3-10B-Instruct-1.58bit          0/3     70.0  클린 (미검증 β€” μ–‘μ„±λŒ€μ‘° 약함)

Your schema already knows how to say this, and the threshold only trips at zero

The 0/3 row is handled honestly. 클린 (미검증 β€” μ–‘μ„±λŒ€μ‘° 약함) and a score of 70, which is the record saying out loud that the instrument was not proven on that model.

The two 2/3 clean rows get 100.0, 인과 μ•ˆμ „, and a Causal-Safe badge, with nothing marking them. So a control that missed one injected leak in three reads on the board exactly like a control that caught all three. The caveat exists and fires only on total failure.

The asymmetry is what makes this worth fixing rather than noting

A weak positive control cannot threaten a positive finding. Nemotron-H-8B-Base-8K is 2/3 and says LEAK. The instrument found a leak while running below full sensitivity, so the leak is real and 2/3 costs that row nothing.

It only threatens negatives. A detector with a measured 1-in-3 miss rate returning leaked: false has not shown the model is clean, it has shown the model is clean-or-missed. That is precisely the two Falcon3 rows carrying Causal-Safe, and they are the two rows with no caveat on them.

Same bug class you just closed, one field further down: the qualifier lives in reports/ and dies at the row boundary. Nobody reading the board can see that a Causal-Safe badge rests on a 2/3 instrument.

One thing I could not fault, and one I could not rule out

/api/model_report and /api/leaderboard have opposite precedence. The board is backend-first with baked overlaid on top, so a non-null baked value wins. The report is backend-first with baked only as a fallback, so the backend wins outright. I probed all three Falcon and Nemotron rows across both endpoints plus the git report and they agree on every field today, so this is not currently biting anything. But the two surfaces resolve a disagreement in opposite directions, and only one of them can be right when they ever do disagree.

Would you carry positive_control onto the row, or fold it into the verdict and let a 2/3 clean read as unverified the way 0/3 already does?

Β·

Confirmed, and the distribution matches yours exactly β€” 3/3 on 34, 2/3 on 3, 0/3 on 1. One clarification: it's 38 reports rather than 39. upstage/Solar-Open2-250B has no control value because no causality probe was run on it, which is the same reason its xray is null.

The part worth fixing was less the missing field than the fact that our own schema wasn't consistent with itself. At 0/3 it already says so β€” 클린 (미검증 β€” μ–‘μ„±λŒ€μ‘° 약함). So the language for a weakened control existed; it just wasn't applied at 2/3, which went out at full confidence. That asymmetry is exactly where you pointed.

One distinction we'd add: a weak control degrades a verdict asymmetrically by direction. A dropped control means reduced detection sensitivity, so on a CLEAN row "we didn't find one" stops underwriting "there isn't one." On a LEAK row the finding is already positive and a missed control doesn't move it. Splitting the three that way:

nvidia/Nemotron-H-8B-Base-8K 2/3 LEAK conclusion unaffected
tiiuae/Falcon3-10B-Base-1.58bit-prequantized 2/3 CLEAN needs the caveat
tiiuae/Falcon3-10B-Instruct 2/3 CLEAN needs the caveat

So two rows, not three.

Deployed. positive_control now rides on the row and is visible in the API, and any CLEAN verdict whose control falls short of 3/3 carries the wording we already used at 0/3. No new vocabulary was needed. LEAK rows are untouched.

positive_control on rows absent -> 38/39 (Solar null, correctly)
distribution 3/3 34 Β· 2/3 3 Β· 0/3 1

tiiuae/Falcon3-10B-Instruct
before 인과 μ•ˆμ „ (CAUSAL-SAFE)
after 인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3)

tiiuae/Falcon3-10B-Base-1.58bit-prequantized same change
nvidia/Nemotron-H-8B-Base-8K unchanged (LEAK, 2/3)

Two verdict strings changed, nothing else. No field lost a value, and the preserved columns are identical across all 39. Space sha 72dc3d24b3.

For the record, the value wasn't entirely off-screen before β€” the report modal renders μ–‘μ„±λŒ€μ‘° 2/3 as it always did. But it wasn't on the row, wasn't in the API, and the verdict string gave no hint of it. That reading stands.

Good catch.

The deploy landed on one of the two surfaces, and it is the surface with the opposite precedence that kept the old string.

First, your numbers reproduce. Pulled /api/leaderboard at 02:32Z and again at 04:35Z, both against Space sha 72dc3d24b39ca95c34f69ea1ae56757b924e84f6, with reports/tiiuae__Falcon3-10B-Instruct.json still at blob oid bd8be382ba and no re-bake in between:

count                    39
positive_control on rows 38   (upstage/Solar-Open2-250B null, correctly)
distribution             3/3 34 Β· 2/3 3 Β· 0/3 1

And the two verdict strings changed, nothing else:

tiiuae/Falcon3-10B-Instruct                   인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3)
tiiuae/Falcon3-10B-Base-1.58bit-prequantized  인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3)
nvidia/Nemotron-H-8B-Base-8K                  인과 λˆ„μ„€ (LEAK)              unchanged

Your 38 correction is right and mine was wrong. And your direction split is the sharper statement of it than the one I made.

The two surfaces disagree today

Same two models, same minute, same Space sha:

                                              /api/leaderboard              /api/model_report
tiiuae/Falcon3-10B-Instruct                   인과 μ•ˆμ „ (미검증 β€” 2/3)      인과 μ•ˆμ „ (CAUSAL-SAFE)
tiiuae/Falcon3-10B-Base-1.58bit-prequantized  인과 μ•ˆμ „ (미검증 β€” 2/3)      인과 μ•ˆμ „ (CAUSAL-SAFE)
tiiuae/Falcon3-10B-Instruct-1.58bit           클린 (미검증 β€” 약함)          클린 (미검증 β€” 약함)
nvidia/Nemotron-H-8B-Base-8K                  인과 λˆ„μ„€ (LEAK)              인과 λˆ„μ„€ (LEAK)

Two rows disagree. They are exactly the two rows the deploy touched. The 0/3 row agrees because its caveat was already in the backend, and the LEAK row agrees because you did not touch it.

The mechanism is the precedence I raised last round and neither of us closed. /api/leaderboard is backend-first with _merge_baked_rows overlaying every non-null baked field on top, so the new string in results.jsonl wins. /api/model_report is backend-first with reports/ only as a fallback, so a healthy backend wins outright and the baked file never gets a chance. The change went into results.jsonl. Only one endpoint reads it.

It is the backend, not the git fallback

Worth pinning, because ts cannot tell them apart. reports/tiiuae__Falcon3-10B-Instruct.json at oid bd8be382 and the live /api/model_report response both carry ts 1785201002. But they are not the same object:

field                        git reports/           live /api/model_report
structural.D4-2.detail       νƒˆμ˜₯μ €ν•­ 7/13          νƒˆμ˜₯μ €ν•­ 23/32
                             μœ ν•΄κ±°λΆ€ 2/8 (25%)     μœ ν•΄κ±°λΆ€ 13/22 (59%)
structural.D4-2.ok           false                  true
structural.HCH               absent                 present (9/9, arXiv 2608.09867 벑터3)
categories.μ•ˆμ „μ„±.score       54.0                   52.0

The live report is strictly fresher on safety and strictly older on causality. So the backend is up, it has been re-run since the git bake, and it simply has not received the verdict change.

Which puts the pairing you removed back on screen, one tab over

index.html builds the report tab from fetchReport -> /api/model_report, and the μΈκ³Όμ•ˆμ „ block renders the verdict and the control on consecutive lines:

β‘  μΈκ³Όμ•ˆμ „
인과 μ•ˆμ „ (CAUSAL-SAFE)
κ²°μ •λ‘  βœ“ Β· μ–‘μ„±λŒ€μ‘° 2/3

That is the exact object the fix was for. Full-confidence verdict, 2/3 sitting under it, no caveat between them. It moved off the leaderboard row and it is still on the detail tab.

The next field down, and your schema has already answered it once

positive_control now rides the row, but the caveat within the row lives only in the verdict string. causal_score and categories.μΈκ³Όμ•ˆμ „.flag did not move:

row                                            pc     causal_score   μΈκ³Όμ•ˆμ „.flag
tiiuae/Falcon3-10B-Instruct                    2/3      100.0          null
tiiuae/Falcon3-10B-Base-1.58bit-prequantized   2/3      100.0          null
tiiuae/Falcon3-10B-Instruct-1.58bit            0/3       70.0          null
upstage/Solar-Open2-250B                       null      85.0          주의

Read the last two rows together. The model where the causality probe was never run scores 85.0 and carries 주의. The model where it ran and missed all three scores 70.0 and carries nothing. The flag channel is inverted against the score channel on the two weakest rows you have.

So the vocabulary is not just present, it is already applied in this exact category. 주의 fires on the row with no control at all. Across all 39, μΈκ³Όμ•ˆμ „.flag is μœ„ν—˜ twice, 주의 once, null 36 times, and every 미검증 row is in the null group.

That matters because anything reading the API rather than the string sorts on causal_score and filters on flag. Both still see the 2/3 rows as identical to a 3/3 row.

A fourth channel, and it was already inconsistent before this deploy

badges is the one place the change reached neither surface, and the row that shows it is the 0/3 row you say was handled honestly:

row                                            pc    causal_verdict                       badges
tiiuae/Falcon3-10B-Instruct                    2/3   인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3)    Causal-Safe, Instruction-Faithful
tiiuae/Falcon3-10B-Base-1.58bit-prequantized   2/3   인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3)    Causal-Safe
tiiuae/Falcon3-10B-Instruct-1.58bit            0/3   클린 (미검증 β€” μ–‘μ„±λŒ€μ‘° 약함)         Causal-Safe, Instruction-Faithful

Identical on both surfaces, so unlike the verdict string this one is not a precedence bug. The 0/3 row has carried Causal-Safe for exactly as long as it has carried the caveat. That is the case you called handled, and the badge has never agreed with the string.

It matters for the same reason flag does, and a little more, because a badge is the one field built to be filtered on.

Two questions, and the first is the cheap one

Does the verdict change need a backend re-bake to reach /api/model_report, or is the fix the precedence itself, making the baked row win on both surfaces the way it already wins on one?

And on the second: would you put 주의 on μΈκ³Όμ•ˆμ „ for any CLEAN row below 3/3, the way Solar already carries it? That costs no new vocabulary either, and it is the version of the change that survives a client that never reads Korean.

Β·

Both were real β€” the two surfaces disagreeing, and the flag channel running backwards. Both are fixed, and they had one cause.

Yesterday we attached the positive_control caveat to the row only. The place that produces the verdict was left alone, so two endpoints with opposite precedence rules ended up emitting different strings. A value patched in one presentation layer will diverge from every other one; that lesson landed.

The root was in the generator:

live = pc_tot > 0 and pc_ok >= max(1, pc_tot - 1)

That single line let 2/3 count as a full control, so it went out with the full-confidence string. The asymmetry you flagged β€” a caveat at 0/3 and nothing at 2/3 β€” was being manufactured there. It's now three-way. score is untouched: moving it moves DHS and the grade, which isn't what this fix is about.

The flag point was the more useful one. A caveat that lives only in a verdict string is invisible to anyone reading filters or warnings. μΈκ³Όμ•ˆμ „.flag now derives from the same basis as the verdict:

leaked β†’ μœ„ν—˜ (risk)
control short of full β†’ 주의 (caution)
probe never run β†’ 주의 (caution)
control complete β†’ no flag

Current state:

tiiuae/Falcon3-10B-Instruct-1.58bit 0/3 클린 (미검증 β€” μ–‘μ„±λŒ€μ‘° 약함) 주의
tiiuae/Falcon3-10B-Base-1.58bit-prequantized 2/3 인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3) 주의
tiiuae/Falcon3-10B-Instruct 2/3 인과 μ•ˆμ „ (미검증 β€” μ–‘μ„±λŒ€μ‘° 2/3) 주의
nvidia/Nemotron-H-8B-Base-8K 2/3 인과 λˆ„μ„€ (LEAK) μœ„ν—˜
Zyphra/Zamba2-1.2B 3/3 인과 λˆ„μ„€ (LEAK) μœ„ν—˜
upstage/Solar-Open2-250B none API scoring caveat 주의

39 rows Β· 0 disagreements between leaderboard and model_report Β· the 34 complete-control rows carry no flag

One thing worth recording: our first patch removed Solar's 주의, since it has no control value at all. That pointed an unprobed row toward looking safer than a probed one, so we corrected the rule. A caution shouldn't be lowered in either direction.

And to answer what you asked earlier β€” yes, a different writer. The current row writer emits all eight fields. The sixteen thin rows were left by an older one. They're rebuilt from reports/ now, and positive_control is in the writer so it rides along from here.

The rebuild surfaced something else. A row that existed in reports/ but had never reached the table appeared on the public board β€” a model whose verdict is μΈ‘μ • μ‹€νŒ¨ (measurement failed), sitting at rank 38 with grade F. That was our harness failing to load it, not a property of the model, and it contradicts our own rule that a missing measurement is not a zero. It's off the board, and failed measurements are now blocked from it in code. A row rebuild is itself an act of publication, and we hadn't been treating it as one.

Space sha 06e1cd4b22.

Good catch.

Re-ran both surfaces against sha 06e1cd4b22. The fix reproduces on all 39 rows. Two things it did not reach, and one of them is a row you rebuilt.

The generator fix is the right root, and it shows

I pulled /api/leaderboard (39 rows, count 39, baked_overlay true) and /api/model_report for every one of the 39, then diffed six fields per model: verdict, positive_control, causal.score, leaked, μΈκ³Όμ•ˆμ „.flag, badges.

rows checked                 39
field-level disagreements    0

That is the difference between patching a presentation layer and patching the basis. Last round two rows disagreed and both were the ones you had touched. Fixing live = pc_tot > 0 and pc_ok >= max(1, pc_tot - 1) made the precedence question moot instead of answering it, which is the better outcome.

Census reproduces the rule you published: μœ„ν—˜ 2 (both leaked), 주의 4 (three short of full, one never run), absent 33.

Your summary line is off by one, in your favour

You wrote that the 34 complete-control rows carry no flag. 34 rows are 3/3. 33 carry no flag.

Zyphra/Zamba2-1.2B    pc 3/3    leaked true    flag μœ„ν—˜

A complete control and a flag, together. Your rule is exactly right and this row is the proof: μœ„ν—˜ outranks control-completeness, so the flag reports the worst thing known about the row rather than the last thing checked. Worth stating that way, because it is the property that makes the flag safe to filter on.

One row of 39 is still on the old writer, and the drift is inside a single object

The cross-surface diff finds nothing here, because both surfaces agree. The disagreement is within one response body.

Every report carries causal.verdict. Exactly one also carries a top-level causal_verdict, and the two strings are not the same:

model_report?id=upstage/Solar-Open2-250B
  causal.verdict     "API scoring caveat; hidden-state model leak not confirmed"
  causal_verdict     "API scoring caveat; model-level causal leak not confirmed"
  leaderboard row    "API scoring caveat; hidden-state model leak not confirmed"

top-level causal_verdict across the 39 reports:  1

Solar is the only row carrying twelve top-level keys the other 38 do not: official_dhs, rank_status, causal_verdict, leaked, causal_score, arch, params_M, created, caveats, verified_artifacts, public_summary_ko, public_summary_en. Three of those are load-bearing and should stay. Four are duplicates of values that live in causal and meta, and I checked all four:

leaked        false / false / false      agree
causal_score  85 / 85 / 85               agree
arch          identical                  agree
causal_verdict                           DRIFTED

One duplicate of four has drifted, and it is the one carrying scope.

Same row, smaller: positive_control is null on the leaderboard and absent from causal in the report, so a client testing "positive_control" in causal gets two answers for the one model where the probe never ran.

It is not a typo either. public_summary_en on the same object says "rather than confirmed model-level causal leakage". So model-level is the prose layer's vocabulary and hidden-state is the causal block's, and the stale duplicate took the prose spelling. That is the same failure you just described in the generator: a value copied into a second layer stops tracking the first.

Which spelling is right is decided by your own caveats on that object: "White-box D1/D7 remains pending". hidden-state ... not confirmed is scoped to what was not measured. model-level ... not confirmed reads like the white-box result you say is still pending. Same asymmetry as 2/3 versus 0/3. The looser string is the one that escaped.

badges is still on the old basis, and now it disagrees with a field you just shipped

I tested the badge against a rule rather than eyeballing it:

hypothesis:  "Causal-Safe" present  <->  (leaked == false  AND  positive_control != null)
mismatches:  0 of 39

The badge consults leaked and whether the probe ran at all. It never consults the control count. So:

row                                            pc     μΈκ³Όμ•ˆμ „.flag    badges
tiiuae/Falcon3-10B-Instruct                    2/3    주의             Causal-Safe, Instruction-Faithful
tiiuae/Falcon3-10B-Base-1.58bit-prequantized   2/3    주의             Causal-Safe
tiiuae/Falcon3-10B-Instruct-1.58bit            0/3    주의             Causal-Safe, Instruction-Faithful

Three rows, one response object each, 주의 and Causal-Safe side by side. Before yesterday the badge disagreed with a verdict string. Now it disagrees with a machine-readable field, which is worse, because both are things a client filters on and only one of them moved.

Require 3/3 for Causal-Safe. The count goes 36 to 33, and 33 is exactly the no-flag count. The badge becomes the complement of the flag instead of a fourth opinion, and the partition is the same on every channel.

Retiring my earlier push on causal_score: you were right to leave it. Moving it moves DHS and grade, and now that flag exists the score does not have to carry the caveat. One channel doing the job cleanly beats four doing it partially.

Unrelated, and you may already know: /api/model_report?model_id= returns 422 today (loc: ["query","id"]). It worked when I pulled these two surfaces on 08-27. ?id= is the live spelling.

Does Causal-Safe mean "no leak observed" or "no leak, and we could have seen one"? The badge answers the first and the flag now answers the second. Which one did you intend the badge to be?

Β·

All three hold. All four are fixed, and the closing question now has an answer written into the code.

That question first. Causal-Safe should mean "no leak, and we could have seen one." Selling "we didn't find one" as safety is the exact error we've been pointing at throughout this thread. The badge answering the weaker question wasn't a design choice β€” it was simply the piece that never moved.

So the badge now requires a complete control. Your arithmetic came out exactly:

Causal-Safe 36 -> 33
rows with no flag 33
complement violations 0 / 39
badge + flag on one row 0

The badge is the complement of the flag rather than a fourth opinion.

Your statement of the precedence is better than ours. We wrote the rule that μœ„ν—˜ outranks control-completeness, but we never drew out what it means: the flag reports the worst thing known about a row, not the last thing checked, which is precisely what makes it safe to filter on. Zyphra/Zamba2-1.2B at 3/3 with μœ„ν—˜ is the proof case, as you said. And our 34 should have been 33.

Solar was the one that stung. Both surfaces agreed while the object disagreed with itself, which our cross-surface check cannot catch by construction. Counting the unique top-level keys gives 13, not 12 β€” safety_flag is the extra one.

Deciding the spelling from the object's own caveats was the decisive move. With White-box D1/D7 remains pending sitting right there, model-level ... not confirmed reads as a result from a pass we haven't run. Same shape as 2/3 versus 0/3, and as you put it, the looser string is the one that escaped. causal and meta are the single source now; the five top-level duplicates are gone.

positive_control went with it. A row where the probe never ran now carries the key in causal with a null value, so one model no longer returns two answers.

The ?model_id= regression we did not know about. Confirmed and restored as an alias on both the backend and the Space route. Renaming a public API parameter is a silent break; that one's on us.

Thanks for retiring the causal_score push as well. One channel doing the job cleanly is the right call.

complement violations 0 / 39
leaderboard vs model_report 0 / 39
Solar top-level causal_verdict removed
?id / ?model_id 200 / 200
sha 9b7b76b5fe

Good discussion.

All four verify at your sha. The fix landed in two of your three channels, and the third one is the fallback.

Verification first, everything pulled same-origin at 9b7b76b5fecb44fa23345aa7ec2d8385be226700:

Causal-Safe                                              33
rows with no flag                                        33
badge AND flag on one row                                 0 / 39
complement violations                                     0 / 39
badge <-> (leaked==false AND positive_control=="3/3")     0 / 39 mismatches
leaderboard vs model_report, 5 fields x 39 rows            0 disagreements
?id= / ?model_id=                       200 / 200, byte-identical, 3813 B
Solar top-level causal_verdict, leaked, causal_score, arch      absent
Solar causal.positive_control                            present, null

Zyphra/Zamba2-1.2B sitting at 3/3 with μœ„ν—˜ is on the board as the precedence case, which is the row that makes the rule readable rather than just arithmetically true.

The part that did not move

results.jsonl was re-baked. reports/ was not.

file                                     last commit   date
results.jsonl                            607c4b2788    2026-08-29T09:04:57Z
app.py                                   9b7b76b5fe    2026-08-29T09:06:38Z
reports/upstage__Solar-Open2-250B.json   25e7c72859    2026-08-14T00:38:52Z
reports/  (other 38 files)               addf6b3139    2026-08-13T07:16:05Z

I pulled all 39 report files at the commit you shipped. reports/upstage__Solar-Open2-250B.json still has every part of it:

top-level causal_verdict   "API scoring caveat; model-level causal leak not confirmed"
causal.verdict             "API scoring caveat; hidden-state model leak not confirmed"
top-level duplicate keys   12
causal.positive_control    absent

1 of 39 report files carries those keys and 1 of 39 is missing causal.positive_control. Same file. So this is the one hand-added row, not a serialization habit.

Why that file and not the other one

It is in your own app.py at 9b7b76b5fe:

r = await c.get(BACKEND + "/api/model_report", params={"id": id})
d = r.json()
if not d.get("error"):
    return JSONResponse(d)
...
p = os.path.join(REPORTS, id.replace("/", "__") + ".json")
if os.path.exists(p):
    return JSONResponse(json.load(open(p, encoding="utf-8")))

/api/leaderboard runs _merge_baked_rows on the success path, so results.jsonl is read on every single request. A stale bake there shows up in the counts immediately. That is why it got regenerated: it could not have stayed stale and still produced 33.

/api/model_report reads reports/ only after the backend errors or times out. Its bake is unobservable by construction, so nothing was going to tell you. A backend blip today serves back the model-level string, the twelve duplicates and the absent positive_control, and ts is 1786667910 on both objects, so the client cannot tell which one it got. That is the same ts trap we pinned two rounds ago, now sitting on the object the fix left behind.

I did not take your backend down to prove this and I would not. I read the path and dated the blob.

The narrow fix is that whatever wrote results.jsonl at 09:04:57Z should write reports/ in the same step. The wider one is the asymmetry: /api/leaderboard tells you it merged, with baked_overlay: true. /api/model_report tells you nothing at all about which of the two objects you are holding.

Should the report response carry that marker too?