Skip to content

fix(spec): bound the pipeline loops by their buffers, and drive buffers in the harness - #3429

Merged
gHashTag merged 1 commit into
masterfrom
tool/buffer-grid
Sep 7, 2026
Merged

fix(spec): bound the pipeline loops by their buffers, and drive buffers in the harness#3429
gHashTag merged 1 commit into
masterfrom
tool/buffer-grid

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Closes #3428

One pass ago I shipped a differential generator and wrote down its weakest part: a &mut [T] parameter got a single zero-filled [T; 8], so it compared what came back while never varying what went in. ring-099 read 25 of 25 agreeing, and I recorded that "25 is not the evidence 936 is".

Driving length and fill — lengths 0, 1, 4, 8 — the same pair reads:

cases 80  agree 44  disagree 36
first disagreement: pipeline_inject_failure

The defect

The spec bounds its loop by a constant and indexes without consulting the buffer:

while (i < MAX_PIPELINE_STAGES and ...) {
    stages[i] = current;

MAX_PIPELINE_STAGES is 10. Hand it four elements and it writes stages[4..10].

The hand-written rings/ring-099-rust, which this spec is supposed to define, has always bounded by the buffers:

let cap = stages.len().min(results.len()).min(MAX_PIPELINE_STAGES);

In C there is nothing to check

uint8_t pipeline_run(uint8_t* stages, bool* results, size_t* count);

No length parameter. A []T becomes a bare pointer at the C ABI. Probed directly: buf.len lowers to buf.len() in Rust and buf.len in Zig, and the C backend emits nothing for it at all. The bound this spec needs is expressible in 2 of the 4 backends.

In the generated Rust a short buffer panics. In C it writes past the end, silently.

Fixed, and not fixed

Fixed: both loops bound by stages.len and results.len. Rust and Zig now match the model — 80 of 80 agree — and reverting the bound reproduces 44 of 80 and exit 1, which is the control.

Not fixed: the C output still writes to the constant regardless of what it was handed. Making C safe needs an explicit length parameter, which changes the ABI for all four backends and would make ring-099 read DRIFTED again unless the ring changes with it. Filed rather than decided.

Third pass in a row where the hand-written code was right

the spec the model
#3420 wrapped saturated
#3422 raw pointer safe reference
#3428 writes past the end bounds by length

The first two were wrong values. This one is memory safety.

The lesson worth more than the fix

I wrote this limitation down as prose one pass ago and it changed nothing. It became a finding only when the grid was built and run. A named weakness is not a measured one.

Verified end to end: the full Spec Guards workflow body runs green under bash 3.2 — 936/936, 80/80, CONVERGED pairs run: 2, STALE 0 — and both seals for the edited spec were refreshed by t27c seal --save, duplicates included.

…rs in the harness

Closes #3428

One pass ago I shipped a differential generator and wrote down its weakest
part: a `&mut [T]` parameter got a single zero-filled `[T; 8]`, so it compared
what came BACK while never varying what went IN. ring-099 read 25 of 25
agreeing, and I recorded that "25 is not the evidence 936 is".

Driving length and fill -- lengths 0, 1, 4, 8 -- the same pair reads:

    cases 80  agree 44  disagree 36
    first disagreement: pipeline_inject_failure

The spec bounds its loop by the constant MAX_PIPELINE_STAGES = 10 and indexes
without consulting the buffer, so a buffer of four gets stages[4..10] written.
The hand-written rings/ring-099-rust, which this spec is supposed to define, has
always computed stages.len().min(results.len()).min(MAX_PIPELINE_STAGES). In the
generated Rust a short buffer panics; in C it does not.

In C there is nothing to check: `uint8_t pipeline_run(uint8_t* stages, bool*
results, size_t* count)` has no length parameter. A `[]T` becomes a bare pointer
at the C ABI. Probed directly, `buf.len` lowers to `buf.len()` in Rust and
`buf.len` in Zig, and the C backend emits nothing for it -- so the bound this
spec needs is expressible in 2 of the 4 backends.

Fixed here: both loops bound by `stages.len` and `results.len`. Rust and Zig now
match the model, 80 of 80 agree, and reverting the bound reproduces 44 of 80 and
exit 1. NOT fixed: the C output still writes to the constant regardless of what
it was handed. Making C safe needs an explicit length parameter, which changes
the ABI for all four backends and would make ring-099 read DRIFTED again unless
the ring changes with it. Filed rather than decided.

All slice parameters of one call share the (length, fill) pair: in the corpus
they are parallel arrays, and a loop each would multiply cases without adding a
shape the callee can distinguish.

Third pass in a row where the hand-written code was right and the spec was
wrong. #3420 wrapped where the model saturated; #3422 emitted a raw pointer
where the model took a safe reference; this writes past the end. The first two
were wrong values.

The lesson worth more than the fix: the limitation was written down as prose one
pass ago and changed nothing. It became a finding only when the grid was built
and run. A named weakness is not a measured one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag enabled auto-merge (squash) September 7, 2026 22:46
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-07 22:46:15 UTC

Summary

Status Count
Total Open PRs 16
PRs with Failing Checks 13
PRs with All Checks Green 3
READY 2
FAILING 13
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 13 + 0 + 0 = 15, and there are 16 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=1d58d30ea13e != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@gHashTag
gHashTag merged commit 76a8f5a into master Sep 7, 2026
26 of 29 checks passed
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.

pipeline_run writes past any buffer shorter than MAX_PIPELINE_STAGES; in C there is no length to check

1 participant