Skip to content

fix(stella-pipeline): unbreak main — three parallel-merge collisions in one round - #1975

Closed
macanderson wants to merge 2 commits into
mainfrom
unbreak-main-pipeline
Closed

fix(stella-pipeline): unbreak main — three parallel-merge collisions in one round#1975
macanderson wants to merge 2 commits into
mainfrom
unbreak-main-pipeline

Conversation

@macanderson

@macanderson macanderson commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What & why

main is red at 43402ae: cargo test -p stella-pipeline fails to compile
with three errors, and cargo clippy -p stella-pipeline --all-targets fails a
fourth. Until this lands every PR against main inherits a red gate, so this
is deliberately narrow — no behavior change, nothing but the repairs.

Three independent parallel-merge collisions from the same round of merges.
None was visible to the CI of the PR that caused it, because each PR was green
against the main it branched from.

1. Clobbered flip-halt test doubles (#1945 vs #1951)

#1945 added mod flip_halt_arming; to
pipeline/tests/verification_hardening.rs together with the two doubles
the child module uses — PassingShell and shell_call_result — plus the
configured-command witness a_revision_halts_at_the_step_where_the_tracked_test_flips.

#1951 then rewrote that file from a pre-#1945 base. It deleted all three, and
left the mod flip_halt_arming; line standing — so the child module referenced
two symbols that no longer existed:

error[E0425]: cannot find value `PassingShell` in this scope
error[E0425]: cannot find function `shell_call_result` in this scope

Restored into flip_halt_arming.rs itself rather than back into the
parent, for two reasons: verification_hardening.rs is 1434 lines against the
1500 ceiling and cannot hold them, and the child module is their only user.
Both #1793 witnesses — the configured-command side and the authored-witness
side — now sit in one module, which is where a reader looking for "how is the
flip halt witnessed" would expect them.

2. ModelCallRole::Research not covered (#1778)

#1778 added the Research variant. management_prompt/tests.rs holds a
deliberately exhaustive match over ModelCallRole — its doc says a new
role "must declare its prefix posture here, not escape the family witness by
omission" — which #1778's CI never compiled against.

error[E0004]: non-exhaustive patterns: `ModelCallRole::Research` not covered

Research runs as an engine sub-agent turn (research_stage.rs builds a
SubAgentSpec carrying RESEARCH_SYSTEM_PROMPT), never through the management
chokepoint — so it joins the never-dispatched arm returning None. That is the
same grouping, with the same stated reasoning, that raw_usage.rs already
gives it. Added to ALL_ROLES as well, since that array is documented as
"every role the crate can dispatch".

3. plan_stage over the argument limit (#1778)

#1778 also added research: &[ResearchFinding] to plan_stage, taking it to 8
arguments against clippy's limit of 7. The gate runs clippy at -D warnings.

Fixed structurally rather than with an #[allow]: budget and total are
exactly the pair the crate's own Spend envelope bundles
(pipeline/stage_budget.rs), they travel together everywhere else, and
verifier() already took this shape in #1951. plan_stage now takes Spend;
plan_with_review — which uses the two for nothing but that call — builds one
before its re-plan loop and reborrows it per iteration. 8 arguments become 7
and the meaning is unchanged.

The witness

  • This PR includes a witness test

Deliberately none, and this is the case the template's "explain how you
verified instead" is for: a repair to a broken build is witnessed by the
build. The evidence is that cargo test -p stella-pipeline does not compile on
main and compiles here, which no test inside that crate could express — a
test that cannot be built cannot fail informatively.

The two witnesses #1951 deleted are restored verbatim and do run again, so
#1793's coverage returns with this PR rather than being re-asserted by it.

The gate

Run on this branch, from the worktree:

  • cargo test -p stella-pipeline — 583 + 5 + 2 + 5 + 6 + 4 = 605 passed, 0
    failed
    (on main: does not compile)
  • cargo clippy -p stella-pipeline --all-targets — clean (on main: 1 warning
    = 1 error under the gate's -D warnings)
  • cargo fmt --check -p stella-pipeline — clean
  • cargo check --all-targets on stella-protocol, stella-core, stella-tui,
    stella-serve, stella-cli — clean, confirming stella-pipeline was the
    only crate the new enum variant broke
  • scripts/check-file-size.sh — OK, none grew
  • scripts/check-god-files.sh — OK
  • scripts/check-left-behind.sh — OK, 0 grandfathered

The full workspace suite is left to CI.

Nothing left behind

Two follow-ups worth tracking, neither fixable inside an unbreak PR:

  1. The clobber in fix(zai): don't misreport an insufficient-balance 429 as a rate limit #2 is a recurring shape, not an accident. A PR that
    rewrites a test file from a stale base silently drops whatever landed
    underneath it, and the only thing that caught it here was a mod
    declaration surviving. I will file an issue for it rather than leave the
    observation in this description.
  2. ALL_ROLES in management_prompt/tests.rs is a hand-maintained array whose
    own doc admits completeness "is not compiler-checked here". It stayed
    correct this time only because the neighbouring match is exhaustive.
    Worth a strum-style derive or a const assertion; also going in the issue.

Summary by Sourcery

Repair stella-pipeline so it builds and passes clippy again by resolving recent merge collisions in tests, enum handling, and pipeline budgeting signatures.

Bug Fixes:

  • Restore the flip-halt verification witnesses and their test doubles so the flip_halt_arming tests compile and run again.
  • Handle the ModelCallRole::Research variant consistently in management_prompt tests, including matching and ALL_ROLES coverage.
  • Refactor plan_stage to accept a Spend bundle instead of separate budget and total parameters to satisfy clippy's argument limit without changing behavior.

Tests:

  • Reinstate the configured-command flip-halt witness test alongside the authored-witness test to fully exercise FlipHalt arming behavior across both paths.

…an uncovered ModelCallRole

main was red at 43402ae: stella-pipeline's lib test build failed with
three compile errors, so every PR against it inherits a red gate.

Two independent parallel-merge collisions, neither visible to the CI of
the PR that caused it:

1. #1951 rewrote tests/verification_hardening.rs from a pre-#1945 base,
   deleting the PassingShell double, shell_call_result, and the
   configured-command witness, while leaving the 'mod flip_halt_arming;'
   #1945 had added — so the child module referenced two symbols that no
   longer existed. Restored into flip_halt_arming.rs itself rather than
   the parent: verification_hardening.rs is 1434 lines against a 1500
   ceiling and cannot hold them, and the child is their only user. Both
   #1793 witnesses now sit in one module.

2. #1778 added ModelCallRole::Research; management_prompt/tests.rs holds
   a deliberately exhaustive match over the enum, which #1778's own CI
   never compiled against. Research runs as an engine sub-agent turn, so
   its system prompt rides its SubAgentSpec and it joins the
   never-dispatched-through-the-chokepoint arm — the same grouping, with
   the same reasoning, that raw_usage.rs already gives it. Added to
   ALL_ROLES too, since that array is 'every role the crate can dispatch'.

No behavior change: test-only code plus one test-only match arm.
… the research stage

Third break on main from the same round of parallel merges: #1778 added
'research: &[ResearchFinding]' to plan_stage, taking it to 8 arguments
against clippy's limit of 7. The gate runs clippy at -D warnings, so
stella-pipeline could not pass it.

Fixed structurally, not with an #[allow]: budget and total are exactly
the pair the crate's own Spend envelope bundles (stage_budget.rs), and
they travel together everywhere else — verifier() already took this
shape in #1951. plan_stage takes Spend, plan_with_review builds one
before its re-plan loop and reborrows it per iteration, and neither
uses the two for anything else. 8 arguments become 7 and the meaning
is unchanged.

No behavior change.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Aug 7, 2026 2:05am

@sourcery-ai

sourcery-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Repairs three merge-induced breakages in stella-pipeline: restores flip-halt test doubles and a configured-command witness into the flip_halt_arming module, updates management_prompt tests to account for the new Research ModelCallRole, and refactors plan_stage’s budget/total parameters into a Spend struct to satisfy Clippy’s argument limit while preserving behavior.

File-Level Changes

Change Details Files
Restore flip-halt arming test doubles and configured-command witness directly within flip_halt_arming.rs so the module compiles and fully exercises the flip-halt behavior.
  • Reintroduce the PassingShell ToolExecutor double that always returns a passing shell command result.
  • Reintroduce the shell_call_result helper that wraps shell commands into CompletionResult tool calls with stable call_ids.
  • Restore the configured-command witness test a_revision_halts_at_the_step_where_the_tracked_test_flips, updated to live in flip_halt_arming.rs and to share doubles with the authored-witness test.
  • Expand flip_halt_arming module docs to describe both authored and configured-command paths and document why witnesses and doubles live in this child module instead of the parent.
crates/stella-pipeline/src/pipeline/tests/verification_hardening/flip_halt_arming.rs
Refine management_prompt tests to handle the Research ModelCallRole consistently with its non-management dispatch semantics.
  • Extend the ALL_ROLES array to include ModelCallRole::Research and adjust its declared length.
  • Update management_system_block’s exhaustive match to treat ModelCallRole::Research as a never-dispatched role that returns None, alongside Unknown and other non-management roles, with commentary tying this to its SubAgentSpec-based system prompt.
crates/stella-pipeline/src/management_prompt/tests.rs
Refactor plan_stage and its callers to pass a Spend envelope instead of separate budget and total references, reducing the parameter count below Clippy’s function argument limit without changing behavior.
  • Change Pipeline::plan_stage signature to take &mut Spend<'_> instead of &mut BudgetGuard and &mut f64, and update internal calls to use spend.budget and spend.total.
  • In scope_stage, construct a Spend instance once from the existing budget and total before the planning loop and reborrow it for each plan_stage call, with documentation explaining the structural fix for Clippy’s argument limit.
  • Update management_accounting tests to construct and pass a Spend wrapper when directly calling plan_stage, keeping test coverage aligned with the new API.
crates/stella-pipeline/src/pipeline.rs
crates/stella-pipeline/src/pipeline/scope_stage.rs
crates/stella-pipeline/src/pipeline/tests/management_accounting.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@macanderson

Copy link
Copy Markdown
Owner Author

Follow-ups from the "Nothing left behind" section, now filed as handoffs:

@macanderson macanderson closed this Aug 7, 2026
macanderson added a commit that referenced this pull request Aug 7, 2026
…ngress (#1787) (#1982)

> **Stacked on #1975.** Based on `unbreak-main-pipeline` because `main`
does not
> currently compile `stella-pipeline`'s test target; the diff below is
the one
> commit on top. GitHub retargets this to `main` when #1975 merges.

## What & why

The last unbounded ingress into the verdict prompt, and the item #1787
folds in
at the end of its body:

> Also worth folding in: the trusted evidence summary has no length
bound
> (`oracle_trace` grows per observation; the diff has a token budget,
the
> trusted zone does not) — `pipeline/evidence.rs` since the extraction.

`verifier_evidence_summary` renders `oracle_trace` in full. That trace
gains an
observation per verification round, and the repair gate (#1479) keeps
granting
rounds for as long as a measured budget affords them — so the one
channel that
grows without limit was also the one channel with no ceiling. Every
other input
to that prompt is bounded: the diff has a token budget, recall frames
have
`bound_recalled_frames`, and `Verdict::reasoning` got its cap in #1932.

Bounded to the newest 24 observations, with the drop **stated in-band**:

```
oracle_trace=[…76 earlier observation(s) omitted → candidate:pass → candidate:fail → …]
```

Three choices worth naming, because each has a wrong-looking
alternative:

- **Newest kept, oldest dropped.** The recent runs are what the verdict
weighs;
a trace clipped from the front would hand the verifier a history that
stops
  before the evidence.
- **Stated, not silent.** A trace that silently began mid-run reads as
the whole
run — the verifier would draw conclusions about a first observation that
was
  not the first.
- **Clipped where the value is constructed**, not at a downstream
consumer.
  That is the same "structural, not by convention" rule #1932 applied to
  `reasoning`, and it is why the stored `LadderSnapshot` and
`verdict_provenance` are deliberately untouched: the bound is on the
*prompt
  ingress*, not on the record.

24 is sized far above a normal run (a baseline plus a handful of
rounds), so
the bound only ever bites a pathological loop. It is a named constant
next to
its rationale rather than a literal.

## The witness

- [x] This PR includes a witness test

`a_pathological_oracle_trace_is_clipped_with_the_drop_stated` — a
100-observation
trace renders exactly 24 entries behind the `…76 earlier observation(s)
omitted →` marker, and still ends on the newest observation.

`an_ordinary_oracle_trace_renders_unchanged` is the other half, and the
one
that matters for regression: a 5-observation trace is asserted
**byte-identical
to `render_oracle_trace`**, main's own unbounded function, which is
still
present and still used for provenance. So every prompt the bound does
not bite
is unchanged to the byte — which also means no verdict-reuse digest
(#1431)
moves for an ordinary run.

Honest note on "fails on main": these pin a bound that does not exist on
`main`,
so the failure there is that `bounded_oracle_trace` is not defined — the
same
shape as #1932's witnesses for the `reasoning` cap, and the shape any
"add a missing ceiling" change has. The behavioural claim is carried by
the
second test, which compares against main's function directly rather than
against a copied expectation.

## The gate

- `cargo test -p stella-pipeline` — 585 + 5 + 2 + 5 + 6 + 4 = **607
passed, 0 failed**
- `cargo clippy -p stella-pipeline --all-targets` — clean
- `cargo fmt --check -p stella-pipeline` — clean
- `scripts/check-file-size.sh` — OK, none grew (`evidence.rs` was
extracted from
`pipeline.rs` precisely so this kind of channel can be added without
touching
  a god file, and that still holds)

Full workspace left to CI.

## Nothing left behind

`Refs #1787`, deliberately not `Closes` — this is the folded-in bound
only.
Item 1 (a provider-parity-aware structured verdict output path,
invariant 8)
remains open and is being approached from a different angle in #1964;
item 2
shipped as #1932 and item 3 as #1951.

Refs #1787

## Summary by Sourcery

Bound the oracle trace rendered in the verifier prompt to a fixed number
of recent observations and document the truncation in-band while
preserving full traces in stored provenance.

Enhancements:
- Introduce a bounded oracle trace renderer for the verifier prompt,
limiting the trusted-zone trace to the newest 24 observations and
prefixing output with an omission marker when older entries are dropped.

Tests:
- Add tests ensuring pathological long oracle traces are clipped with an
explicit omission notice and that ordinary short traces remain
byte-identical to the existing unbounded rendering.
macanderson added a commit that referenced this pull request Aug 7, 2026
…ngress (#1787) (#2002)

## Why this PR exists

**#1787's fix is not in `main`.** PR #1982 carried it, but its base was
the topic branch `unbreak-main-pipeline`, whose own PR (#1975) was
**closed, not merged**. #1982 then merged into that dead branch, so the
oracle-trace bound landed nowhere `main` can see, and nothing is
carrying that branch forward.

It also merged in a **broken** state. While the base was being
reconciled with `main`, git's auto-merge of the two
independently-written unbreaks concatenated both sides, leaving:

- `struct PassingShell` and `fn shell_call_result` **defined twice**
- `async fn a_revision_halts_at_the_step_where_the_tracked_test_flips`
defined twice
- a duplicate `ModelCallRole::Research` match arm (unreachable pattern)

None of that compiles. `unbreak-main-pipeline` currently holds it;
`main` is unaffected.

This PR is the clean landing: **`main` plus `evidence.rs`, and nothing
else.**

## What it does (#1787)

Bounds the oracle trace at the verifier-prompt ingress. The trace grows
once per verification round and the repair gate can keep granting rounds
while a measured budget affords them — so unlike the diff, which rides
under a token budget, this channel had **no ceiling at all**.

- `MAX_ORACLE_TRACE_OBSERVATIONS = 24` — sized far above a normal run
(baseline plus a handful of rounds) so the bound only bites a
pathological loop.
- `bounded_oracle_trace` keeps the **newest** observations and states
the drop **in-band** (`…N earlier observation(s) omitted → …`), so the
verifier reads "earlier observations exist" rather than a trace that
silently starts mid-run.
- The **stored snapshot keeps the full trace**; only the prompt ingress
is clipped — the structural-bound rule from #1932.

## Witnesses

- `a_pathological_oracle_trace_is_clipped_with_the_drop_stated` — a
100-observation trace renders clipped to the newest 24 with the omission
counted in-band.
- `an_ordinary_oracle_trace_renders_unchanged` — the bound does not
touch a normal run, so this cannot ship as "always clip".

Observations alternate pass/fail in the fixture so a clipped render is
distinguishable from a repeated one.

## Verification

- `cargo test -p stella-pipeline` — **585 pass**, 0 fail, including both
witnesses above
- `cargo fmt --check -p stella-pipeline` — clean
- Diff vs `main` is exactly one file:
`crates/stella-pipeline/src/pipeline/evidence.rs` (+74/−2)

## CI is red on `main`'s breaks, not this diff

This branch is merged up to current `main`. Every failing step fails in
a file this PR does not touch, and each already has a dedicated unbreak
in flight:

| Failing step | Where | Covered by |
|---|---|---|
| `check-file-size` | `scripts/file-size-baseline.txt` (parallel-merge
skew) | **#2003**, **#2008** |
| `cargo fmt --check` | not this crate's file | **#2005** |
| clippy: unused `spend` / unused `mut` | `pipeline/scope_stage.rs:34` —
a dead local `#1985` left behind | **#2000** |
| rustdoc: unresolved `CompactionRewrite` | `stella-protocol` |
**#2010** |

The clippy one is worth naming precisely, since it is `stella-pipeline`:
`main`'s `scope_stage.rs` binds `let mut spend = Spend { budget, total
};` and then never uses it — the loop constructs a fresh `Spend` inline
per iteration. `spend` occurs exactly once in the file. That is `main`'s
dead local, untouched by this PR.

No competing unbreak is included here on purpose — six are already open
against `main`, and duplicating one is how `main` gets re-broken.

## Note on the dead branch

`unbreak-main-pipeline` still holds the duplicate-definition breakage
and the only copy of #1982's merge. It is not reachable from `main` and
its PR is closed, so nothing needs to be reverted — but it should not be
revived without first taking `main`'s copies of `flip_halt_arming.rs`,
`management_prompt/tests.rs` and `scope_stage.rs`, which is what this PR
does. Filed as #2001.

Closes #1787
macanderson added a commit that referenced this pull request Aug 7, 2026
…#1976) (#2036)

## What & why

Three times a PR has landed on `main` that silently deleted code another
PR
added to the same file hours earlier, and CI could not see it.

The most recent (fixed in #1975): #1951 rewrote
`crates/stella-pipeline/src/pipeline/tests/verification_hardening.rs`
from a
pre-#1945 base, deleting the `PassingShell` double, `shell_call_result`,
and the
witness `a_revision_halts_at_the_step_where_the_tracked_test_flips` that
#1945
had added hours earlier.

It was caught **only by luck**: #1945 had also added a `mod
flip_halt_arming;`
line that survived the rewrite and referenced two of the deleted
symbols, so
`main` went red. Had #1945 added only the test and its doubles — no new
module —
the deletion would have compiled clean and silently removed a witness
from the
tree. *A witness that no longer exists cannot fail*, so nothing
downstream would
ever have reported it. Same shape on record for #1860 reverting #1836's
forwarding in four crates.

Both PRs are green against the `main` they branched from, and the merge
is
textually clean: git has no conflict to report, because one side simply
does not
contain the other's lines.

### The design decision that matters

`scripts/check-deleted-tests.sh` compares the **base branch tip**
against the
**merge result** — never the PR's branch point.

That pair is the whole guard. Comparing against the branch point would
miss
exactly this defect: a test added to `main` *after* the PR branched is
absent
from the branch point too, so its disappearance would look like nothing
at all.
Comparing main's tip against the merged tree asks the question that
matters —
"did everything main had survive this merge?" — and is quiet on a merely
stale
branch, because git merges main's own additions in unless the PR's side
actively
removed them.

On a `pull_request` event the checkout is `refs/pull/N/merge`, so
`HEAD^1` *is*
the base branch tip. The guard needs no PR metadata, and because it
compares two
**trees** rather than two histories, `fetch-depth: 2` is sufficient — no
full-depth clone.

### It asks for an acknowledgement, it does not forbid deletion

A removed test is not automatically wrong — renames, folding into a
table-driven
case, and deliberate removal with the feature covered are all ordinary.
So a
removal fails **only while unnamed**: writing the test's name in the PR
description (or a commit message) passes it.

That mechanism is deliberately weak. The goal is not to adjudicate
whether a
deletion was correct — a script cannot — but to convert an invisible
deletion
into a sentence a reviewer reads.

### Deliberately NOT a `GATE_STEPS` entry

It is the one question here about *two* trees, and a local `make gate`
has no
second tree to compare, so there is nothing for it to do there. This
also means
the five-edit gate-parity dance does not apply; `check-gate-parity`
still reports
25 steps, unchanged.

Closes #1976

## The witness

- [x] This PR includes a witness test (fails on `main`, passes here),
**or**
- [ ] No witness needed

The witness is reproduced from **real history**, not a synthetic fixture
—
`eddf9700` is #1945 (added the witness), `2a142b26` is #1951 (deleted
it):

```
$ PR_BODY="" ./scripts/check-deleted-tests.sh eddf970 2a142b2
check-deleted-tests: FAILED

These tests exist in eddf970 but not in the merged tree, and nothing
in the PR description or the branch's commit messages names them:

  a_revision_halts_at_the_step_where_the_tracked_test_flips
```

It names exactly the test that was really lost. The other three required
behaviours, all verified:

```
$ PR_BODY="Folded a_revision_halts_at_the_step_where_the_tracked_test_flips into …" \
    ./scripts/check-deleted-tests.sh eddf970 2a142b2
check-deleted-tests: OK — 1 removed test(s), each named in the PR description or a commit.

$ ./scripts/check-deleted-tests.sh 1feb029 eddf970      # a range that only ADDS tests
check-deleted-tests: OK — 6790 test(s) in 1feb029, none lost by the merge.

$ ./scripts/check-deleted-tests.sh origin/main HEAD       # this very branch
check-deleted-tests: OK — 6865 test(s) in origin/main, none lost by the merge.
```

A genuine rename is reported too (the old name is gone), which is
intended —
naming it in the PR is the whole cost.

## The gate

- [x] `shellcheck` — clean
- [x] `make guards-fast` — all green, including `check-gate-parity` (25
steps,
      unchanged) and `check-action-pins`
- [x] Docs updated: AGENTS.md § witness tests gains the paragraph, and
the CI
description now lists the guard among what `ci.yml` adds beyond the gate
- [x] `Closes #1976` appears both here and as a commit trailer

No Rust changed, so fmt/clippy/test are untouched by this PR.

## Nothing left behind

- [x] Filed: see below

Two limitations are **measured and documented in the script header**
rather than
left implicit:

1. **The key is the bare test name, unqualified by file or module.**
That makes
a test *moved* between modules silently fine (the common legitimate
case),
at the cost that a duplicated name masks a deletion. Measured: 51 of
6867
distinct test names (0.74%) are duplicated today, nearly all the
per-adapter
provider suites where one contract is asserted against each vendor under
one
name. The house style of long sentence-shaped test names is what makes
the
   unqualified key work — it is not a general assumption.
2. **`#[test]` inside a multi-line string fixture is counted.** Two
lines in the
tree (`witness/density.rs`, `candidate_ws/witness_tools.rs` — code that
analyses test code) put `#[test]` at the start of a continuation line
inside
a string literal. It is symmetric noise, so a difference detector
cancels it;
it could only false-positive if such a fixture were edited, which lands
in
   the acknowledge path by design.

## Ground-rule check

- [x] No I/O added to `stella-core`; no new deps
- [x] No new outbound network calls

## Anything reviewers should know?

`fetch-depth: 2` on the `check` job's checkout is the one change that
touches
every CI run. It is one extra commit, not a full clone — deliberately
the
smallest thing that makes the merge commit's first parent readable.

The guard runs on `pull_request` only: on a squash-merged push to `main`
there is
no merge commit to inspect, and the report would arrive too late to act
on
anyway. The script self-skips on a non-merge `HEAD` with no explicit
base, so
running it by hand needs a base ref: `./scripts/check-deleted-tests.sh
origin/main`.

## Summary by Sourcery

Add a CI guard that detects tests removed by a PR’s merge unless their
deletion is explicitly acknowledged, and document this behaviour in
agent guidance.

CI:
- Update the main CI workflow to fetch two commits for pull_request
checkouts and run a deleted-test guard that compares the base branch tip
to the merge result, passing only when removed tests are named in the PR
body.

Documentation:
- Extend AGENTS.md to describe the deleted-test guard, its scope, and
its requirement to mention removed tests in PR descriptions.

Chores:
- Add the scripts/check-deleted-tests.sh utility to scan test trees
across two revisions and enforce acknowledgement of removed tests.
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.

1 participant