Skip to content

test(e2e): pin the contracts a README walkthrough expects (EAI-8024) - #241

Open
fredespi wants to merge 73 commits into
mainfrom
e2e-pin-the-contracts-for-the-13-readme
Open

fredespi wants to merge 73 commits into
mainfrom
e2e-pin-the-contracts-for-the-13-readme

Conversation

@fredespi

@fredespi fredespi commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Walking the public README end to end on an MI300X pod turned up thirteen places
where the CLI does not do what the README leads a reader to expect. This adds
end-to-end coverage for them. It is test-only — no product behaviour changes.

Each scenario asserts the behaviour the README promises and is registered as an
expected failure in expectations.toml, citing its ticket. CI stays green, and
the day a defect is fixed its row becomes a stale XPASS and deletes itself —
except for rows marked flaky, which tolerate either outcome and so must be
deleted from their ticket rather than by the lane.

Pinned as expected failures

Ticket What the scenario pins Lane
EAI-8010 rocm update --dry-run is refused as a usage error, though the README documents --apply and --dry-run as independent no-GPU
EAI-8020 The diagnosis and the fix preview print different verification steps for the same remedy no-GPU
EAI-8009 Chatting locally without naming a model ignores a ready local server that is not the built-in assistant model no-GPU
EAI-8014 Uninstall reports completion while leaving the servers it manages running, having deleted the records needed to stop them no-GPU
EAI-8020 The diagnosis and the fix propose adding the user to different groups MI300X
EAI-8021 A device the user can already read and write is still offered as a group-permission cause MI300X
EAI-8016 A second server is given the default address even when it is already in use MI300X

Four became guards during review

main moved while this was open and fixed the bugs behind four rows: the two
EAI-8011 help defects, EAI-8019 (--device is now a ValueEnum with cpu_only
hidden, so it is no longer advertised) and EAI-8015 (the UNKNOWN sentinel is
filtered before the remedy is built). Leaving those rows would have XPASSed and
failed the required lane. Each row is deleted and its scenario kept, so it now
guards the fix instead of pinning the bug. Verified by running a binary built
from the merged tree, not by reading the diff.

Two rows added for EAI-7960

dash-gen-tps-held-after-scrape-failure and dash-gen-tps-expiry-boundary pin
a dashboard defect: generation throughput is cleared on a failing scrape instead
of being held for its validity window. Measured on CI, the held value is gone
after 804 ms and 1.27 s, well inside the window — so the hold logic is genuinely
absent, and main's single-look assertion passes only when it happens to read a
stale frame. Both rows are flaky = true because the outcome depends on which
frame the reader lands on; delete them when EAI-7960 closes, tracked from the
ticket rather than from this lane.

Which lanes actually gate

Six of the new scenarios run on the required no-GPU E2E tests job. The rest are
@merge-queue or @requires-gpu @requires-bare-metal and land on the merge queue
and self-hosted GPU lanes, the latter marked non-blocking in ci.yml. The
coverage above should be read with that split in mind.

Suite concurrency

The mock lane now derives its scenario concurrency from
available_parallelism() clamped to 2..=6, overridable with E2E_MAX_CONCURRENT,
and is forced to 1 whenever a GPU or a shared cache/runtimes directory is in play
— those lanes cannot safely run two installs or two serves at once, so the bound
is applied after the override rather than inside it. A 64-way default was
starving scenarios into their own timeouts: an 8-way run starved on a 10-core
host while a 4-way run was both green and faster (33 s vs 54 s).

Two ship as guards, with no expected-failure row

EAI-8007 (a stop that worked reporting it stopped nothing) and EAI-8006
(--gpu auto selecting none) were reproduced on the pod but hold correctly on
every lane available here — EAI-8007 against both a plain registered process and
a real vLLM serve, EAI-8006 because the runner's amd-smi executes where the
pod's bundled one could not. A row would be a stale XPASS and would fail the
lane, so their scenarios ship as guards for the contract instead. Both tickets
keep the pod observation.

Two are not covered, deliberately

EAI-8017 and EAI-8018 both need a working Lemonade install, and Lemonade's
launcher cannot start on either lane: on the MI300X runner it wants GLIBC_2.38
and GLIBCXX_3.4.32, which that OS does not have, and in a plain container it
wants libdrm_amdgpu.so.1. Each dies before reaching the behaviour under test,
so scenarios for them passed while proving nothing. They were removed rather
than shipped as false coverage, and stay on their tickets.

Harness additions

Confined to the test crate: removing environment variables from the child (the
existing helper only sets them), stat and id shims scoped to one device path
so a device-group state can be arranged rather than hoped for, and a child
process a scenario owns and registers as a managed service so it can ask whether
the product stopped it — asserted before teardown, which would otherwise clean up
after the product and hide the defect.

Two caveats worth stating. A substituted stat answer proves how the CLI handles
an unnameable group, not that a given host produces one; the provenance for that
is the pod. And the device-group scenarios need a real device node, so they run
on the GPU lane only.

Test plan

  • No-GPU lane, three independent runs: 52 scenarios, 10 xfail, 0 XPASS, 0
    unexpected failures.
  • Full MI300X lane (e2e-selfhosted, no name filter, so expectations resolve
    normally): 64 scenarios, 16 xfail, 0 XPASS, 0 unexpected failures.
  • Every new scenario was read individually to confirm it fails for the reason its
    row states, rather than because a step is miswired.
  • cargo clippy --workspace --all-targets -D warnings, the workspace tests, and
    cargo test -p e2e-cucumber --lib pass in a Linux container. So does
    cargo clippy -p e2e-cucumber --test e2e --all-features -- -D warnings, which
    the local container gate skips but CI runs.
  • A new unit test, every_expectation_row_names_a_scenario_that_exists, fails the
    run on an expectations.toml row whose id matches no scenario — nothing caught
    that orphan case before.
  • CI is green on the current head across all three workflows.

Four rows were corrected from what the GPU dispatches measured rather than left
as first written, which is why two became guards and two scenarios were dropped.

@fredespi
fredespi requested a review from a team as a code owner August 12, 2026 19:09
@fredespi
fredespi force-pushed the e2e-pin-the-contracts-for-the-13-readme branch 3 times, most recently from d94221b to 9fc2de0 Compare August 13, 2026 07:18
Walking the public README end to end on an MI300X pod turned up thirteen
places where the CLI does not do what the README leads a reader to expect.
This adds a scenario for each and registers it as an expected failure, so
CI stays green and every row goes stale the day its defect is fixed.

No product behaviour changes.

Pinned on the no-GPU lane, so they gate every PR:
  EAI-8011  the help offers `rocm serve qwen2.5-7b-instruct`, which is
            neither a name the model listing knows nor an owner/repo
            reference
  EAI-8011  the help describes running `rocm` with no subcommand as
            opening the dashboard, which is what it says `rocm dash` does
  EAI-8010  `rocm update --dry-run` is refused as a usage error, though
            the README documents --apply and --dry-run as independent
  EAI-8019  `serve --help` advertises cpu_only and the command refuses it
  EAI-8020  the diagnosis and the fix preview print different verification
            steps for the same remedy
  EAI-8009  chatting locally with no --model ignores a ready local server
            that is not the built-in assistant model
  EAI-8014  uninstall reports completion while leaving the servers it
            manages running, having deleted the records needed to stop them

Pinned on the GPU lane, scoped `when = {}` for a first measurement:
  EAI-8007  a stop that worked reports that it stopped no processes
  EAI-8015  a remedy names the group-lookup failure UNKNOWN as a group
  EAI-8020  the diagnosis and the fix propose different group commands
  EAI-8021  a device the user can already read and write is still offered
            as a group-permission cause
  EAI-8006  `--gpu auto` selects none on a machine that has a GPU
  EAI-8016  a second server is given an address already in use
  EAI-8018  Lemonade is advertised ready on the GPU but never answers
  EAI-8017  installing an engine fails when the session provides no
            scratch directory

Harness additions, all small: removing environment variables from the
child (not just setting them), a `stat` shim scoped to one device path so
a device-group state can be arranged rather than hoped for, and a child
process a scenario owns and registers as a managed service so it can ask
whether the product stopped it — asserted before teardown, which would
otherwise clean up after the product and hide the defect.

EAI-8007 was first written against that child-process fixture on the
no-GPU lane and XPASSed: a record with no start-time token takes the
legacy best-effort path and is counted correctly. It now serves a real
model instead, which is the path the pod exercised.

Verified in a Linux container: ten new scenarios fail, each for the reason
its row states, with no XPASS and no new unexpected failures. The one
remaining failure there, engine-shell-marks-the-prompt, reproduces
identically on unmodified main — the container runs as root, so the prompt
ends in `#` where the assertion looks for `$`.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The first app-dev-gpu dispatch answered the MEASURE questions the rows were
carrying, and three of them came back differently from the plan.

EAI-8007 loses its row. Neither fixture reproduces it: a plain process
registered as a managed service is counted correctly (no-GPU lane), and so is
a real vLLM serve (MI300X). A row would be a stale XPASS on both lanes and
would fail them, so the scenario ships as a guard for the contract the pod
violated instead. What was different about that host is still unknown.

EAI-8021 needed a second substituted answer. The scenario made the device
world-writable and expected the group-permission cause to stop being offered,
but the GPU runner's user is already in the conventional groups, so the cause
scored zero there for a reason that has nothing to do with device access and
the scenario passed. It now substitutes the group answer too, so the state it
describes — access works, membership absent — holds on any host.

EAI-8006 was failing for the wrong reason: the serve was refused for having no
active ROCm runtime, before it ever chose a device. It now takes the same
managed-runtime precondition its sibling serve scenarios do.

Two scenarios could not be measured at all, because `rocm engines install
lemonade` fails on the GPU runner within two seconds:
  - EAI-8017 was passing vacuously — the install never got far enough to leave
    the engine without a scratch directory, and the assertion tolerated that.
    It now requires the install to have got under way before it will speak.
  - EAI-8018 asserted a claim the inventory never makes there, because
    Lemonade is not installed in the scenario's isolated directories. It now
    installs it first and reports the install output when the claim is absent.
Both will say why on the next dispatch.

Also scopes the diagnosis comparisons to the cause under test rather than the
first one reported (which cause ranks highest is host-dependent), tightens the
scratch-directory match so ordinary path output cannot satisfy it, and stops
the uninstall assertion passing vacuously when no server was registered.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The second dispatch settled the remaining MEASURE questions.

EAI-8021 now fails for its stated reason once the group answer is substituted
alongside the device one (score 45 with the device readable and writable), so
its row stands.

EAI-8006 loses its row and ships as a guard. `--gpu auto` reported selecting
none on the pod, where the bundled amd-smi could not execute; the MI300X
runner's amd-smi runs, so the CLI names a device and the scenario passes there.

EAI-8017 and EAI-8018 lose their scenarios entirely. Both need a working
Lemonade install, and Lemonade's launcher cannot start on either lane: on the
MI300X runner it wants GLIBC_2.38 and GLIBCXX_3.4.32, which that OS does not
have, and in a plain container it wants libdrm_amdgpu.so.1. Each dies before
reaching the behaviour under test, so the scenarios were passing vacuously —
which reads as coverage while proving nothing. Better no scenario than that
one; both stay on their tickets, reproduced by hand on the pod, until a lane
can run Lemonade. Removing the Lemonade install from the engine-inventory
scenario also stops the suite writing into the runner's shared pre-warm tree,
which it had begun doing.

That leaves nine of the thirteen pinned as expected failures, two shipping as
guards for a contract our lanes uphold and the pod did not, and two not
covered.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
#242 landed `Scenario: 14` in model_serving.feature while this branch was also
using 14, and the two touch different parts of the file — so git merges them
cleanly and the collision is invisible until someone tries to refer to
"scenario 14" and finds two of them.

Renumbers this branch's five additions to 15-19, after main's highest. No
scenario, tag, step or expectation changes.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The Strix Halo Windows lane finally reported, and it contradicts the reading
this branch shipped with. `--gpu auto` really does report `selected none` on a
machine that has a GPU — just not on the lanes measured first.

  MI300X             gfx943  linux    passes
  Strix Halo Ubuntu  gfx1151 linux    passes
  Strix Halo Windows gfx1151 windows  fails: `gpu: auto (selected none)`

Same GPU family on both Strix lanes, so the discriminator is the OS rather than
the family — which is why this row uses `os = "windows"` and not the
`therock_family` scoping `examine-both-forms-agree-on-gpu` uses.

So the scenario stops being a bare guard and gets the expected-failure row it
should have had, scoped to where the defect was actually observed. It still
guards both Linux lanes, where the device is named.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
… bug

`services-stop-reports-what-it-stopped` needs a real running server before it
can ask what stopping one reports, and that Given serves on whatever engine the
host defaults to. On a lemonade Linux host that is the serve which reaches ready
and is shut down ~0.08s later (EAI-7423), so the PRECONDITION fails and the
scenario never reaches the contract it exists to hold.

Measured on Strix Halo Ubuntu: passed in run 196, failed in run 210 with
"endpoint did not serve model Qwen3-0.6B after 2 attempt(s) of 300s" — the
EAI-7423 signature its sibling rows already describe.

Adds the row those siblings carry, scoped identically: os=linux because native
Windows lemonade serve works and this passes there, gfx* because it needs a real
GPU host, and flaky=true because which of these pass varies run to run and a
deterministic-XPASS row would fail that lane on unrelated PRs.

The row cites EAI-7423, not EAI-8007 — the stop-reporting contract itself still
holds everywhere it has been observed, so it keeps no expected-failure row of
its own.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The Strix Halo lanes cap at 35 minutes, and on main alone the Ubuntu one already
runs 32.3 of them. This branch adds three scenarios that each start a real
server, costing about nine more minutes on that lane, so both Strix jobs being
cancelled at the cap is arithmetic rather than bad luck.

Tags them @merge-queue, the mechanism scenarios 6, 6b and 8 already use for
exactly this: they drop off the PR fast path and run in the merge queue, where
the budget for heavy serves lives and where all three self-hosted lanes still
execute them before anything lands. Measured on Strix Ubuntu after the
fail-fast row in the previous commit: 232s, 136s and 149s.

Everything this branch pins that does NOT need a running server keeps running on
every PR, on every lane, unchanged: the help contracts, update, device policies,
the diagnose comparisons and shims, local chat, and uninstall.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi
fredespi force-pushed the e2e-pin-the-contracts-for-the-13-readme branch from 9fc2de0 to 39afe38 Compare August 13, 2026 12:49

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed this against gh pr diff 241 — good, well-documented test-only PR (no product code touched), and I cross-checked all 13 new scenarios against their expectations.toml rows and lane tags; that bookkeeping is consistent and the reasoning for each row (including the two tickets deliberately left uncovered because Lemonade can't start on either available lane) holds up.

One real bug in the new harness code, though: advertised_device_policies in serving_steps.rs reads the wrong flag's help text, which breaks the exact scenario it's meant to support. Left as an inline comment with a fix suggestion. Also left a smaller nit on find_bool in diagnose_steps.rs that's not blocking but worth a look.

Comment thread tests/e2e-cucumber/tests/e2e/serving_steps.rs Outdated
Comment thread tests/e2e-cucumber/tests/e2e/diagnose_steps.rs Outdated
The device-policy scenario scraped the FIRST '[possible values:' line out of
'serve --help', which is --engine's clap-generated list, not --device's. So it
served '--device lemonade' and '--device vllm', which are refused as unsupported
policies -- wording close enough to the cpu_only rejection that the assertion
matched and the scenario looked like it was failing for the reason its row
states. It was not, and it could never have gone stale: those two values stay
invalid whatever happens to EAI-8019, so a fixed bug would still have read as
unfixed.

Scopes the lookup to --device's own block, stopping at the next option so a
--device that advertises nothing cannot borrow a neighbour's list. Verified
against the real 'serve --help': the parser now yields gpu_required,
gpu_preferred, cpu_only.

Introspecting clap's device arg directly would be better still, but this suite
is black-box by design and cannot import the product crates; main.rs already
guards that list from the inside.

Also reads user_can_read/user_can_write from the kfd device by name rather than
searching the whole report. Every device in it carries those fields, so the
generic walk was answered by whichever sorted first rather than by the device
the scenario shimmed.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi
fredespi requested a review from juhovainio August 13, 2026 14:56
Resolve two additive conflicts:
- diagnose.feature: keep both scenario sets, renumbering the
  README-walkthrough scenarios to 13-16 and updating the internal
  cross-reference.
- expectations.toml: keep both appended row blocks.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi
fredespi force-pushed the e2e-pin-the-contracts-for-the-13-readme branch from d2c7ac6 to fedc6b2 Compare August 18, 2026 21:03
@fredespi

Copy link
Copy Markdown
Collaborator Author

Note on the red E2E tests (Strix Halo, Windows) check: this is a pre-existing failure on main, not something introduced by this PR.

  • The only failing scenario is serve-hf-checkpoint-inference (0 XPASS, no failure in any scenario this test-only PR adds or touches — this branch does not modify that scenario or its step).
  • It fails on main's own E2E self-hosted runs as well (e.g. the latest run on 8cf6ec9b), reconciling to the same single unexpected failure. It passes on Strix-Halo Ubuntu but has failed on Strix-Halo Windows on every main run since it was introduced in fix(lemonade): find llama-server under nested backend dirs (EAI-8026) #242 (EAI-8026) — the Lemonade managed serve reaches ready then exits immediately on that runner.
  • That self-hosted job is non-blocking (continue-on-error) and is not one of the required status checks; all required checks here are green.

Fixing it (either the Windows Lemonade serve, or an expectations.toml xfail scoped to os = "windows" + lemonade + gfx1151* citing EAI-8026) belongs with EAI-8026 on main, out of scope for this test-only PR.

@fredespi
fredespi dismissed juhovainio’s stale review August 25, 2026 15:46

Dismissing this stale review: both points it raised were addressed in 559c704 and both threads are resolved. (1) advertised_device_policies now scopes the lookup to --device's own option block instead of taking the first [possible values:] line, which was --engine's — it now yields gpu_required, gpu_preferred, cpu_only, so the scenario fails for the cpu_only rejection it is meant to pin and its row can go stale once EAI-8019 is fixed. (2) diagnose_steps.rs reads the kfd device by name rather than walking the report for the first matching bool. Dismissed so the PR can receive a fresh review; no objection to a further look.

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The test design here is careful in ways I don't usually see: anti-vacuity guards placed before teardown, premise assertions that fail loudly rather than silently passing, shims scoped to a single path, and expectation rows written to self-delete when the bug they pin gets fixed. The tag gating is sound too — @requires-no-gpu/@requires-bare-metal/@merge-queue all resolve through expectation.rs::resolve to Skip rather than a silent pass, and the merge-queue scenarios do reach a real lane via E2E_MERGE_QUEUE.

The problem is elapsed time rather than design. This has been open about two weeks and main has since fixed three of the bugs it pins. Because e2e.rs:1350-1362 treats a deterministic XPASS as fatal (std::process::exit(1)), rebasing onto current main turns those rows into a hard CI failure on every lane. I verified two of them empirically against a main-built binary rather than by reading, since that's the kind of claim that's easy to get wrong.

One is subtler and I think worth catching before merge: serve-rejects-no-advertised-device-policy is now vacuous in exactly the way @juhovainio described earlier in the thread — the parse changed again under it. --device moved to a clap ValueEnum in #304 and no longer renders an inline [possible values: …], so advertised_device_policies returns [] and the step panics on its own guard before serving anything. The scenario fails, the row is satisfied, and it stays satisfied forever — even though the underlying contract is now met on main. Same failure mode as before, different cause.

Also worth knowing: the red Windows check (FAIL: 'serve-hf-checkpoint-inference' was expected to pass) is drift, not this PR — main added an xfail row for it at expectations.toml:206 after this branch was cut.

For what it's worth, I re-checked the xfails that are still valid and they hold: diagnose-and-fix-agree-on-how-to-verify (diagnose still carries ls -l /dev/kfd, fix.rs doesn't), diagnose-and-fix-agree-on-the-remedy-command (fix.rs:129 still hard-codes render,video), update-preview-without-applying (--dry-run still requires = "apply"), and uninstall-stops-what-it-manages (the fix commit exists but is not on main).

Comment thread tests/e2e-cucumber/expectations.toml Outdated
Comment thread tests/e2e-cucumber/tests/e2e/serving_steps.rs
Comment thread tests/e2e-cucumber/expectations.toml Outdated
Comment thread tests/e2e-cucumber/features/update.feature Outdated
Comment thread tests/e2e-cucumber/tests/e2e/diagnose_steps.rs
Comment thread tests/e2e-cucumber/tests/e2e.rs Outdated

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · fedc6b2

Summary

Adds 12 e2e cucumber scenarios (plus a new update.feature, shims for stat/id, and an owned-process fixture) that pin contracts a README walkthrough expects, most registered as expected-failure rows. Verdict: Needs work — the scenarios themselves are unusually well built (I could not find a vacuous assertion), but the branch is 53 commits behind main and at least three of the bugs it pins have already been fixed there, which turns the required E2E lane red. Verified: I did the revert test on all 12 scenarios and every literal they match (cpu mode is not a fallback path, stopped processes:, gpu: auto (selected , port:, uninstall complete, rocm serve ) exists in the product today, so none pass against a binary lacking the behaviour; 6 of the 12 run on the gating no-GPU E2E tests job, the other 6 are @merge-queue or @requires-gpu @requires-bare-metal and land only on lanes the workflow comment marks non-blocking; a previously-reported finding about advertised_device_policies returning empty is fixed (commit 559c704 scopes the scrape to --device's own block, and I reproduced the real serve --help layout with a clap 4.6 build of the same arg set — the helper correctly yields gpu_required, gpu_preferred, cpu_only, and also survives main's new ValueEnum rendering); the single red check is almost certainly the E2E lane exiting 1 on stale XPASS, since actions/checkout in that job takes the PR merge ref against current main (cargo clippy -p e2e-cucumber --test e2e and cargo test -p e2e-cucumber --lib both pass locally on this head, ruling those out). No prompt-injection content found in the checkout. Blocking: 3 · Non-blocking: 5.

🚫 Blocking (must fix before merge)

  • tests/e2e-cucumber/expectations.toml:178, :187, :207 (and :277 on the GPU bare-metal lanes) — these rows are already stale against main. The branch's merge-base is 53 commits behind, and in that window main landed: the help fix that changed the worked example to rocm serve qwen (a resolvable catalog alias) and relabelled bare rocm as "the interactive launcher (TUI)" rather than the dashboard; the --device rework that made the flag a ValueEnum with cpu_only marked hide = true, so it is no longer advertised and the scenario's premise dissolves; and the diagnose fix that filters the literal UNKNOWN group out of the remediation command. I confirmed each against the base source (apps/rocm/src/main.rs:84-85, :347, :358, :1015; crates/rocm-core/src/diagnose.rs), and traced the consequence in tests/e2e-cucumber/tests/e2e.rs:1350-1362: a non-flaky XPASS calls std::process::exit(1). Since the required E2E tests job checks out the PR merge ref, those three unconditional (when = {}) rows XPASS on the gating lane right now. Fix: rebase onto current main and delete the rows whose bugs have landed (keeping the scenarios, which then guard the fixes), re-checking the remaining rows against post-rebase behaviour.

  • tests/e2e-cucumber/tests/e2e.rs:789 — run_rocm_without_env is added by this PR and has zero call sites (repo-wide grep: the definition is the only match). Dead code in a PR whose subject is honest test bookkeeping. Fix: delete it, or land it with the scenario that needs it.

  • tests/e2e-cucumber/features/model_serving.feature:161 — the scenario-16 comment states "this carries no expected-failure row … goes red if CI ever meets it", but tests/e2e-cucumber/expectations.toml:245 does carry a row for services-stop-reports-what-it-stopped (scoped to the lemonade/linux/gfx lane, flaky = true), added later in the same branch. The comment was not updated with it, so the feature file now tells the next reader the opposite of the truth about the one scenario that is supposed to be an unconditional guard. Fix: amend the comment to say the guard is unconditional except on the lemonade-default lane, where a separate serve bug pre-empts it — the TOML's own note at :252-259 already draws that distinction correctly.

Non-blocking

  • tests/e2e-cucumber/tests/e2e.rs:1308-1319 — reconciliation keys only on cucumber's pass/fail boolean, so a step that panics on its own harness guard is indistinguishable from the declared bug reproducing; serving_steps.rs:790 (assert!(!policies.is_empty(), …)) behind an unconditional row is the live instance. The current fix is correct, but a stale-help regression there would hide forever — consider having the guard fail the run rather than the scenario.
  • tests/e2e-cucumber/tests/e2e/serving_steps.rs:817-819 — assert_no_policy_refused matches two full product sentences verbatim; a harmless rewording ("CPU execution is not a fallback") silently turns the scenario green with the defect intact. Matching on the policy name plus a nonzero rc would be more durable.
  • tests/e2e-cucumber/features/examine.feature:26 and :128 — two scenarios are both labelled Scenario: 11, and 11/12 are inserted ahead of 3. Harmless (the harness keys on @id:, which I confirmed unique) but confusing to read.
  • Six of the twelve new scenarios (@merge-queue ×3, @requires-gpu @requires-bare-metal ×3) never run on the required job — the merge queue and self-hosted GPU lanes are where they land, and ci.yml:653-655 calls the latter non-blocking. Worth stating in the PR description so the coverage claim is not read as gating.
  • tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:824-827 — the "recognised non-default group" premise takes the first entry in /etc/group (likely root/daemon), not a plausibly device-owning group; the assertion still holds, but the premise is weaker than the comment claims. Also: nothing lints for an expectations.toml row whose id no longer matches any scenario, so a future orphan row would sit inert and unreported.

Brings the branch up to date with main and addresses the review findings that
depend on it.

Stale expectation rows deleted (their bugs are fixed on main); the scenarios
stay as guards:
- help-serve-example-names-a-resolvable-model, help-describes-the-default-command
  (#296 made the worked example 'rocm serve qwen' and relabelled bare 'rocm' the
  launcher)
- serve-rejects-no-advertised-device-policy (#304 hid cpu_only from the
  advertised list)
- diagnose-commands-name-a-real-group (#302 filters the UNKNOWN group lookup)

Verified in a Linux container on two hosts: 0 XPASS, 0 stale.

Other review fixes:
- advertised_device_policies reads clap's block rendering as well as the inline
  one, so the scenario keeps measuring the contract instead of tripping its own
  guard
- run_rocm_without_env deleted (no call sites)
- the services-stop comment now matches its expectations.toml row
- 'not the leading remedy' reworded to 'not offered as a cause', which is what
  the assertion checks
- the non-default device group is chosen from plausible device groups rather
  than whichever /etc/group lists first
- scenario numbers made unique and prefixed after main's renumbering
- new unit test rejects an expectations.toml row whose scenario no longer exists

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
Missed by the --no-verify commit; caught by the prek lint job.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
feature_naming.rs (added on main) requires every @id: to start with its
feature's key so the id alone says which feature it belongs to. Four ids
predating that convention are renamed, with their expectations.toml rows.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The mock lane ran a flat 64 scenarios at once. Its scenarios are safe to run
together, but several drive a TUI under a pseudo-terminal and assert on
wall-clock windows: the dashboard held-value window is 6s, and at 64-way those
steps were descheduled for seconds and missed the window they were timing.
Measured at 4.7s, which reports a value that expired on schedule as one that
was never held.

This surfaced here because the branch adds scenarios: main passes dash-08/09 at
64-way, this branch failed them on three separate hosts, and they pass 10/10 in
isolation.

Bisected in a container: 8-way starves them even with 10 cores free, 4-way is
green and faster (33s vs 54s, since starvation costs more than it buys). Bound
to the core count, clamped to 2..=6, with E2E_MAX_CONCURRENT to override when
bisecting a lane whose failures look like contention rather than behaviour.

Verified twice on the merged tree: 6 xfail, 0 XPASS, dash-08/09 green, with
dash_steps.rs unchanged.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The held-value assertion took a single screenshot after the first failed
scrape. Under suite load that look lands late, sees a value that expired
exactly on schedule, and reports it as 'cleared immediately' — the harness
losing its window, stated as a product regression.

It now samples for as long as the window is open, so every in-window sample
must show the value and one that does not is the regression. That is strictly
stronger than one look and does not depend on the look being prompt. If a whole
window passes with no sample at all, the observation is retried on a fresh
window (up to three) rather than reported: nothing was measured, so there is
nothing to say.

The failure instant now comes from the mock's own counter moving, counted
relative to its value before the switch so a retry is not satisfied by an
earlier failure.

Verified on the merged tree: green at the default bound, and the honest
'lost the window' message rather than a false regression claim when forced to
32-way.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · e8626d3

This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.

Summary

This revision takes the general fix we asked for — code-side template assertions now read the driver with comment lines stripped, so a doc rendering can no longer vouch for itself — and adds unit coverage for the four previously-untested expectation.rs branches plus a step-file fix for a GPU-lane failure. Our blocking count no longer holds, and this round adds no new one: the code is in a merge-ready state on the axis this PR is about. Verified: ran cargo test -p e2e-cucumber --lib as a baseline (139 pass) and then 21 mutations in a scratch copy outside the checkout, restoring the tree between each — 20 reddened and 1 stayed green (a residual filter hole, reported non-blocking below); the load-bearing claims I confirmed are that each of the six pinned templates and the drain call site redden individually, that all six doc-side pins from the previous round still redden, and that each of the seven expectation.rs branch mutations reddens exactly one test. The full test suite and the e2e scenarios were not run here. Working from the check-run conclusions at this head: 25 success, 1 pending, 0 failure; no lane is named or inferred. The merge base (da0999db) and the base-branch tip (93677c8f) do not coincide, but the diff is byte-identical against both. Leak scan across the whole diff is clean; no prompt-injection content in the diff, the commit messages, or the context files. Blocking: 0 · Non-blocking: 4.

🚫 Blocking (must fix before merge)

None.

Non-blocking

  • tests/e2e-cucumber/src/diagnostic_wording.rs:168-175 — code_text filters only lines whose trimmed start is //, so a /* … */ comment on its own line survives into the "code" text and can satisfy a code-side template assertion. Verified: reworded the third diagnostic to prior to {describe}. and parked before {describe}. in an own-line block comment — all 7 tests pass. The class that actually bit this PR (/// doc comments) is closed, and Rust convention here is // throughout the driver, so this is a theoretical re-opening rather than a live one; one added || line.starts_with("/*") || line.starts_with('*') in the same filter closes it.
  • tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1411-1440 — assert_no_device_group_remedy guards that matched is an array but not that it is non-empty, and .unwrap_or(0) then makes score == 0 true when the entry is absent for any reason. The named defect can still fail it, so it is not vacuous today, and the scenario is currently declared xfail so a silent pass would surface as an unexpected pass — but once that row is retired the hole goes live. One assert!(!matched.is_empty(), …) restores the symmetry the two neighbouring scenarios already have.
  • tests/e2e-cucumber/tests/e2e/examine_steps.rs:529-570 — the assertions after the field-slot fix are reached for the first time; before this commit the step panicked ahead of them. The pinned strings check out against product code (framework_source = "managed-runtime" and the active managed runtime's interpreter note both exist), but the containment check note.contains(root) assumes the interpreter path renders as a substring of the printed install root — a plausible contract I could not exercise here. It rests on the gated lanes at this head, where the counts show no failures.
  • tests/e2e-cucumber/tests/e2e/tui_driver.rs:479-481 — the doc paragraph reflowed badly when the two-fragment sentence was inserted; clause. So it must be a clause that sits on a short orphan line mid-paragraph. Cosmetic only, and the guard reads the block whole, so nothing depends on it.

Carried over unchanged, not re-raised as a finding: 20 of the 63 commits still carry a Signed-off-by address that differs from that commit's author address. The author has said he would rather a maintainer weigh in than rewrite that history unasked, which is a reasonable place to leave it.

Our previous objection

We wrote:

tests/e2e-cucumber/src/diagnostic_wording.rs:132-135 with tests/e2e-cucumber/tests/e2e/tui_driver.rs:477 — the third clause diagnostic's code-side pin is now satisfied by the doc comment it is supposed to be checking against, so rewording the emitted message passes green.

and named the property to preserve:

for every entry in clause_diagnostics() and marker_diagnostics(), rewording that template in the driver's code must fail a test, whatever the doc happens to say.

No longer holds. Mechanism, not commit message: assert_both_sides now binds let code = code_text(source) and asserts code.contains(template) instead of source.contains(template), where code_text drops every //-prefixed line — which is every doc line in the file. Re-running our exact mutation at this head, changing tui_driver.rs:521 from "process exited ({status:?}) before {describe}.\n{}" to "… prior to {describe}.\n{}": RED, every_clause_diagnostic_is_both_emitted_and_documented fails.

The author took the general fix over reverting the period, which is the right call: I reworded each of the six pinned templates in the code one at a time and each reddens on its own — the two panicked while waiting … templates, the timeout template, the before {describe}. template, both halves of the drain message (outer draining the final frame{context} and inner , waiting until {wanted}), and the marker exit template. So the property we named is now satisfied for every entry, not just the one we caught.

Which side is load-bearing: the code side. The driver's emitted templates are what must be preserved; the doc rendering is the describing side and must change to follow the code, never the reverse. The new Documented doc at :109-114 now says this in terms, and names the period incident as the case that proved it — which is the cheap comment we asked for, and it is the reason this will not recur silently.

Reviewer-error classification: none this round. The earlier round's miss (reading source.contains as a code-side pin without asking whether the same file's prose could satisfy it) is now prevented structurally rather than by the reader noticing, so the confusion cannot recur in the same shape.

Pinning

Yes — the new work still pins everything the previous version pinned, and adds to it. Evidence, all by mutation in a scratch copy:

  • Test set: all six tests present at the previously-reviewed revision are present at this head under the same names — every_clause_diagnostic_is_both_emitted_and_documented, every_marker_diagnostic_is_both_emitted_and_documented, the two the_documented_count_of_*_diagnostics_is_the_real_one, the_clause_label_is_never_debug_formatted, the_documented_example_marker_is_the_one_its_caller_passes. One is added: the_drain_is_still_reached_with_the_callers_clause. Assertion count in the file went 14 → 16. Nothing was dropped, weakened, or relabelled.
  • Doc-side pins, all still red: deleting the first rendering from the owner's doc — RED; changing four diagnostics to five diagnostics — RED; lifting the whole enumeration paragraph off wait_for_screen_where's doc onto another function's doc in the same file — RED (2 tests, so the owner argument is still load-bearing); introducing {describe:?} into a diagnostic — RED (3 tests); adding a third marker-bearing diagnostic — RED.
  • Code-side pins, all now red (this is the net gain): seven template rewordings, one per fragment, each reddening on its own — listed in the section above.
  • New pins this round:
    • the_drain_is_still_reached_with_the_callers_clause closes the first non-blocking from the last round. Mutating the call site drain_final_frame_where(Some(describe), …) → (None, …) — RED. This is a literal-substring pin, so a future rustfmt rewrap would false-red rather than false-green; acceptable.
    • The four expectation.rs branches are now genuinely covered. Seven mutations, each reddening exactly one test and no other: neutering the is_wsl comparison in Condition::matches — RED; removing deny_unknown_fields from Condition — RED; removing it from XfailEntry — RED; dropping the requires_gpu && conjunct from the engine-availability gate — RED; and three inside ResolvedScenario::new (dropping the bug, dropping the flaky flag, dropping the skip's reason) — RED each. The author's claim of seven one-per-branch mutations is reproduced exactly.
  • Vacated: nothing. The only assertion changed rather than added is source.contains → code.contains, which strictly narrows the text an assertion may be satisfied by, so it can only make a pin stronger.

The one thing that does not pin is the residual /* … */ hole in code_text, reported non-blocking above — it is a narrower re-opening of the closed defect, not a vacated pin.

Outside the diagnostic-wording work, both file-scope sweeps came back with no blocking findings: the required-check date guard in the xtask contract test correctly flattens wrapped prose and fails on drift, deletion or a duplicate occurrence; the feature-scan tests fail against a revert of the scan's own predicate; and every world.<field> read in a Then across the step files pairs with a When that writes it on the path its feature file takes — the cli_stderr/cli_other_output mismatch class does not recur anywhere else.

One sweep finding was refuted on check and is not reported: a claim that this diff loosened a path-prefix match to a substring match in the examine steps. The diff contains no such change, and the cited lines are past the end of the file.

Sign-off

All 63 commits in the range carry a Signed-off-by trailer, read from the raw commit objects — including all 10 merge commits, which is the shape that usually gets missed here. None missing.

The AI Co-Authored-By trailer flagged last round is gone: scanning every commit object in the range for that trailer returns nothing. The author's account of rebuilding the two commits that carried it, with trees preserved, is consistent with what the range now contains.

Replies

  • "Blocking finding is correct; took the general fix rather than reverting the period." Correct, and the general fix is the better one — reproduced independently: the blocking mutation reddens, and so does each of the six templates mutated one at a time. His diagnosis of how the defect arrived (adding the period to make the doc match the entry, thereby letting the doc satisfy the code-side assertion) matches what the code shows.
  • "Also closed the first non-blocking — the drain call is now pinned; the mutation reddens." Correct. Reproduced.
  • "The expectation.rs branches — all four now covered … seven mutations, each reddens its own test and no other." Correct. Reproduced all seven; each reddened exactly one of the 139.
  • "The AI co-author trailer … removed from both commits that carried it … nothing in this range carries it now." Correct. Verified against the raw commit objects across the whole range.
  • "The \n{} scoping boundary … agreed it is a real hole and I am leaving it." Fair, and his reasoning holds: counting file-wide would pull in the signature line and the clause wait_for_screen builds for its caller, neither of which is a diagnostic. A stated boundary is the right outcome here. Note that the same reasoning does not cover the block-comment hole above, which has a clean one-line fix.
  • "On the sign-off mismatch: unchanged, and still wanting a history rewrite over the older 20." Correct as a statement of fact — I re-derived it: 20 of 63 commits have a sign-off address differing from the author address. Leaving it for a maintainer to weigh is reasonable.
  • On the GPU-lane failure at the examine step — his account checks out mechanically: the When writes the human form to cli_other_output, the sibling Then already read that slot, and this one did not, so it found None and panicked before asserting. The fix reads the slot its own When writes. The strings the now-reachable assertions pin exist in product code. What I could not verify here is the runtime-data claim that the interpreter path renders inside the printed install root; that rests on the gated lanes, and the counts at this head show no failures.

…n cleared

Three non-blocking notes from the last review.

`code_text` dropped only `//` lines, so an own-line `/* ... */` comment could
still vouch for a template the way the doc comment did before. Reproduced:
reworded the third diagnostic and parked the old wording in a block comment,
all seven tests green. Block-comment lines (`/*` and a leading `*`) are now
prose too; the same mutation reddens exactly the code-side test.

The diagnose-23 step read a missing device-permission entry as score 0. That
is the outcome it asks for, but a run that failed, or routed out of scope
before any device check ran, also produces no entry. The step now requires
exit 0 and no out-of-scope verdict before an absent entry counts, and an entry
present without a readable score panics instead of reading as zero. Not
`!matched.is_empty()`, as suggested: the scenario runs diagnose with no
symptom on a machine whose device works, where matching nothing at all is a
correct answer and asserting otherwise would fail the fix it waits for.

The wait_for_screen_where doc paragraph is reflowed; the orphaned short line
came from inserting the two-fragment sentence.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
Three conflicts, and one guard that main's new code tripped.

diagnose.feature: main added its own diagnose-20 (the sudo + re-login fix
preview) in the slot this branch's four scenarios had taken. Main's keeps
diagnose-20 and this branch's shift to 21-24, including the one cross-reference
between them ("distinct from scenario diagnose-21"). Expectation rows key on
@id, not on the number, so nothing else moves.

engines_steps.rs: main widened the prompt-line predicate to accept `#` as well
as `$`, because the WSL2 hardware lane runs as root; this branch had replaced
the wait-then-read with one condition through wait_for_screen_where. Kept the
single condition, and carried main's `#` into its predicate -- otherwise the
merge would have quietly reverted main's root-prompt fix.

e2e-selfhosted.yml: both sides rewrote the WSL2 lane's comment. Main's version
describes the lane's move to the dispatch pool and no longer carries the
"non-blocking while GPU-on-WSL is proven out" sentence this branch had
corrected, so main's text is taken as is.

diagnostic_wording.rs: main added wait_until_gone, which quotes its own bare
marker in three more messages. The marker count guard counted `{marker:?}`
across the whole driver, so it went red at 5 against the doc's stated 2 -- the
count was measuring every function whose parameter happens to be called
`marker`, not the one whose doc states the number. It now counts inside
terminal_state_after_wait's own body. Re-verified: a third marker message added
inside that function still reddens, and rewording either template still
reddens.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi

Copy link
Copy Markdown
Collaborator Author

Thanks — all four non-blocking notes taken, plus a merge of main that tripped the wording guard for a real reason.

Block-comment hole in code_text — closed. Reproduced first: reworded the third diagnostic and parked before {describe}. in an own-line /* … */, all seven green. code_text now drops /* and leading-* lines as well as //; the same mutation reddens exactly every_clause_diagnostic_is_both_emitted_and_documented, both as a one-line and a multi-line block comment.

diagnose-24 (was 23) reading an absent entry as score 0 — the step now requires exit 0 and no out-of-scope verdict before an absent fix-4-render-group counts as "not offered", and an entry present without a readable score panics rather than reading as zero. I did not use assert!(!matched.is_empty()) as suggested: that scenario runs diagnose with no symptom on a machine whose device already works, where matching nothing at all is a correct answer — asserting non-empty would fail the very fix it is waiting for. The exit-code and out-of-scope checks rule out the two ways an entry can be absent for the wrong reason.

examine-15's containment check on real data — resolved by the lanes at e8626d38: MI300X, MI350P, Strix Halo Ubuntu and Strix Halo Windows each ran Then the framework report names the runtime's interpreter green.

Doc reflow — done.

Merge of main. Three conflicts:

  • diagnose.feature: main took diagnose-20 for its own sudo + re-login preview scenario. Main's keeps 20, this branch's four move to 21–24, including the one cross-reference between them. Rows key on @id, so nothing else moves.
  • engines_steps.rs: main widened the prompt predicate to accept # (the WSL2 lane runs as root); this branch had folded the wait-then-read into one wait_for_screen_where condition. Kept the single condition and carried main's # into it, so the merge does not quietly revert the root-prompt fix.
  • e2e-selfhosted.yml: both sides rewrote the WSL2 lane comment; main's version no longer carries the sentence this branch had corrected, so it is taken as is.

And one guard went red after the merge, correctly: main added wait_until_gone, which quotes its own bare marker in three more messages, so the marker count read 5 against the documented 2. The count was measuring every function whose parameter happens to be named marker. It now counts inside terminal_state_after_wait's own body; a third marker message added there still reddens, and so does rewording either template.

Gate: prek run --all-files --no-group local-tools exit 0, and on a native-Linux host (not a local VM) cargo clippy --locked --workspace --all-targets -- -D warnings and -p e2e-cucumber --test e2e, the workspace tests, e2e-cucumber --lib + feature_naming, and the full cargo xtask e2e mock selection — all green: 0 XPASS, 0 stale, 0 unexpected failures.

Sign-off mismatch on the older commits: unchanged, still left for a maintainer.

@siloteemu

Copy link
Copy Markdown

pr-review-watcher · 22c7744 — new round, full findings above.

No blocking findings this round, across two independent passes: the whole branch, and the latest commits in depth.

The three things most worth confirming all hold. The block-comment fix is load-bearing and precisely aimed — a reworded template parked in a block comment is green under the old filter and red under the new one. The merge did not quietly revert the base's root-prompt fix; the resolution carries it across while keeping this branch's single-condition form. And the marker-count guard cannot go vacuously green: a third message in that body reddens it, as does rewording either template, and neither an unbalanced brace nor a planted comment defeats it.

The refusal to assert the matched list is non-empty is right and should stand — that scenario passes no symptom, and a healthy machine legitimately produces a non-empty list.

Six notes, none of them gating. The merge decision is a human's.

Two conflicts, and one API change the branch's new tests had to follow.

crates/e2e-report: main split lib.rs into modules (EAI-8032), and the two
sentences this branch reworded -- the self-hosted lanes are non-blocking
because their check names are not in branch protection's required list, not
because the hardware is being proven out -- moved with the code into
consolidated.rs. lib.rs is taken from main as is, and both rewordings are
carried into consolidated.rs, so the merge does not quietly restore the
stale reason.

examine.feature: main added its own examine-16 (the setup runtime folder in
--json) in the slot this branch's two help scenarios had taken. Main's keeps
16 and this branch's move to 17 and 18. Rows key on @id, so nothing else
moves; the one `examine-16` mention left in examine_steps.rs is main's own and
refers to main's scenario.

expectation.rs: main replaced resolve()'s three trailing booleans with an
`Included` struct. The four resolve() calls in this branch's new branch-coverage
tests pass `Included::default()`, which is what the three `false`s meant.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the delta since the last clean round (siloteemu's two-pass review at 22c7744c, no blocking findings): the only new content is the merge commit at bd8a44c3, which resolves three conflicts with main (the e2e-report lib.rs/consolidated.rs split, examine.feature renumbering, and expectation.rs's Included struct migration). I checked each claim directly against the merged source rather than taking the commit message on trust:

  • Both reworded non-blocking-reason sentences ("not in branch protection's required list", not the stale "hardware being proven out" phrasing) are present in consolidated.rs.
  • examine.feature: main's examine-16 (setup runtime folder) is intact, and this branch's two scenarios correctly renumbered to examine-17/examine-18 with no collision.
  • Every resolve() call site in the new branch-coverage tests correctly passes Included::default() or an explicit Included { .. } literal matching the old three-bool calling convention.

All three check out. Nothing else in the diff changed since the previous clean pass, and this PR has already been through extremely thorough iterative review (rominf, siloteemu, and my own earlier pass) with every substantive finding addressed.

One process note, not a content finding: GitHub currently reports this branch as CONFLICTING" with mainagain (drift since thebd8a44cmerge), and the review-decision still shows rominf'sCHANGES_REQUESTEDfrom94a8ea2outstanding (addressed per youra9a08ce`/later comments, but never re-approved). Neither is a content problem, but the branch will need another sync before CI can run clean and it can merge.

Approving on content; the merge-conflict resolution and the outstanding review thread are left for you and rominf to close out.

@rominf

rominf commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

🔴 Automated review · pr-review-watcher · bd8a44c

This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.

Summary

The PR adds 12 Gherkin e2e scenarios for contracts a reader of the README expects, with matching expectations.toml rows and guard tests. It also carries a TUI-driver wait refactor with a new prose guard (diagnostic_wording.rs), and rewords why the self-hosted lanes are non-blocking, with an xtask date test. Needs work. Reviewed: the whole change, git diff prw-base...HEAD (24 files, +2901/−135, 61 first-parent commits), by five parallel reviewers split by coupling, plus a synthesis pass and a trial merge with prw-base. The PR description was not available to me, so I took intent from the commit messages. Verified: at HEAD, cargo test -p e2e-cucumber --test feature_naming --lib passes (7 + 151) and the xtask workflow_contract tests pass. On a trial merge with prw-base in a scratch worktree, feature_naming fails 2 tests: scenario_names_are_indexed_sequentially_per_feature and scenario_indexes_are_unique_across_the_suite. A mutation test showed the new date test catches a one-sided re-date. The e2e suite itself was not run, and the head has no CI runs at all because it conflicts with the base. Blocking: 3 · Non-blocking: 9.

🚫 Blocking (must fix before merge)

  1. tests/e2e-cucumber/tests/e2e/tui_driver.rs:631 (wait_for_screen_where). When the child exits, the new drain runs the caller's is_ready against the frame the dead process left behind, and returns Ok(()) if it matches.
    • Why it is wrong: for an absence predicate, a crash now counts as success. Main's version of this function (feat(services): count the local server records the default list hides (EAI-8075) #409, on prw-base) explicitly refuses to do this, for this reason: "an absence, or a cleared cell, is satisfied by accident by the near-empty frame a dead process leaves behind, so a predicate checked first would report a crash as success".
    • Who is affected after merge: main's wait_until_gone now delegates to this function, and its doc says "A child that exits first is a failure rather than a success". So do gen_tps_no_longer_displayed (!screen.contains("tok/s")), the scripted TTFT-cell wait, and configure_shows_chosen_folder in dash_steps.rs. For all of them, a dashboard that dies would turn a failing assertion into a pass.
    • The conflict does not cover it: in the trial merge the code auto-merged without a conflict and keeps the drain. Only the doc comment conflicts, so picking a side of the prose does not fix the behaviour. diagnostic_wording.rs stays green over it because it compares text with text, never behaviour.
    • Which side governs: main's. It is a reasoned, shipped design. The PR's own stated motive (a matching frame buffered behind the exit notification) only holds for presence predicates.
    • Fix:
      • Keep the final-frame drain only on the presence path (wait_for_screen), or make it opt-in per caller.
      • Restore exit → Err in wait_for_screen_where.
      • Rewrite the doc comment, and the diagnostic list that diagnostic_wording.rs checks against it, to match.
      • Add a behaviour test: a PTY child that exits, waited on with an absence predicate, must return Err.
  2. tests/e2e-cucumber/features/examine.feature:211 and tests/e2e-cucumber/features/model_serving.feature:268. The PR conflicts with prw-base in examine.feature, model_serving.feature, tests/e2e.rs and tui_driver.rs, so CI has never run on this head.
    • Index collisions: main independently added examine-17 (examine-lists-every-gpu-with-its-address-and-target) and serve-22 (serve-past-attempts-surfaced). This PR's examine-17/-18 and serve-22..-26 collide with them.
    • Effect: on the trial merge, the required feature_naming guards fail with index "examine-17" is used by both ….
    • Fix:
      • Rebase or merge, and renumber one side's scenarios so each feature's indexes are sequential again.
      • Update prose that cites indexes (for example "serve-06/07/08", "Scenario 7").
      • Keep both sets of E2eWorld fields.
      • Resolve tui_driver.rs as described in item 1.
      • Then let CI run, including the mock lane, which per the PR's own history is sensitive to extra scenarios.
  3. tests/e2e-cucumber/features/diagnose.feature:297 — @id:diagnose-and-fix-agree-on-how-to-verify is tagged @requires-os:linux without @requires-bare-metal.
    • Why it matters: WSL2 reports os_family linux, but check_4_render_group is registered &["linux"] and platform_family() returns "wsl". So on the WSL lane the fix-4-render-group cause never appears, and assert_verification_agrees panics with "did not produce the device-permission cause".
    • The xfail hides it: the when = {} EAI-8020 row absorbs that as the expected failure, for a reason unrelated to the bug, and it will never XPASS there once EAI-8020 is fixed.
    • This is a documented project convention: the file's own comment at line 173 says the two tags "are not interchangeable". diagnose-08/13/15/16 pair them, and combining_requires_os_and_requires_bare_metal_narrows_to_native_linux pins the pairing.
    • Fix: add @requires-bare-metal.

Non-blocking

  • .github/workflows/e2e-selfhosted.yml:32 says crates/e2e-report "point[s] at these rather than asserting it". Yet crates/e2e-report/src/consolidated.rs:948 and :1590 restate the non-blocking reason in their own words. The header states the single-source intent, so it governs: trim consolidated.rs to a pointer, or list it as a carrier of the reason.
  • xtask/src/workflow_contract.rs:448 — confirmed_dates requires exactly one confirmed <date> per file. Any unrelated dated "confirmed" sentence added to either file fails the test spuriously (mutation-verified). Anchor the match to the required-check sentence instead.
  • tests/e2e-cucumber/src/diagnostic_wording.rs:183 — assert_both_sides checks templates against the whole file (code_text) rather than body_of(owner). A template could move to another function and the test would stay green.
  • tests/e2e-cucumber/src/diagnostic_wording.rs — at about 390 lines of source-text scraping, it pins prose but no behaviour, and it stayed green over blocker 1. A small behavioural test of the wait helpers would buy more.
  • tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1516 — offered.map_or(0, …) treats "fix id never emitted" as "checks ran and cleared it". If the fix id is renamed or the checker is dropped from CHECKERS, diagnose-24 passes vacuously, contrary to the comment at 1491. Assert that the checker ran, for example via a sibling cause or the catalog listing.
  • tests/e2e-cucumber/expectations.toml:235,244,280 — "MEASURE" is used as if it were a row convention, but it is defined nowhere (header, README). Define it or drop it.
  • Scope (AGENTS.md §11): the CI/docs non-blocking-reason rewording, the xtask date test, and the tui_driver/diagnostic_wording refactor are independent of the README-contract scenarios. Split them out, or describe each in the PR body.
  • I could not check the PR description. The founding commit message (35c0942: "thirteen places… registers it as an expected failure", EAI-8010/8017/8018 pinned) no longer matches the diff: 12 scenarios, five shipping as guards because the bugs were fixed on main, and none for EAI-8010/8017/8018. Make sure the PR body and squash message describe what actually ships.
  • crates/rocm-core/src/diagnose.rs check_4_render_group (existing code; while you're here): commands uses the device's real group, but verify is hard-coded to grep -E '^(render|video)$'. Diagnose's own command and verify step therefore disagree for a non-render group, and no new scenario covers that, including the EAI-8020 pair.

rominf
rominf previously requested changes Oct 2, 2026

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · bd8a44c

Requesting changes for the three blocking findings in this round's report comment (full details and fixes there):

  1. tests/e2e-cucumber/tests/e2e/tui_driver.rs wait_for_screen_where: after the child exits, the final-frame drain can satisfy an absence predicate from the frame a dead process leaves behind, so a crashed dashboard passes wait_until_gone-style assertions. Resolved by restoring exit → Err for that path (drain only for presence waits) plus a behaviour test.
  2. Conflicts with main: the PR's examine-17/-18 and serve-22..-26 collide with main's new examine-17 and serve-22, so the required feature_naming guards fail on merge, and the head has no CI runs. Resolved by merging/rebasing, renumbering, and getting CI green.
  3. tests/e2e-cucumber/features/diagnose.feature @id:diagnose-and-fix-agree-on-how-to-verify needs @requires-bare-metal alongside @requires-os:linux; on WSL the xfail row currently absorbs a failure unrelated to the bug it pins.

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · bd8a44c

This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.

Round note: every finding below is new this round. No blocking finding from this automation's previous round (e8626d38) was standing. This change request carries the complete set of objections that hold now.

Review — needs work

Full review of the whole change.
The change adds end-to-end coverage for the contracts behind EAI-8024 and the tickets it lists. None of those tickets can be retrieved from here, so the relationship is taken from the description only.

Blocking

  • (new) The head does not merge into the base, and no CI ran for it — .github/workflows/e2e-selfhosted.yml, tests/e2e-cucumber/features/{diagnose,examine,model_serving}.feature, tests/e2e-cucumber/tests/e2e.rs, tests/e2e-cucumber/tests/e2e/{diagnose_steps,tui_driver}.rs
    • git merge-tree prw-base HEAD reports content conflicts in these seven files. GitHub reports the PR as CONFLICTING.
    • The only status on bd8a44c3 is the docs preview build. No ci.yml job, no e2e-selfhosted.yml lane and no E2E job ran on this commit, so nothing has executed the scenarios this change adds.
    • The scenario-numbered files are among the conflicts, and the merge will change them. The diff reviewed here is not what would land.
    • Confidence 100 · mechanical · Fix: merge or rebase onto the current base, resolve the conflicts (including scenario numbering), and let CI run on the result.
  • (new) The description misstates what the change contains — PR description; tests/e2e-cucumber/expectations.toml; tests/e2e-cucumber/tests/e2e.rs:1488-1500
    • It claims work the diff does not contain:
      • an EAI-8010 update --dry-run row. No EAI-8010 row or scenario exists, and update_steps.rs only gains a doc comment.
      • "Two rows added for EAI-7960". No EAI-7960 row exists.
      • "Suite concurrency … clamped to 2..=6, overridable with E2E_MAX_CONCURRENT". The code keeps 64, and its comment says that bound "was tried on this branch and reverted". Nothing reads E2E_MAX_CONCURRENT.
      • EAI-8006 shipping "with no expected-failure row". expectations.toml has an EAI-8006 row scoped to os = "windows", and the scenario comment says "Expected to FAIL on Windows".
      • "CI is green on the current head across all three workflows". See the finding above.
    • It also omits work the diff does contain, and calls the harness additions "confined to the test crate" when they are not:
      • the tui_driver.rs wait/drain refactor;
      • the new 391-line src/diagnostic_wording.rs;
      • a new xtask contract test (the_required_check_observation_is_dated_the_same_in_both_places);
      • comment rewrites in ci.yml and e2e-selfhosted.yml;
      • the docs/ci-hardware-testing.md and README changes;
      • changed report text in crates/e2e-report/src/consolidated.rs.
    • Confidence 100 · logic · Fix: rewrite the description to match the diff. Narrowing the description is the right remedy here, not adding work.
  • (new) diagnose-21 records a WSL precondition failure as the known bug — tests/e2e-cucumber/features/diagnose.feature:297, tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1108,1269-1274, tests/e2e-cucumber/expectations.toml (row diagnose-and-fix-agree-on-how-to-verify)
    • The scenario is tagged only @requires-os:linux, and WSL2 reports os family linux. But check_4_render_group is registered for &["linux"] only (crates/rocm-core/src/diagnose.rs:2013), and platform_family returns "wsl" first (:2062-2064). So fix-4-render-group is never produced on WSL.
    • The Then step panics with "the symptom did not produce the device-permission cause". The when = {} row then records that panic as EAI-8020 on the WSL lane.
    • Two comments are false for the same reason: "makes the device-permission cause score on any host" (diagnose_steps.rs:1108) and "holds wherever the catalog runs at all" (the toml row).
    • Once EAI-8020 is fixed and the row is deleted, the WSL lane goes red.
    • The sibling scenarios diagnose-22 to 24 already carry @requires-bare-metal, and expectation.rs documents that tag as the way to exclude WSL.
    • Confidence 85 · logic · Fix: add @requires-bare-metal to diagnose-21 and correct the two comments.
  • (new) A process that has exited now satisfies negative screen waits — tests/e2e-cucumber/tests/e2e/tui_driver.rs:616-636; affects tests/e2e-cucumber/tests/e2e/dash_steps.rs:1383 (gen_tps_no_longer_displayed, |screen| !screen.contains("tok/s"))
    • On the base, wait_for_screen_where checked try_wait() before is_ready and returned "process exited" as an error.
    • Now is_ready runs first on every iteration, and the exit path drains the final frame and accepts any frame that satisfies the predicate.
    • When the dashboard exits it leaves the alternate screen (crates/rocm-dash-tui/src/app/mod.rs:2023). The primary screen has no "tok/s", so the negative predicate is satisfied by a dead process.
    • A crash then fails a later step with a misleading message. A clean exit with code 0 would let "the dashboard exits successfully" pass as well.
    • The drain was written for positive markers: its doc says "the frame that satisfies is_ready is often in it". It now applies to every caller.
    • Confidence 85 · logic · Fix: restore exit-before-predicate for wait_for_screen_where, and keep the post-exit drain for callers whose predicate is a positive marker (wait_for_screen).
  • (new) Two new guards cannot fail on the branch they exist to check — tests/e2e-cucumber/src/expectation.rs:2241-2249 (the_report_row_carries_each_expectations_own_metadata), tests/e2e-cucumber/src/expectation.rs:1804-1806 (every_expectation_row_names_a_scenario_that_exists)
    • Mutation-checked, one change at a time:
      • Making Expectation::label() return "pass" for ExpectXfail leaves the first test green. It compares xfail.expected with ….label(), which is label() compared with itself. The skip and pass rows never assert expected at all.
      • Replacing if !line.starts_with('@') with if false leaves the second test green. Its comment calls ignoring commented-out @id: tags "the one thing this test exists to catch".
    • Verdicts:
      • (1) Reverting the production change would make each test fail to compile, or remove it.
      • (2) The label mapping and the tag-line filter can each be broken alone and stay green.
    • Confidence 100 · mechanical · Fix: assert the literals "xfail", "skip" and "pass". Move the tag scan into a function and give it a fixture containing a commented @id:, the same way claimed_failure_ids is tested.

Non-blocking

  • (new) Two new xfail scenarios cannot tell their bug apart from an unrelated failure — tests/e2e-cucumber/tests/e2e/chat_steps.rs:94-103, tests/e2e-cucumber/tests/e2e/serving_steps.rs:1224-1235
    • The harness classifies an xfail scenario from one pass/fail bool and never looks at which step failed (crates/e2e-report/src/parse.rs:263-275, tests/e2e.rs:1652-1670). That is pre-existing behaviour.
    • chat-10's When ignores the exit code. Its Then only looks for "mock response". It never checks the premise its comment states ("services list shows a ready local server"). So a bad flag, a registration fault or a crash all record as EAI-8009.
    • serve-25 likewise records any serve failure that happens before the plan line as EAI-8006.
    • Confidence 70 · logic · Fix: assert the premise and the specific refusal wording the bug produces.
  • (new) Comments describe the branch's review history rather than the code
    • Examples:
      • expectation.rs "that was the round-9 false positive";
      • "rows were deleted while this file was being written";
      • e2e.rs:1488-1493 "tried on this branch and reverted … failed here at 2..=6";
      • e2e.rs "Readability only — NOT a bug fix";
      • diagnostic_wording.rs "several consecutive review rounds";
      • README "citations written during this suite's review … one by five hundred lines".
    • These go stale on merge.
    • Confidence 70 · mechanical · Fix: state the standing rule each one supports.
  • (new) Comments and docs that are wrong now
    • chat.feature:111 says "Scenario 7 names the model". The scenario that passes --model is chat-08; chat-07 is the HTTP chat.
    • diagnostic_wording.rs:48 says "send_until's only caller". It has three callers (dash_steps.rs:184,194,322).
    • diagnose.feature:3-6 says "every scenario here runs on the mock lane". diagnose-22 to 24 are @requires-gpu.
    • README.md:109 says "Three tags tier the suite at runtime". @requires-docker is gated by E2E_INCLUDE_DOCKER the same way (tests/e2e.rs:1434) and is not listed.
    • Confidence 85 · mechanical · Fix: correct each one.
  • (new) examine-16 still uses the stderr slot — tests/e2e-cucumber/tests/e2e/examine_steps.rs:595,790
    • This change adds cli_other_output and says borrowing cli_stderr for the human report is the mistake that field exists to avoid. It moves three readers over but leaves this one.
    • Confidence 70 · mechanical · Fix: move it too.
  • (new) A Given step that sets nothing — tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1241-1254
    • user_hit_device_permission_failure has an empty body. The premise lives in a constant that the When passes. The comment explains this, but the step's name still claims a fixture it does not arrange.
    • Confidence 70 · mechanical · Fix: fold the premise into the When's wording, or have the Given record the symptom the When reads.
  • (new) The feature-directory scan is duplicated — tests/e2e-cucumber/src/expectation.rs feature_files_in (plus its three tests), tests/e2e-cucumber/tests/feature_naming.rs feature_files_in
    • Two copies, each with a comment saying they must keep "the same shape".
    • Confidence 70 · architectural · Fix: one pub helper in the lib.
  • (new) confirmed_dates has an untested branch — xtask/src/workflow_contract.rs:448-467
    • The # marker stripping is never exercised. The workflow's date sits mid-line, so removing trim_start_matches('#') stays green. Every other extractor in this file has its own small guard test.
    • Confidence 70 · mechanical · Fix: add a fixture where the date wraps onto the next # comment line.

Decisions for the author

  • Size and scope of the change — non-blocking-improvement
    • 24 files and more than 60 commits. Alongside the README-contract scenarios it carries a TUI-driver refactor, a source-text guard module, an xtask contract test, CI comment rewrites and report wording.
    • Splitting the harness and CI parts out would make each reviewable on its own, and would make it easier to keep the description accurate.
  • diagnostic_wording.rs scans source text — tradeoff
    • It keeps doc comments and format! templates in step, which stops prose from drifting.
    • The cost is about 390 lines of source matching that a rustfmt re-wrap can break, and by its own account it cannot detect a template that is never reached.
    • The alternative is to state fewer messages in doc comments.
  • The required-check date lives in two files, plus a test that keeps them equal — tradeoff
    • Two carriers plus a sync test, against one carrier (the doc) with the workflow header pointing to it, which would need no test.
  • GPU serve steps clear the default port — tradeoff
    • The new serve-25 and serve-26 paths call ensure_serve_port_free, which force-kills whatever is listening on the default serve port.
    • On a developer's GPU machine that includes their own rocm serve. That is acceptable on CI lanes; it is worth a line in docs/testing.md.

Positive signals

  • serve-24 confirms the service is listed in its Given and checks OS process state before teardown runs, so "the server is no longer running" cannot be satisfied by teardown cleaning up after the product.
  • labelled() returns None for an empty value, so two empty "verify" lines cannot compare equal and pass diagnose-21.
  • The stat/id shims match on both the format argument and the device path. diagnose-24 also confirms its substituted premise through examine --json before relying on it.
  • uninstall in serve-24 can only reach scenario-owned directories: isolate_env sets every ROCM_CLI_*_DIR override, and --keep-cache protects the shared CI cache.

Deployment notes

None

What this covered

Every file in the change was read, split across four coupled worker groups (expectations/harness, diagnose/examine, serving/TUI, CI/xtask/docs), plus each group's callers and the product code it pins. Two gaps:

  • The expectations worker read tests/e2e.rs lines 1-212 only as diff hunks.
  • It read consolidated.rs as diff hunks plus a grep; that file's changes are two prose strings.

Range read: 9d261bd8 (merge-base with prw-base 2d45c617) … bd8a44c3.

Commands run:

  • cargo test -p e2e-cucumber --lib --test feature_naming, -p xtask and -p e2e-report: all passed.
  • cargo clippy -p e2e-cucumber -p xtask -p e2e-report --all-targets -D warnings: clean.
  • Two mutation checks (the guards finding).
  • A git merge-tree against prw-base.

Did not run:

  • the cucumber scenarios themselves (no GPU here, and the mock-lane harness was not run);
  • workspace-wide tests and clippy;
  • the prior-changes pass (review comments on earlier PRs touching these files were not fetched).

The history, agent-instruction (including the leak scan, which was clean) and merge-hygiene passes ran. The tickets named (EAI-8024 and the others) cannot be retrieved from here, so their content was not checked. No prompt-injection content was found. This report has not been reconciled against the PR's discussion.

Conflicts: e2e-selfhosted.yml (main dropped the rad3/mi350p lanes and added w7900, so the header rewording is reapplied on main's file with w7900 as the exception lane), the diagnose/examine/model_serving features (this branch's scenarios renumbered past main's), and the Rust steps (both sides kept).

Two fixes the merge needed: the ASCII-output source walk now skips any target* directory, not only target, so the container gate's target-container is not scanned; and the clause-diagnostic count in diagnostic_wording.rs is scoped to the functions that build those messages, since main added wait_for_popup_body_where with its own copies.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi
fredespi requested a review from rominf October 27, 2026 09:26
Main took diagnose-41 (Windows display driver) and examine-29 in the slots
this branch's scenarios held. Main's keep their numbers; this branch's move to
diagnose-42..45 and examine-30..31, and the one in-feature cross-reference
("distinct from scenario diagnose-42") follows. Rows key on @id, so nothing
else moves. The remaining diagnose-41 / examine-29 mentions in the step files
and docs are main's own and refer to main's scenarios.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
rominf
rominf previously requested changes Oct 27, 2026

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · dd5fce0

This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.

Round note (dd5fce0, after the rebase): this change request replaces the automation's earlier one at bd8a44c and carries the complete set. Of that round's three blockers, two were re-found blind this round and are marked standing below (the absence-wait drain in tui_driver.rs; diagnose-42's missing @requires-bare-metal). The third — scenario-index collisions with main — is cleared: this round's review did not re-find it, and putting it to the reviewer afterwards confirmed the head merges cleanly with the current base and that feature_naming (7) and the crate's lib tests (190, incl. scenario_indexes_are_unique_across_the_suite and every_expectation_row_names_a_scenario_that_exists) pass on that merge result. Everything else is new. The gate rests on the three logic findings (examine-29, the absence-wait drain, diagnose-42); the bare-path citations, the Lint (PowerShell) failure (a PowerShell Gallery install error at the module-install step; the job passes on main — a re-run should clear it) and the description mismatch are reported but would not block on their own. The "E2E self-hosted" workflow is still pending with no jobs started at this head, so no GPU/Windows/WSL lane has measured the new scenarios — missing evidence, not a finding. The hand-written change requests on this PR are not this automation's and are untouched.

Review — needs work

Full review of the whole change.
Implements part of EAI-8024 (the tracker now files it as ROCMAI-108). Eight scenarios get expected-failure rows, five ship as guards, and EAI-8017/8018 are deliberately left uncovered. The EAI-8010 coverage the description lists is not in this change; it is main's existing update-04.

Blocking

  • (new) examine-29 now fails on the only lane it runs on: its When writes a slot its Thens no longer read — tests/e2e-cucumber/tests/e2e/examine_steps.rs (user_inspects_both_ways, assert_examine_reports_driver_version_and_date, assert_examine_driver_age_follows_the_date, user_inspects_for_scripting_first)

    • user_inspects_both_ways used to store the human examine report in cli_stderr. It now stores it in cli_other_output.
    • Commit 97615d6 moved examine-15's reader to the new slot. The two driver Thens used by examine-29 (@requires-os:windows @requires-gpu) were not moved. They still do world.cli_stderr.as_ref().expect("no readable inspection"), so on the Windows GPU lane they panic before asserting anything.
    • At the merge-base c586618 the When wrote cli_stderr and those Thens read it, so this change is what breaks it. No expectations.toml row covers it.
    • The same half-migration remains in user_inspects_for_scripting_first, which still stores the human report via world.cli_stderr = Some(human). That contradicts the new cli_other_output doc in e2e.rs.
    • Confidence 85 · logic · Fix: point both driver Thens, plus user_inspects_for_scripting_first and its reader, at cli_other_output.
  • (standing since bd8a44c, re-found blind) A crashed dashboard now satisfies every absence wait — tests/e2e-cucumber/tests/e2e/tui_driver.rs (wait_for_screen_where, wait_until_gone), tests/e2e-cucumber/src/diagnostic_wording.rs (the_drain_is_still_reached_with_the_callers_clause)

    • When the child has exited, wait_for_screen_where now calls drain_final_frame_where(Some(describe), &mut is_ready) and returns Ok(()) if the predicate holds on the dead process's last frame. prw-base has no drain here.
    • wait_until_gone passes |screen| !screen.contains(marker), which any final frame without the marker satisfies. A dashboard that crashed or quit therefore passes every wait_until_gone and cleared-cell wait. That includes the steps whose whole point is that a key did not quit, such as the decline-quit-prompt step in dash_steps.rs.
    • The doc comments still state the opposite. wait_until_gone says "A child that exits first is a failure rather than a success". wait_for_screen_where says "Here that recovery is given up on purpose" and that liveness is checked "before the predicate".
    • The new guard in diagnostic_wording.rs asserts that this exact drain call is present, so it holds the regression in place. AGENTS.md §3 warns about this: "a pin over a false claim holds the claim in place."
    • Traced by reading; not reproduced under a pty.
    • Confidence 85 · logic · Fix: drain only on the positive path (wait_for_screen, or a flag only that path sets), then realign the docs and the guard.
  • (standing since bd8a44c, re-found blind) diagnose-42 runs on WSL2, where the check it compares is never produced, and the xfail row hides that — tests/e2e-cucumber/features/diagnose.feature (@id:diagnose-and-fix-agree-on-how-to-verify), tests/e2e-cucumber/expectations.toml (its when = {} row)

    • The scenario's only gate is @requires-os:linux. WSL2 reports os_family linux, so it resolves to run on e2e-wsl.
    • In crates/rocm-core/src/diagnose.rs, check_4_render_group is registered for &["linux"] only, and platform_family returns "wsl" there. fix-4-render-group is never emitted, so section_for fails and the step panics. The when = {} row records that as the EAI-8020 bug reproducing.
    • The row's own comment says the bug "holds wherever the catalog runs at all", which is false on WSL2. That lane reports a bug it cannot reach and can never go stale.
    • Confidence 85 · logic · Fix: add @requires-bare-metal, as diagnose-43/44/45 have.
  • (new) New markdown citations are bare paths, against AGENTS.md §5 — tests/e2e-cucumber/README.md (the new tier paragraph, the @merge-queue row, and the cite-a-symbol paragraph)

    • The added text cites .github/workflows/e2e-selfhosted.yml, docs/ci-hardware-testing.md and apps/rocm/src/main.rs in backticks.
    • §5 requires a citation of a repo file "as a link relative to the citing file". The citation is not in any exempt location: not .github/, not root README/CONTRIBUTING, not docs/rocm-docs. This README already uses relative links elsewhere, e.g. [src/bin/](src/bin/).
    • Confidence 85 · mechanical · Fix: make them relative links. The illustrative main.rs:16427 can stay bare.
  • (new) A required check is failing at this head — Lint (PowerShell)

    • The status rollup for dd5fce02 shows it FAILURE, and it is in main's required-status-check list.
    • It failed at the "Install PSScriptAnalyzer" step with a PowerShell Gallery "No match was found" error. This change touches no PowerShell, and the same check passes on main's latest commit, so it looks like infrastructure. It is still red.
    • Confidence 100 · mechanical · Fix: re-run it; escalate if PowerShell Gallery keeps failing.
  • (new) The description misstates what the change contains — PR description

    • It claims work that is not in the diff:
      • an EAI-8010 update --dry-run expected-failure row (no such row or scenario here; main's update-04 already covers it);
      • a section adding two EAI-7960 rows (deleted in d6825bb);
      • a "Suite concurrency" section (available_parallelism / E2E_MAX_CONCURRENT appear nowhere in the diff; reverted in ea4fd91);
      • "CI is green on the current head across all three workflows", which is false: one required check is red and the self-hosted workflow has started no jobs.
    • It contradicts the diff: it says EAI-8006 "ship[s] as [a] guard, with no expected-failure row", but serve-auto-gpu-selection-names-a-device carries an EAI-8006 Windows row and its scenario says "Expected to FAIL on Windows".
    • It omits real changes:
      • the tui_driver.rs wait/drain refactor (the source of the second finding);
      • the new diagnostic_wording.rs guard module;
      • the moved examine report slot;
      • the non-blocking-rationale rewording across ci.yml, e2e-selfhosted.yml, docs/ci-hardware-testing.md and crates/e2e-report;
      • the new xtask date-agreement test;
      • the widened ascii_output_contract directory skip.
    • Confidence 85 · architectural · Fix: rewrite the description to match the diff. Narrowing it is the remedy, not adding the missing work.

Non-blocking

  • (new) A merge left a duplicated fragment in a module doc — tests/e2e-cucumber/tests/e2e/update_steps.rs (module //! block)

    • //! binary (EAI-8072). Mock lane. now follows the update-07/08 paragraph mid-sentence.
    • Confidence 85 · mechanical · Fix: delete the stray line.
  • (new) chat-10's comment points at the wrong scenario — tests/e2e-cucumber/features/chat.feature (chat-10 comment)

    • "Scenario 7 names the model and works", but the CLI one-shot that passes the model is chat-08. chat-07 talks to the endpoint over HTTP.
    • Confidence 85 · mechanical · Fix: say chat-08.
  • (new) One of three sibling "proven out" comments was left behind — .github/workflows/e2e-selfhosted.yml (e2e-gpu-w7900)

    • This change rewrote the strix-ubuntu and strix-windows comments so that non-blocking is "for the reason in this file's header".
    • The w7900 job still says "Non-blocking while this hardware is proven out, as the MI350P lane is". That mixes the continue-on-error reason back into "non-blocking", which the new header calls "not the reason any of them is non-blocking". It also cites an MI350P lane this file does not define.
    • The text was there before this change.
    • Confidence 85 · mechanical · Fix: reword it to the continue-on-error sense only.
  • (new) The guard module's own doc gives a false reason — tests/e2e-cucumber/src/diagnostic_wording.rs (body_of doc)

    • It says wait_until_gone "quotes its own bare marker in messages of its own". wait_until_gone emits no message; its {marker:?} only builds the describe label.
    • Confidence 70 · mechanical · Fix: correct the stated reason.
  • (new) The "harness read failure, not a verdict" panics are scored as the verdict — tests/e2e-cucumber/tests/e2e/diagnose_steps.rs (assert_verification_agrees, assert_remedy_command_agrees)

    • Under when = {} xfail rows, any panic counts as the bug reproducing. A missing section or unparsable groups therefore reads as EAI-8020; the WSL2 finding above is a live case.
    • Confidence 70 · logic · Fix: make precondition failures distinguishable from the measured assertion, or soften the messages.
  • (new) serve-26's help parser is half-exercised — tests/e2e-cucumber/tests/e2e/serving_steps.rs (advertised_device_policies)

    • Only the block Possible values: rendering runs today; the inline [possible values: …] branch is never reached.
    • The block scan stops only at a line starting --, so a following -x, --foo option would not end it. The empty-list guard does catch a total misread.
    • Confidence 70 · logic · Fix: a small unit test with both clap renderings.
  • (new) groups_in_command parses forms neither command prints — tests/e2e-cucumber/tests/e2e/diagnose_steps.rs (groups_in_command)

    • Both diagnose and fix print -a -G x,video. The --groups= and bundled -aG branches are untested.
    • Confidence 70 · logic · Fix: drop them (the empty-result guard already fails loudly on any unrecognised form) or unit-test them.
  • (new) The ASCII-contract skip is wider than its stated reason — xtask/src/ascii_output_contract.rs (directory walk)

    • name.starts_with("target") skips a directory of that prefix at any depth, so a future src/targets/ would silently drop out of the scan.
    • The justification, "target-container/ is the container gate's build dir", matches nothing in the tree.
    • Confidence 70 · mechanical · Fix: name == "target" || name.starts_with("target-"), or apply it at the repo root only, and cite what creates that directory.
  • (new) Two copies of the feature-file scan are kept identical only by comments — tests/e2e-cucumber/src/expectation.rs (feature_files_in, under cfg(test)), tests/e2e-cucumber/tests/feature_naming.rs (feature_files_in)

    • Both comments say the copies must not diverge; nothing enforces it.
    • Confidence 70 · architectural · Fix: expose one pub fn from the lib and call it from both places.

Decisions for the author

  • The real-serve scenarios are telemetry, not gates — tradeoff

    • serve-27, serve-29 and serve-30 are @merge-queue. E2E_MERGE_QUEUE is set only on the self-hosted lanes in merge_group (not e2e-gpu), and none of those lanes is required today. Their pinned contracts can regress without blocking anything.
    • The README states this honestly, which keeps per-PR cost down. Confirm that telemetry-only coverage is the intended standing for these contracts.
  • A lot of guard code now pins prose rather than behaviour — non-blocking-improvement

    • diagnostic_wording.rs (400 lines over a test driver's doc comments), the "Expected to FAIL" prose binding, and the date-agreement xtask test all guard wording.
    • Mutation runs show they do catch drift. But the second blocking finding shows a wording guard pinning a behavioural regression in place.
    • Consider whether these guards, and the CI-rationale rewording, belong in a separate change from the README contracts. That would also bring this PR back to one logical change (AGENTS.md §11).

Positive signals

  • serve-28 checks whether the owned process is still running (OwnedProcess::is_running) before teardown, so the harness cannot clean up after a product failure and turn it green. Its uninstall stays host-safe: --keep-binaries and --keep-cache, under the scenario's own isolated root.
  • compare_diagnosis_with_fix gives the stat/id shims to both diagnose and fix. The comparison will not silently become shimmed-versus-real once fix starts reading the device.
  • given_usable_device establishes its premise through examine --json before relying on it.

Deployment notes

None. Test, CI-comment and xtask-test changes only; no product code changes.

What this covered

Read: every file in git diff prw-base...HEAD, across two coupled-group workers, plus the product code the new steps drive:

  • diagnose.rs / fix.rs (check_4, platform_family, CHECKERS)
  • examine.rs probes
  • the serve, uninstall and chat paths
  • rocmd service stop

The diff runs from merge-base c586618 to head dd5fce02. prw-base a9e9aa2 merges cleanly with the head (git merge-tree).

Run, in the checkout's own target/:

  • cargo test -p e2e-cucumber --lib (190 passed)
  • --test feature_naming (7 passed)
  • cargo test -p e2e-cucumber --test e2e --no-run (built)
  • cargo clippy -p e2e-cucumber --test e2e -- -D warnings (clean)
  • the xtask date and ascii tests (28 passed)
  • cargo test -p e2e-report (66 passed)
  • nine mutation checks, all of which went red: five on diagnostic_wording.rs, two on the expectation.rs scan, one deleted expectations.toml row, one re-dated docs/ci-hardware-testing.md

Sign-off check: 54 commits, none missing a trailer.

Did not run:

  • no e2e scenario was executed;
  • no Windows-target cargo check;
  • the GPU, Windows and WSL lanes did not run at this head, so every GPU-lane xfail row is unmeasured on this commit;
  • prior-changes pass: earlier review comments are deliberately out of reach here;
  • the design questions were investigated in the driving context, not by independent workers (fan-out budget of two).

Ticket: EAI-8024 retrieved by identifier (title, body, status only).

Not reconciled against any PR discussion here.

@fredespi
fredespi dismissed siloteemu’s stale review October 27, 2026 11:42

Requested changes addressed: commits were pushed after this review and all review threads are resolved. Re-review requested.

@fredespi
fredespi requested review from a team and siloteemu October 27, 2026 11:42
Move examine-29 readers onto cli_other_output, stop absence waits being
satisfied by a dead process's last frame, gate diagnose-42 to bare metal,
and fix citations, stray doc lines and comment drift.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
@fredespi
fredespi requested a review from rominf October 27, 2026 12:10
Signed-off-by: fredespi <fredrik.espinoza@gmail.com>

@tomastola tomastola left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick round. Re-read at 7ce66d5b:

  • wait_until_gone: looks right. Draining the last frame on exit now only happens for the positive wait_for_screen form, so an absence wait can no longer be satisfied by a dead process's empty screen. The diagnostic_wording.rs guard follows the move to wait_loop.
  • diagnose-42: looks right. @requires-bare-metal keeps it off WSL2, and the row's comment now says so.

Two findings that still hold at this head:

1. serve-28's row pins documented behaviour as a bug. README.md:1202-1203 says, under what uninstall warns about: "Managed service records exist. uninstall doesn't stop their background processes." serve-28 asserts the opposite, and the serve-uninstall-stops-what-it-manages row in expectations.toml calls the warning "the behaviour being disputed". That contradicts the file's own rule that a row means a known bug, and the section header that each scenario asserts "the behaviour the README leads a reader to expect". Either the product and README change first, with the scenario landing alongside, or this one is framed as a proposed contract outside the README-walkthrough set, with its row comment and the header adjusted.

2. serve-27 never checks that anything stopped. the CLI reports that it stopped a process (tests/e2e-cucumber/tests/e2e/serving_steps.rs:1301-1322) asserts rc == 0 and stopped processes: N with N ≥ 1, and nothing else. A services stop that signals the pid but leaves the server answering would pass, even though the panic text says "the server was running and is now stopped". Suggest also asserting that the endpoint no longer answers or that the recorded pid is gone, the way serve-28 checks OwnedProcess::is_running.

Non-blocking:

  • Orphan-row test. every_expectation_row_names_a_scenario_that_exists (tests/e2e-cucumber/src/expectation.rs:1926) is correct, and a bogus row does turn it red. But its tag-lines-only rule is untested: switching it to scan whole lines stays green today, because every commented @id: in model_serving.feature also exists as a real tag. Extracting the scan into something like scenario_ids_in(text) and giving it a fixture with a commented-out id would pin the property the comment describes.
  • README citations. The header above the README-contract rows says they "were all filed from one pass through README.md". I couldn't find README text stating the contracts behind diagnose-42/44/45 (that diagnose and fix agree on the remedy and its verification, and that a usable device isn't offered as a permission cause) or serve-30 (a second server avoids an address in use). The README's diagnose/fix section only says rocm fix takes the id diagnose reports. Either the header could say these are contracts implied by the walkthrough, or the README could state them.
  • tests/e2e-cucumber/tests/e2e.rs:1544-1551. The concurrency comment narrates this branch's history ("tried on this branch and reverted … failed here at 2..=6"). A reader of main won't have that context. The rationale for 1 vs 64 fits better in the code, and the history fits better in the PR or commit message.

@nowycondro nowycondro left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 7ce66d5

Review — needs work

Full review of the whole change: the diff between merge-base c586618 and head 7ce66d5b across all 25 files (test-only; 12 new Gherkin scenarios plus matching expectations.toml rows, a new diagnostic_wording.rs self-consistency guard, an e2e.rs harness extension, and comment-only CI/docs rewording). This PR has already been through several rounds of review (an automated pass and a human pass by tomastola at this exact head); most previously-reported blocking issues (the absence-wait drain in tui_driver.rs, diagnose-42's missing @requires-bare-metal, bare-path README citations, a stale PowerShell check) are confirmed fixed at this head. Three issues remain open.

Blocking

  • The PR description still misstates what the diff contains — PR description vs. tests/e2e-cucumber/expectations.toml, tests/e2e-cucumber/tests/e2e.rs
    Independently reconfirmed at this head: the description's "Pinned as expected failures" table lists an EAI-8010 row, but no such row exists in expectations.toml (grep finds nothing). The "Two rows added for EAI-7960" section describes dash-gen-tps-held-after-scrape-failure/dash-gen-tps-expiry-boundary rows that are not in the file. The "Suite concurrency" section describes an available_parallelism()/E2E_MAX_CONCURRENT scheme that is not in e2e.rs — the actual comment there (around the max_concurrent block) says this was "tried on this branch and reverted". And "Two ship as guards, with no expected-failure row" names EAI-8006 as row-less, but expectations.toml carries a bug = "EAI-8006" row (os = "windows") for serve-auto-gpu-selection-names-a-device.
    Confidence 85 · architectural · Fix: rewrite the description to match the current diff rather than an earlier draft of it.

  • serve-28 pins the README's own documented behaviour as a bug — tests/e2e-cucumber/features/model_serving.feature:384, tests/e2e-cucumber/expectations.toml:225-232, README.md:1201-1203
    README.md states plainly, under what uninstall warns about: "Managed service records exist. uninstall doesn't stop their background processes." The EAI-8014 row and serve-28 scenario assert the opposite as an expected failure, and the row's own comment calls the warning "the behaviour being disputed" — but the README isn't disputing it, it documents it. This contradicts the PR's own framing (each row pins a gap between what the README promises and what the product does); here the README already promises the behaviour the scenario pins as a bug.
    Confidence 85 · logic · Fix: either land the product/README change together with this scenario, or reframe this as a proposed contract outside the README-walkthrough set (adjust the row's reasoning and the section header's "the behaviour the README leads a reader to expect" claim accordingly).

  • serve-27 never checks that the server actually stopped — tests/e2e-cucumber/tests/e2e/serving_steps.rs:1302 (assert_stop_count_reported)
    The step only asserts rc == 0 and a stopped processes: N line with N >= 1. A services stop that signals the PID but leaves the process (or its listening endpoint) alive would still pass, despite the panic message's own claim ("the server was running and is now stopped"). serve-28's assert_owned_server_stopped shows the available pattern (OwnedProcess::is_running) for actually confirming this.
    Confidence 85 · logic · Fix: additionally assert the registered PID (or endpoint) is gone, not just the reported count.

Non-blocking

  • tests/e2e-cucumber/tests/e2e/serving_steps.rs (advertised_device_policies): only the block Possible values: clap rendering is exercised by any scenario; the inline [possible values: …] branch has no test reaching it.
  • tests/e2e-cucumber/tests/e2e/diagnose_steps.rs (groups_in_command): the --groups= and bundled -aG parsing branches are unexercised by any scenario.
  • tests/e2e-cucumber/src/expectation.rs (every_expectation_row_names_a_scenario_that_exists): correct, but its "tag lines only, not whole-file" scanning rule is itself untested — a regression there (e.g. picking up a commented-out @id:) would currently go unnoticed.

Decisions for the author

  • Near-duplicate directory-scan logic in src/expectation.rs and tests/feature_naming.rs (feature_files_in) — tradeoff. The comments are explicit that this duplication is deliberate (independent guards that must "belong together" rather than share one helper), trading a small amount of duplication for two independently-failing checks. Reasonable as stated; flagging only so it's a confirmed choice rather than an oversight.

Positive signals

  • tests/e2e-cucumber/src/diagnostic_wording.rs is a genuinely useful guard: it pins the TUI driver's wait/drain diagnostics against the doc comments describing them in both directions (code→doc and doc→code), which the file's own history shows has drifted silently before.
  • The new stat/id shims (stat_shim_path, id_shim_path in tests/e2e-cucumber/tests/e2e.rs) are scoped tightly to the one invocation shape the product makes, so they can't quietly swallow unrelated commands.
  • Consistent, careful separation throughout the new assertions between "harness could not measure this" and "the product failed the check" (e.g. assert_verification_agrees, assert_named_groups_exist), which avoids a harness read failure masquerading as a product defect.

Deployment notes

  • Test-only change; no product (crates/, apps/) behaviour is touched.
  • CI is green at this head: gh pr checks 241 shows every required check passing, including E2E tests and E2E consolidated report (both were pending/red in earlier rounds). The self-hosted GPU/Windows/WSL lanes in e2e-selfhosted.yml are non-blocking by design (not in branch protection's required list), so their results remain informational rather than a merge gate — consistent with this PR's own rewording of that rationale.
  • No migration or compatibility concerns; this only adds scenarios, harness helpers, and xtask contract tests.

What this covered

Read the entire diff between c586618 (merge-base) and 7ce66d5b (head) across all 25 changed files in full, including the CI/docs comment-only changes, expectations.toml's new rows, the new diagnostic_wording.rs module, the expectation.rs/feature_naming.rs scan-and-guard tests, mock_server.rs, the e2e.rs harness additions (OwnedProcess, stat_shim_path/id_shim_path, stop_managed_services's env fix), every touched step-definition file, and the tui_driver.rs wait/drain refactor.

Did not run any tests, lint, or builds — this sandbox has no Rust toolchain or network access for cargo; relied on gh pr checks 241 for CI status, which is the live signal reported above.

Reconciled against this PR's existing review history: compared this head against the most recent automated round (against the prior head dd5fce02) and the most recent human round (tomastola, at this exact head). Confirmed by direct inspection — not by trusting either report — that the earlier round's blocking findings are fixed here, and independently verified tomastola's two still-open findings (serve-28, serve-27) against the README and the step code myself before including them above. The PR-description mismatch above was independently re-derived from the current diff rather than carried over from an older round's text.


(Correction note: an earlier version of this review, posted at this same commit, contained content from a different PR due to a sandbox file-collision bug on the review tooling's side. This text replaces it as the actual review of PR #241.)

@fredespi
fredespi dismissed rominf’s stale review October 27, 2026 12:54

Requested changes addressed: commits were pushed after this review and all review threads are resolved. Re-review requested.

@fredespi
fredespi requested a review from a team October 27, 2026 12:54

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 7ce66d5

This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.

Round note (7ce66d5): this round files no change request, and the automation's standing change requests on this PR (at bd8a44c3 and dd5fce02) are being withdrawn. The three logic objections they carried were standing rather than re-found: this round's blind full review did not re-find any of them, and each was then put to the reviewer after its report was frozen (anchored evidence, read not run), which found it no longer holds — examine-29's driver Thens and both "both ways" Whens now share cli_other_output; absence and cleared-cell waits go through wait_loop(.., drain_on_exit = false), which returns an error when the child has exited, so only the positive wait_for_screen drains; diagnose-42 carries @requires-bare-metal and its row comment now scopes the bug to bare metal. Of the earlier non-gating items: the README citations are now relative links, and Lint (PowerShell) is green at this head (every reported check is green). The description mismatch is standing (re-found blind below). The method rates three findings below as blocking; this account does not gate on them: the description mismatch is description-only (the squash builds its body from commit messages), and the other two — a wording guard that cannot detect the drain becoming unreachable, and a stale doc paragraph — leave behind no false green in what the suite reports (an unreachable drain would surface as a loud presence-wait failure), so they fall under the floor. All three are worth fixing. The "E2E self-hosted" workflow is still pending with no jobs started at this head, so no GPU/Windows/WSL lane has measured the new scenarios — missing evidence, not a finding. Every finding below is new unless marked standing. The hand-written change requests on this PR are not this automation's and are untouched.

Review — needs work

Full review of the whole change.
This covers most of EAI-8024 (EAI-8006, 8007, 8009, 8011, 8014, 8015, 8016, 8019, 8020, 8021). It does not cover EAI-8010. EAI-8017 and EAI-8018 are left out on purpose and recorded as uncovered.

Blocking

  • (standing since dd5fce0, re-found blind) The PR description claims work the diff does not contain, and leaves out work it does — PR description; tests/e2e-cucumber/expectations.toml; tests/e2e-cucumber/tests/e2e.rs:1545-1552

    • Claimed but absent:
      • An EAI-8010 update --dry-run scenario and row. EAI-8010 appears nowhere in the diff.
      • "Two rows added for EAI-7960". Commit d6825bb deletes them, and expectations.toml has no EAI-7960 row.
      • A mock-lane concurrency clamp of 2..=6 with an E2E_MAX_CONCURRENT override. No such variable exists, and the comment at e2e.rs:1545 says the 64-way default was "tried on this branch and reverted".
      • EAI-8006 "ships as a guard, with no expected-failure row". The serve-auto-gpu-selection-names-a-device row exists, scoped os = "windows".
    • Present but not mentioned:
      • The tui_driver.rs wait-loop refactor and the new src/diagnostic_wording.rs.
      • The re-dated required-check observation in e2e-selfhosted.yml and docs/ci-hardware-testing.md, plus its new xtask test.
      • The target-* skip in xtask/src/ascii_output_contract.rs.
      • The reworded non-blocking text in crates/e2e-report.
      • The EAI-7423 row for serve-27.
    • Confirmed by grepping HEAD for each claimed identifier.
    • Confidence 100 · architectural · Fix: rewrite the description to match the diff. The work is done, so correct the description rather than add work.
  • (new) A guard test can never fail on what its name says it checks — tests/e2e-cucumber/src/diagnostic_wording.rs, the_drain_is_still_reached_with_the_callers_clause

    • The test only checks that the source text contains drain_final_frame_where(Some(describe),. Whether that call is reachable depends on drain_on_exit.
    • Worker A set wait_for_screen's true to false, making the drain unreachable from every caller. All 7 diagnostic_wording tests stayed green, including this one.
    • Its failure message also claims wait_for_screen_where hands its clause to the drain. It does not: it calls wait_loop(.., false).
    • Confidence 100 · logic · Fix: assert the call-site flag the drain depends on (the true passed from wait_for_screen), or rename the test to what it actually checks.
  • (new) wait_for_screen_where's doc still describes the driver as it was before the refactor — tests/e2e-cucumber/tests/e2e/tui_driver.rs:775-808

    • It says "Unlike wait_for_screen, the liveness checks run before the predicate", and that the positive form "can afford the opposite order — and drains … via drain_final_frame". Both forms now run the same wait_loop, liveness first. The positive form drains through drain_final_frame_where.
    • It also says describe reaches "four diagnostics", including the drain message. With drain_on_exit = false it reaches three.
    • The code governs: wait_loop's own doc gives the reason that absence waits must never drain.
    • This is the comment that diagnostic_wording.rs exists to keep accurate (AGENTS.md §5, keep behaviour claims in sync).
    • Confidence 85 · mechanical · Fix: rewrite the paragraph so the only difference it states is drain_on_exit, and list three diagnostics plus the drain message reached through wait_for_screen.

Non-blocking

  • (new) wait_for_screen changed behaviour with no doc update and no test — tui_driver.rs:738-745

    • Previously a marker already on screen returned Ok even if the reader thread had panicked. Now the reader panic returns Err first.
    • Its doc still says a call whose marker is already present "returns immediately".
    • The new direction is the safer one.
    • Confidence 70 · logic · Fix: note the change in the doc.
  • (new) Nothing tests the new orphan-row test's tag-line filter — tests/e2e-cucumber/src/expectation.rs:1937-1941

    • The comment calls ignoring a commented-out @id: "the one thing this test exists to catch".
    • Worker A deleted the filter and all 51 expectation::tests still passed.
    • Confidence 100 · logic · Fix: extract the scan into a helper and add a fixture test containing # @id:gone.
  • (new) xfail rows absorb premise and harness-read failures — tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1685, :1692, :1806, :1817; reconciler at tests/e2e-cucumber/tests/e2e.rs:1713-1724

    • The new steps' panics say "harness read failure, not a verdict". The reconciler counts any failure of an ExpectXfail id as the expected failure.
    • So a broken premise in diagnose-42/44/45, chat-10, serve-28 or serve-30 is recorded as the bug reproducing.
    • Confidence 85 · logic · Fix: pin the expected failing assertion in the row or step, or report premise failures as unexpected.
  • (new) serve-30's Then rejects one of the two outcomes its feature comment accepts — tests/e2e-cucumber/tests/e2e/serving_steps.rs:1426-1460; tests/e2e-cucumber/features/model_serving.feature:403-406

    • serve prints port: in its plan before launch.
    • A fix that refuses at bind time, after printing the plan, would land in the Some(SERVE_PORT) arm and stay red. That row would never go stale.
    • Confidence 70 · logic · Fix: in that arm, also accept a nonzero exit that names the port or says "in use".
  • (new) The "e2e-mock" id is a contract between two files, held together only by prose — tests/e2e-cucumber/src/mock_server.rs:679-684, :718; tests/e2e-cucumber/tests/e2e.rs:867

    • Confidence 85 · mechanical · Fix: use one shared const in both places.
  • (new) Prose that is already wrong or will mean nothing after a squash-merge — tests/e2e-cucumber/README.md:173-177; tests/e2e-cucumber/tests/e2e.rs:1545-1552

    • The README says main.rs "alone is over twenty thousand lines". wc -l gives 19,642.
    • The README also tells this branch's review history ("Most of the citations written during this suite's review had already drifted…").
    • e2e.rs says "tried on this branch and reverted … failed here".
    • Confidence 100 · mechanical · Fix: keep the rule and the reason; drop the count and the history.
  • (new) Leftover MEASURE. planning markers — tests/e2e-cucumber/expectations.toml:235, :244, :280

    • The word is defined nowhere. Commit f06f417 says the dispatch "answered the MEASURE questions", but three rows still carry the marker.
    • Confidence 85 · mechanical · Fix: remove the markers, or say what each row's scope was measured on.
  • (new) diagnose-44 runs a symptom its Gherkin never states — tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:1629-1647; tests/e2e-cucumber/features/diagnose.feature:659-664

    • The Given "a user who hit a device-permission failure" is deliberately empty. The When hard-codes DEVICE_PERMISSION_SYMPTOM, and diagnose-44 does not include that Given.
    • Confidence 70 · mechanical · Fix: have the Given set the symptom on the World and both Whens read it, and add the Given to diagnose-44.

Decisions for the author

  • diagnostic_wording.rs checks doc comments against source text — non-blocking-improvement

    • Its 400 lines grep the source to hold comments and format strings together. That certifies the strings, not the behaviour, which is what AGENTS.md §3 warns about.
    • The drain finding above is a case of it: every check stays green while the clause can never reach the drain.
    • A unit test that drives wait_loop's outcomes and asserts the messages would check what the doc claims.
  • Three new GPU scenarios are telemetry rather than gates — tradeoff

    • serve-27, 29 and 30 are @merge-queue and run only on the non-blocking self-hosted lanes.
    • That saves per-PR GPU budget, but a regression there blocks nothing.
    • The description says this, so confirm it is the intended trade.
  • The PR mixes several unrelated changes — non-blocking-improvement

    • Besides the contract scenarios, it carries a harness wait-loop refactor, a rewording and re-dating of the required-check observation across five files, and an xtask directory-skip change.
    • Splitting the last two out would make each one reviewable on its own.

Positive signals

  • The uninstall scenario (serve-28) checks the product stopped its child before harness teardown runs, so teardown cannot clean up after a product failure and turn it green.
  • Rows for bugs that no lane reproduces (EAI-8007, EAI-8017/8018) were left out instead of shipped as stale XPASS rows or vacuous coverage, and expectations.toml records why.

Deployment notes

None

What this covered

What was read: every changed file in 24e405a7…7ce66d5b (diff from merge-base c5866180), read completely across two coupled-group workers.

  • Group A: harness src/, expectations.toml, e2e.rs, tui_driver.rs, feature_naming.rs, e2e-report, README.
  • Group B: feature files, step files, workflows, xtask, docs.
  • The product code the new steps assert against was traced too: diagnose.rs, fix.rs, examine.rs, serve_cmd.rs, uninstall.rs and the help text in main.rs.

What was run:

  • cargo test -p e2e-cucumber --lib --test feature_naming: 190 + 7 passed.
  • cargo test -p xtask on the date and ASCII tests: 28 passed.
  • Mutations:
    • Turned the tests red: examine-30, examine-31, serve-26, and the xtask date test.
    • Stayed green: the drain-reachability guard and the orphan-row tag filter.
  • Mock e2e for examine-30/31, serve-26/28 and chat-10 with the checkout's own binary: 3 passed; chat-10 and serve-28 failed for the bugs their rows state.
  • CI: every reported check on the head is green. No self-hosted GPU lane reported for this head, so diagnose-43/44/45 and serve-27/29/30 were not run on the reviewed commit, and nothing here ran them.
  • Sign-off: the check tool reported all 56 commits signed off.

Test verdicts:

  • Guards examine-30/31 and serve-26 fail when the product contract is broken (shown by mutation).
  • The xfail scenarios XPASS when their bug is fixed. A broken branch that would stay green in them is any premise failure, which gets recorded as the xfail.
  • The new expectation-scan fixture tests pin their branches, except the tag-line filter.
  • The pre-existing-behaviour resolver tests added here would also pass on the base. They add coverage; they do not test this change.

Did not run:

  • The prior-changes pass: no earlier review comments on these files were read.
  • clippy.
  • The GPU scenarios.

Process deviation: one worker's first mock e2e run used the host-installed rocm from PATH, because the harness falls back to it when ROCM_CLI_BINARY is unset. The operator forbids that. The run was confined to the harness's isolated temp dirs, including serve-28's uninstall --yes --keep-binaries --keep-cache, whose plan listed only /tmp/rocm-e2e-* paths. It was discarded and repeated with the checkout's binary, and only the repeat counts as evidence above.

The PR discussion was not reconciled here.

@rominf
rominf dismissed their stale review October 27, 2026 13:21

Re-reviewed at 7ce66d5: the three logic objections (examine-29 report slot, absence waits satisfied by a dead dashboard, diagnose-42 on WSL2) no longer hold, the README citations are relative links, and every reported check is green. The description mismatch persists and stays reported as non-blocking. See the 7ce66d5 round.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>

@nowycondro nowycondro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 40d58dd

Review — needs work

Full review of the whole change (d59f596f...40d58ddf, 25 files, +2925/-139, test-only — no product code touched). This implements most, but not all, of the work ticket EAI-8024 (cited in the PR title) describes — that ticket lists 13 README-walkthrough defects in scope (EAI-8006..8021 inclusive), explicitly including EAI-8010; this PR's own diff does not actually deliver EAI-8010 despite the description listing it as pinned (see Blocking below).

Blocking

  • The PR description makes several specific, checkable claims about this diff that the diff does not support — PR description (no single file/line; each sub-claim checked against the cited paths below).
    Four separate instances, all verified by reading the actual diff/repo state rather than trusting the prose:

    1. "Suite concurrency" section is fabricated. The description claims the mock lane's concurrency "now derives from available_parallelism() clamped to 2..=6, overridable with E2E_MAX_CONCURRENT, forced to 1 whenever a GPU or shared cache/runtimes directory is in play," with specific benchmark numbers (an 8-way run starving on a 10-core host vs. a 4-way run at 33s/54s). None of this exists: grep -rn "available_parallelism\|E2E_MAX_CONCURRENT" over the repo at this PR's head returns zero matches, and tests/e2e-cucumber/tests/e2e.rs:1553-1558's max_concurrent computation is byte-identical to main (still a hardcoded 1/64 split, no env override, no core-count derivation). A comment this PR does add at that exact spot says the clamped approach "was tried on this branch and reverted" — confirmed against the branch's own history (a commit titled "stop bounding mock-lane concurrency, which broke two of main's scenarios"). The described feature was attempted and abandoned, not shipped.
    2. "Two rows added for EAI-7960" is fabricated. tests/e2e-cucumber/features/dash.feature and tests/e2e-cucumber/tests/e2e/dash_steps.rs have zero diff in this PR (byte-identical to main), and no flaky = true (or any) row for dash-gen-tps-held-after-scrape-failure / dash-gen-tps-expiry-boundary exists in expectations.toml at head or base. The scenarios themselves (pre-existing, untouched) assert throughput correctly stays visible across a scrape failure — the opposite of the "gen throughput is cleared" defect the description says they pin.
    3. The EAI-8010 row in the "Pinned as expected failures" table does not exist. The string "EAI-8010" appears nowhere in the repository at this PR's head or base; tests/e2e-cucumber/features/update.feature (where a rocm update --dry-run scenario would live) is byte-identical to main; no corresponding expectations.toml row exists. This isn't a stray unrelated ticket reference — EAI-8010 is one of the 13 defects EAI-8024 explicitly scopes this PR to cover, so the gap is a real shortfall against both the ticket and the PR's own claimed accomplishments, not just a wording slip.
    4. The EAI-8006 "ships as a guard... with no expected-failure row" claim is also wrong, in the other direction: expectations.toml has a real row for serve-auto-gpu-selection-names-a-device (when = { os = "windows" }, bug = "EAI-8006"), so it is not an unconditional guard the way EAI-8007's (correctly, no-row) entry is.

    Confidence 100 (each point directly verified: grep/diff against the repo at both the PR's base and head, plus the branch's own commit history for point 1). Fix: rewrite the "Suite concurrency" and "Two rows added for EAI-7960" sections and the "Pinned as expected failures" table to match what the diff actually contains — either land EAI-8010's scenario or drop it from the table, and correct the EAI-8006 characterization.

Non-blocking

  • Stale doc comment in tui_driver.rs after this PR's own refactor — tests/e2e-cucumber/tests/e2e/tui_driver.rs:788-798
    This PR merges wait_for_screen and wait_for_screen_where into one shared wait_loop (a sound, behavior-preserving consolidation — traced the drain_on_exit flag and confirmed each function keeps its pre-existing semantics). But the untouched paragraph just above wait_for_screen_where still says "Unlike wait_for_screen, the liveness checks run before the predicate, and deliberately so" — true at main, false now that both functions share one check order (panic → exit → predicate) via wait_loop. The same paragraph's link to [`drain_final_frame`](Self::drain_final_frame) is also now the wrong function; the exit-recovery path it describes calls drain_final_frame_where. No runtime effect, but it misdescribes ordering guarantees in TUI-synchronization code a future maintainer will reason from.
    Confidence 85 · mechanical · Fix: update or remove the "Unlike wait_for_screen..." paragraph and retarget the link to drain_final_frame_where.

Decisions for the author

  • Test-plan counts not independently re-verified — tradeoff
    The description's "Test plan" section states specific scenario/xfail counts for the no-GPU and MI300X lanes (52/10 and 64/16). This review did not reproduce those runs. Given the description errors found above, it's worth the author re-confirming these numbers are current before merge rather than carried over from an earlier draft.
  • chat-local-uses-the-running-server's assertion is slightly looser than the bug it's named for — non-blocking-improvement
    The scenario pins EAI-8009 by asserting the absence of the mock-reply success text rather than asserting the specific "nothing is running" failure wording the bug description names. This is a normal, acceptable xfail pattern (it does fail for the right underlying reason, traced against apps/rocm/src/providers.rs), but a reader matching the scenario against its row's reason text has to take the wording on faith rather than see it pinned. Author's call whether it's worth tightening.

Positive signals

  • diagnose_steps.rs's labelled() helper deliberately returns None rather than Some("") for a blank verify-line specifically so two empty values don't compare equal and read as the diagnosis and fix preview "agreeing" when neither said anything — a real false-positive the comparison would otherwise have had.
  • serving_steps.rs's assert_owned_server_stopped guards against a vacuous pass (asserts a process was actually registered before checking liveness) and deliberately checks the OS for the process's liveness before scenario teardown runs, so the harness's own cleanup can't be mistaken for the product having stopped it — exactly the ordering bug this scenario exists to catch, applied to the test itself.
  • The device-group shim scenarios (e.g. diagnose-credits-a-usable-device) prove their own fixture precondition through the CLI's own examine --json output before relying on it, rather than assuming the stat/id substitution produced the intended state.

Deployment notes

None. Test-only change; no product code touched. The CI workflow edits are reworded comments/doc clarifications only (traced both ci.yml and e2e-selfhosted.yml hunks directly) — no required-vs-non-blocking job reclassification.

What this covered

Full diff d59f596f6e08297c207f008021eecb37defc3558...40d58ddf84ca92bfe5f8dbb373d58dfacf7137bc (25 files), split into 7 coupled-file groups. 4 groups were reviewed by independent fan-out workers: harness core (expectation.rs/diagnostic_wording.rs/lib.rs/mock_server.rs), xtask contracts (ascii_output_contract.rs/workflow_contract.rs/feature_naming.rs), examine/chat/engines step defs + features, and the e2e driver/orchestration group (e2e.rs/tui_driver.rs/update_steps.rs) — the last of these is where the concurrency fabrication above was independently re-confirmed by that worker. 3 groups — diagnose (diagnose.feature+diagnose_steps.rs), serving (model_serving.feature+serving_steps.rs), and CI/docs/expectations (ci.yml, e2e-selfhosted.yml, consolidated.rs, expectations.toml, both READMEs) — were read completely by me directly rather than by an independent worker, after repeated fan-out attempts for them failed with a concurrent-subagent-capacity error in this shared environment (not a capability failure: 4 of 7 spawns did succeed); flagging per the method's rule that a pass run by the reviewer itself is not an independent pass.

Did not run: a dedicated "prior changes" pass (mining historical PR review threads on these files) — no practical route to that from here; git log/git blame over the core harness and driver files and expectations.toml's recent main-branch history was read instead (history pass) and surfaced nothing concerning. Code-comment accuracy was folded into each code pass above rather than run separately.

CI: read via gh pr view --json statusCheckRollup — all 24 reported contexts (CI, CodeQL, readthedocs) are SUCCESS; no failing required check.

Step 9: read the PR description in full, and retrieved the ticket named in the title (EAI-8024) by identifier through the project's tracker. Only identifiers and scope already disclosed by this PR's own title/description are referenced above.

Not reconciled against any discussion on this PR — this review was conducted blind to any existing review thread, by design.

Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
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.

6 participants