Skip to content

The conformance corpus's guards can lose a row silently - #510

Merged
blooop merged 10 commits into
mainfrom
wayfinder/devlaunch-357
Aug 29, 2026
Merged

The conformance corpus's guards can lose a row silently#510
blooop merged 10 commits into
mainfrom
wayfinder/devlaunch-357

Conversation

@blooop

@blooop blooop commented Aug 29, 2026

Copy link
Copy Markdown
Owner

The conformance corpus is the one place the two fake devpods' expectations live, and its guards had holes that let the file shrink quietly. All three gaps the ticket names are closed, each one red first.

The mutations that passed on main

1. A row could be deleted with everything green. Removing the regression row for the delete --ignore-not-found drift, which is the defect the whole corpus was built for:

cargo test -p devlaunch-test-support conformance   3 passed; 0 failed
pytest test/unit/test_devpod_conformance.py        24 passed

The old guard asked whether certain flag names appeared somewhere across all rows. The sibling row that puts --ignore-not-found ahead of the positional still mentioned it, so nothing noticed.

2. The provenance guard was Rust-only. Rewriting a row's verified line to who knows:

pytest test/unit/test_devpod_conformance.py        25 passed

3. The value-flag tables were parallel in two languages with nothing comparing them. Dropping --gidmap from the shim's up table, so the two fakes disagree on whether it consumes the next argv element:

cargo test -p devlaunch-test-support conformance   3 passed
pytest .../test_devpod_conformance.py .../test_devpod_shim.py   47 passed

What the guards do now

Rows carry a stable id, and both drivers hold the roll call of ids they expect to find. A row cannot leave without failing by name on both sides, and the Rust driver also counts the rows it actually ran against the roll call's length, so a shorter corpus fails the driver as well as the guard. The shape checks for the flags dl sends are keyed to row ids rather than to the file as a whole, so moving --init-env behind up's positional fails even while another row still mentions it.

The honesty guard (every row opens its verified with measured or unverified, every row says why it is there, every id is well formed) runs on both sides now. An invariant enforced on one side of a file two suites edit is an invariant with a hole in it.

The value-flag tables are one shared file, test/fixtures/devpod/value_flags.json, which both fakes read. They were two hand-written copies with nothing comparing them; the first version of this PR added a diff of the two sources, and review found that parsing source text mis-attributed behaviour-neutral edits (a comment carrying a quote reported a phantom flag, frozenset({...}) read as the table vanishing). One file deletes both parsers and the whole class. The Rust fake reaches the tables through a Table enum, so a call site cannot ask for one that does not exist; the shim reads the file beside itself and exits 78 naming it if it cannot, because a shim with no tables reads every value flag as bare and makes its value the workspace. Each driver checks the fake it drives against the file: the file's keys against the enum's, and the file's keys against the shim's own namespace.

That changes the guarantee rather than only strengthening it, so plainly: the two fakes disagreeing used to be detected and is now unrepresentable. The residual is what it was. A flag dropped from the shared file changes both fakes together, and the ~14 flags with corpus rows catch that behaviourally on both drivers; the ~30 without are unguarded, exactly as an identical edit to both tables was before.

The same mutations after

mutation Rust pytest
delete the --ignore-not-found regression row the_corpus_answers_the_roll_call and every_row_of_the_corpus_holds_for_the_in_process_fake (ran 23 rows, roll call names 24) and the shape guard fail test_the_corpus_answers_the_roll_call, test_the_rows_the_decision_named_keep_their_shape fail
add a row, touch neither roll call fails, naming the new id fails, naming the new id
verified becomes who knows fails now fails too
move --init-env behind up's positional, flag still in the file fails, naming the row fails, naming the row
seventh table in the shared file only fails, seven keys against six fails, same shape
seventh table in the Rust enum only fails, seven against six (its own side)
private _MACHINE_VALUE_FLAGS in the shim (its own side) fails, naming every table the shim defines
--init-env removed from the shared file its corpus row fails six up corpus rows fail
up table emptied non-empty assert, plus corpus rows non-empty assert, plus six rows

Provenance

Seven of the eight rows carrying an unmeasured claim now read measured, with the method recorded: those needed a provisioned workspace, and PR #353's review provisioned one (docker provider, alpine devcontainer, scratch DEVPOD_HOME) and ran them.

The eighth, list-leaves-workspaces-alone, does not, and that was review's blocking finding: #353's review never ran list against the provisioned workspace, so the upgrade claimed a measurement its own citation did not contain. It takes the ticket's other branch instead and opens with unverified, naming the half that was measured (the exit and the empty case) rather than averaging the two. The honesty guard cannot catch that class, since it reads the opening word and not the citation; a reader with the cited record open is the control that did.

Two corrections to the ticket

  • It says seven rows are marked unverified. Five open with the word; three more (ssh with a trailing --workdir, ssh --workdir before the workspace, list leaving workspaces alone) opened with measured and then inherited part of the claim. Seven distinct behaviours were inherited across those eight rows, which is what the count was tracking.
  • It says five value-flag tables. There are six; stop has none of its own, and both drivers assert that its list is empty rather than treating an empty parse as one.

Gates

  • cargo test --workspace: all ok
  • cargo clippy --locked --all-targets -- -D warnings: clean
  • cargo fmt --check: clean
  • pixi run test: 612 passed, 6 skipped

Re-run on the merged tree after main moved three times during review (twice on the CHANGELOG's [Unreleased], once on devpod_shim.py's own docstring). Each merge keeps every line main brought and places this entry under [Unreleased] / ### Fixed, never inside the shipped [0.25.0] where a textual merge puts it.

Closes #357

🤖 Generated with Claude Code

Summary by Sourcery

Strengthen devpod conformance corpus safeguards so missing rows, altered expectations, and fake-driver flag mismatches fail explicitly in both test implementations.

Bug Fixes:

  • Prevent silent loss or alteration of devpod conformance corpus rows by enforcing stable row identities and required argument shapes in both Rust and Python drivers.
  • Ensure corpus provenance and row metadata invariants are validated by both test suites.
  • Prevent the Rust and Python fake devpods from diverging on value-taking flags by moving their tables into a shared fixture with cross-driver consistency checks.

Enhancements:

  • Improve conformance coverage for value-flag parsing and provenance, including measured or explicitly unverified behavior.

Documentation:

  • Document the strengthened conformance corpus guarantees and provenance updates in the changelog.

Tests:

  • Add roll-call, row-shape, provenance, and shared value-flag table guards to the conformance test suites.

blooop added 2 commits August 29, 2026 17:43
The corpus could lose a row with every suite green. The guard asked whether
certain flag names appeared somewhere across all rows, so deleting the
regression row for the very defect it was built for left both cargo test and
pytest passing: a sibling row still mentioned the flag.

Rows now carry a stable id, and both drivers hold the roll call of ids they
expect to find. A missing one fails by name on both sides, and the Rust driver
also counts the rows it ran. The shape checks are keyed to row ids rather than
to the file as a whole, so moving a flag out of the row meant to carry it fails
even while some other row mentions it.

The honesty guard on each row's provenance ran on the Rust side alone, so a
corpus edited from the Python side could lose it silently. Both drivers enforce
it now, along with row ids being well formed.

The five value-flag tables were parallel in two languages with nothing comparing
them, and only about a third of the names have a corpus row to catch a drop
behaviourally. Each driver now compares its own live tables against the other
language's source.

Every row that once read unverified is measured: those were the rows needing a
provisioned workspace, and PR #353's review provisioned one and ran them.
@sourcery-ai

sourcery-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Strengthens the shared devpod conformance corpus by giving every row a cross-language identity and provenance contract, binding regression shapes to named rows, and making both fake implementations cross-check their value-flag tables so deleted or inconsistent expectations fail loudly.

Sequence diagram for cross-language conformance validation

sequenceDiagram
    participant Corpus as Conformance corpus
    participant Rust as Rust driver
    participant Python as Python driver
    participant Shim as Devpod shim
    participant RustFake as Rust fake

    Corpus->>Rust: load rows and expected ids
    Rust->>Rust: validate row ids and provenance
    Rust->>Rust: run rows and compare count to roll call
    Rust->>Shim: read value-flag tables
    Rust->>Rust: compare shim tables with Rust expectations

    Corpus->>Python: load rows and expected ids
    Python->>Python: validate row ids and provenance
    Python->>RustFake: read value-flag consts
    Python->>Python: compare Rust tables with Python expectations
    Python->>Python: validate named regression row shapes
Loading

Entity relationship diagram for conformance corpus identities

erDiagram
    CORPUS_ROW {
        string id PK
        string verified
        string rationale
    }
    RUST_DRIVER {
        string expected_id
    }
    PYTHON_DRIVER {
        string expected_id
    }
    CORPUS_ROW ||--o{ RUST_DRIVER : validated_by
    CORPUS_ROW ||--o{ PYTHON_DRIVER : validated_by
Loading

File-Level Changes

Change Details Files
Make the conformance corpus membership explicit and loss-detecting.
  • Add stable, validated row IDs to the shared fixture.
  • Maintain matching roll calls in Rust and pytest, rejecting missing, unexpected, or duplicate IDs.
  • Count rows executed by the Rust driver so corpus shrinkage fails independently.
rust/devlaunch-test-support/src/devpod/conformance.rs
test/fixtures/devpod/conformance.json
test/unit/test_devpod_conformance.py
Bind regression and argument-shape checks to specific corpus rows.
  • Replace aggregate flag-presence checks with per-ID assertions for command, flag, and positional ordering.
  • Guard the delete, up, and ssh cases that encode the known parsing regressions.
rust/devlaunch-test-support/src/devpod/conformance.rs
test/unit/test_devpod_conformance.py
test/fixtures/devpod/conformance.json
Enforce corpus provenance and metadata invariants in both drivers.
  • Require every row to identify measured or unverified provenance and provide a rationale.
  • Validate nonempty names and lowercase, digit, and dash-only IDs from Rust and pytest.
rust/devlaunch-test-support/src/devpod/conformance.rs
test/unit/test_devpod_conformance.py
test/fixtures/devpod/conformance.json
Cross-check value-consuming flag tables between the two fake devpods.
  • Have Rust parse the shim source while pytest imports the shim and parses Rust source.
  • Compare global, up, ssh, delete, and status tables, and assert both stop tables are empty.
rust/devlaunch-test-support/src/devpod/conformance.rs
test/unit/test_devpod_conformance.py
rust/devlaunch-test-support/src/devpod.rs
test/fixtures/devpod_shim.py
Update corpus provenance and document the strengthened guards.
  • Convert previously unverified rows to measured entries with methods and refresh fixture expectations.
  • Describe the protection against silent row loss and fake-table drift in the changelog and test documentation.
test/fixtures/devpod/conformance.json
CHANGELOG.md
rust/devlaunch-test-support/src/devpod/conformance.rs
test/unit/test_devpod_conformance.py

Assessment against linked issues

Issue Objective Addressed Explanation
#357 Ensure that deleting or altering a specific conformance-corpus row is detected by its identity rather than only by aggregate flag mentions.
#357 Enforce corpus invariants, especially verified/provenance formatting and row validity, from both the Rust and Python drivers.
#357 Prevent drift between the Rust and Python value-flag tables and update the formerly unverified corpus rows with measured provenance.

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.34513% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.75%. Comparing base (2686879) to head (1010ac4).

Files with missing lines Patch % Lines
...t/devlaunch-test-support/src/devpod/conformance.rs 95.45% 3 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 96.05% <97.34%> (+<0.01%) ⬆️

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

Components Coverage Δ
shipped code (rust) 96.05% <97.34%> (+<0.01%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This was generated by AI during review.

Reviewed at merge-base 669df03 (git diff 669df03...HEAD), in fresh context that never saw this code written. Two axes ran as independent parallel subagents; the mutation section at the end is my own work and I say so plainly. Preflight: ref resolves, diff non-empty (4 files, +558/-56), all 15 checks green.

Everything below was run against a git archive copy of the PR head under a scratch CARGO_TARGET_DIR, cargo 1.98.0 and pytest 9.0.3. Baselines first: cargo test -p devlaunch-test-support conformance 5/5, pytest test/unit/test_devpod_conformance.py 29/29, cargo test --workspace all green.

Standards

No blocking findings. The duplication this PR adds is load-bearing rather than accidental: ROLL_CALL and REQUIRED_SHAPES exist in both languages because one copy is what the ticket is about, and the two copies cannot drift apart in practice — the corpus is the referee. I checked: adding a row and updating only one roll call makes the other driver fail on its unexpected branch, so the two lists are forced into agreement transitively. They are identical today (24 ids, same order; 9 shapes, same order, verified mechanically).

1. The roll call of tables is unguarded, unlike the roll call of rows. (minor)
conformance.rs:398-404 and test_devpod_conformance.py:319-325 each hardcode the same five table names, plus stop special-cased. Nothing asserts that this list is the complete set of *_VALUE_FLAGS symbols either source defines. Verified: adding a sixth-plus table to rust/devlaunch-test-support/src/devpod.rs alone leaves both suites green. That is the same artifact class the ticket distrusts, one level up — a hand-maintained list with no completeness check. It is speculative today (a new table would arrive with a new subcommand), which is why it is minor, but the fix is small: derive the names by scanning each source for *_VALUE_FLAGS and assert the derived set equals the enumerated one.

2. shim_table's quote-counting mis-attributes behaviour-neutral edits to the fakes. (minor)
conformance.rs:431-448 reads the shim's sets with .split('"').skip(1).step_by(2), which treats any double-quoted text between = { and the first } as a flag name. Three plausible edits, all verified:

edit to the shim (behaviour unchanged) what the Rust test says
a comment inside the set: # devpod calls this an "additional feature" the fakes "disagree", listing a phantom "additional feature" flag
_UP_VALUE_FLAGS = frozenset({...}) (matching _STOP's own style) "the shim no longer defines _UP_VALUE_FLAGS" — it does
_STOP_VALUE_FLAGS: frozenset[str] = frozenset() "the shim's stop table is no longer empty" — it is empty

Every one fails in the safe direction, which is the important half. But each blames the wrong thing, and the third and the mirror check at test_devpod_conformance.py:334 are exact-string matches on a whole line, so a formatter or a type annotation breaks them. Filtering parsed entries to those starting with - would kill the first; matching the empty tables by parse rather than by literal line would kill the other two.

Not findings, checked and clear. No docs/ page or the README mentions the corpus, so no doc-sync obligation is missed. The CHANGELOG entry is present under [Unreleased] / ### Fixed. why still needs #[allow(dead_code)], which #353's review already flagged as an argument for a tagged enum; this PR does not make it worse.

Spec

One finding worth acting on before merge.

1. One of the eight provenance upgrades claims a measurement the cited record does not contain. (major)
The ticket says: "Either upgrade those rows to measured with the method recorded, or reword them to say 'not measured here' rather than implying it cannot be done." The PR body cites PR #353's review as the record. Seven of the eight rows check out against it exactly. list-leaves-workspaces-alone does not.

The row now reads (test/fixtures/devpod/conformance.json):

measured: devpod list --output json at v0.26.1 with a workspace provisioned lists it and leaves it Running, on the same alpine devcontainer under a scratch DEVPOD_HOME.

PR #353's review (review 5000623596) enumerates what it measured against the provisioned workspace, and list is not in it:

"…every one measured out as the row says — stopStopped; status twice → unchanged; ssh --workdir / <ws> --command … on a Stopped workspace → 0 and Running; up <ws> → Running; delete <ws> --ignore-not-found and delete <ws2> → 0 and gone."

It also scopes itself by name — "Rows 4, 5, 7, 15, 21 and the inherited halves of 16/17" — which is seven, and row 23 is not among them. Its only list measurement is the empty-machine one (list --output json → []/0), which was already measured before this PR. The row also seeds two workspaces, one Running and one Stopped, and asserts both unchanged; the cited run had one alpine devcontainer plus a second workspace that was deleted.

This is the exact defect the honesty guard exists to police, and the guard cannot catch it: every_row_says_how_it_was_verified checks only that the line opens with measured. Either narrow the wording to what #353 actually recorded, or measure it and say so. One line.

2. Gap 3 is closed, not half-closed. The scope call is right. (no action)
The spec's remedy clause is "The value-flag tables are now parallel in two languages with nothing comparing them". A diff of the tables is precisely what that asks for, and it is there and it works in both directions. The "only ~14 of ~44 flag names have a corpus row" clause is the diagnosis of why the missing diff was dangerous, not a demand for 30 more rows — and generating them would have to mark them unverified, trading the honesty problem for a coverage number. I probed the residual: emptying UP_VALUE_FLAGS on both sides passes the table diff vacuously but fails five corpus rows behaviourally, so the covered flags are still guarded by behaviour and the uncovered ones are guarded by the diff. The mode that actually happened — one side changes — is caught for all 44.

3. Both corrections to the ticket verified. (no action)
Eight rows changed provenance, not seven (ids 4, 5, 7, 15, 16, 17, 21, 23 by position; row 17's is a wording follow-on from row 16). Six tables exist, not five, and stop's emptiness is asserted on both sides.

4. "Red first for each" holds. Every mutation the spec names fails a test that names the row or the flag. See below.

Mutation probes (mine, not an axis)

Ten mutations on a pristine copy of the PR head. All reverted; the review worktree is clean.

mutation cargo test -p devlaunch-test-support conformance pytest test_devpod_conformance.py
delete delete-missing-with-ignore-not-found 3 fail: roll call names the id, "ran 23 rows and the roll call names 24", shape guard 1 fails, naming the id
add a row, touch neither roll call 2 fail: "carries rows this driver does not name: [stop-already-stopped-workspace]", count check 1 fails, naming the new id
two rows sharing one id uniqueness assert fires (24 vs 25) fires
verifiedwho knows (its own side) test_every_row_says_how_it_was_verified fails
move --init-env behind up's positional fails, naming the row fails, naming the row
drop --gidmap from the shim's table fails
drop --userns from the Rust table fails
add a 7th value-flag table to one fake green green (Standards 1)
quoted comment inside the shim's up set fails with a phantom flag (Standards 2)
frozenset({...}) / : frozenset[str] in the shim fails claiming the table is gone / non-empty (Standards 2)

The add-a-row direction is loud on both drivers, by name — the unexpected branch, not just the Rust count check. That was the thing I most expected to be one-sided and it is not.

Neither cross-language parser asserts a nonzero parse, but the guard is not vacuous. Both hard-fail when their marker is absent ("the shim no longer defines X"; assert match), and a parse yielding zero entries is caught transitively because all five compared tables are non-empty on the live side. The only silent case is both sides genuinely empty, which the corpus rows then catch for every covered flag. An explicit assert!(!parsed.is_empty()) on each side would make that reasoning local instead of inferred; worth adding for clarity, not for safety.

Verdict

Request changes, on Spec 1 alone. (Posted as a comment: GitHub refuses --request-changes on a PR authored by the same account. The written verdict is the gate.)

The mechanism is sound and I could not break it in the direction that matters: every way I found to lose a row, shadow a row, downgrade provenance or split the two flag tables fails by name on both drivers. The one blocker is a sentence, not a design — list-leaves-workspaces-alone now asserts a measurement that PR #353's review does not record, in the one file whose whole purpose is that its claims are true. Reword it or measure it.

Non-blocking, in the order I would act on them:

  1. Standards 1 — nothing checks that the enumerated value-flag tables are all of them; a seventh is silent on both suites.
  2. Standards 2shim_table's quote-counting and the two exact-line stop checks fail loudly on edits that change no behaviour, blaming the fakes for a disagreement they do not have.
  3. An explicit nonzero-parse assertion in each cross-language parser, so the guard's non-vacuity is stated rather than inferred.

Comment thread test/fixtures/devpod/conformance.json Outdated
"name": "list leaves every workspace as it found it",
"why": "Same read-only claim as status, across the whole machine.",
"verified": "measured for exit and emptiness on a fresh home; the no-side-effects claim is inherited from `devpod list --help` and both fakes agreeing.",
"verified": "measured: `devpod list --output json` at v0.26.1 with a workspace provisioned lists it and leaves it Running, on the same alpine devcontainer under a scratch DEVPOD_HOME.",

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The citation does not cover this row. (major)

The PR body upgrades eight rows to measured on the strength of PR #353's review. Seven check out against it exactly. This one does not.

Review 5000623596 enumerates what it measured against the provisioned workspace, and list is not in it:

"every one measured out as the row says -- stop -> Stopped; status twice -> unchanged; ssh --workdir / <ws> --command ... on a Stopped workspace -> 0 and Running; up <ws> -> Running; delete <ws> --ignore-not-found and delete <ws2> -> 0 and gone."

It also names its own scope -- "Rows 4, 5, 7, 15, 21 and the inherited halves of 16/17" -- which is seven rows, and this is not one of them. Its only list measurement is the empty-machine one (list --output json -> []/0), which was already measured before this PR.

The row also seeds two workspaces, one Running and one Stopped, and asserts both unchanged; the cited run had one alpine devcontainer plus a second workspace that was deleted.

every_row_says_how_it_was_verified cannot catch this -- it checks only that the line opens with measured -- so it is the honesty defect the guard polices, sitting inside the PR that strengthens the guard. Either narrow the wording to what #353 recorded (the base text, "measured for exit and emptiness on a fresh home; the no-side-effects claim is inherited from devpod list --help and both fakes agreeing", was accurate), or measure it and say so.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

You are right, and this was the one finding I could not argue with: I read #353's review as having provisioned a workspace and therefore covering everything that needed one, when what it actually enumerates is seven behaviours and list against a provisioned workspace is not among them. Its only list measurement is the empty-machine row, which was already measured before this PR.

Fixed by taking the ticket's other branch for this row rather than inventing a run. It now reads:

unverified for the claim this row is about: PR #353's review provisioned a workspace but never ran list against it, so that a listing disturbs nothing is not measured here -- it is inherited from devpod list --help and both fakes agreeing. What was measured is the exit and the empty case: devpod list --output json at v0.26.1 on a fresh home exits 0 and prints [].

It opens with unverified rather than measured because the claim the row exists to make is the no-side-effects one, and that is the half nobody has run. The measured half is named rather than dropped, so the line says which is which instead of averaging them, which is the distinction the whole file is organised around.

So the count is seven upgrades and one rewording, not eight upgrades. The PR body, the CHANGELOG entry and the corpus's own header note all say that now.

Worth recording that your finding is exactly the failure the honesty guard cannot see: it checks the opening word, and a wrong claim opening with measured passes. Making it check the citation would mean parsing prose against a review body, which I do not think is worth building. What caught it was a reader with the cited record open, which is the control that was supposed to catch it.

blooop added 5 commits August 29, 2026 19:08
Review of #510 found two more copies of the shape this ticket is about. The
tables saying which devpod flags take a value were written out once per fake,
and the guard for them parsed the other language's source: a comment carrying a
quote inside a table reported a phantom flag, frozenset({...}) read as the table
vanishing, and the empty-table checks were exact-line matches a formatter could
break. All failed safe and all blamed the wrong thing.

They are one file now, test/fixtures/devpod/value_flags.json, for the same
reason the corpus is one file. The Rust fake reads it through a Table enum, so a
call site cannot ask for a table that does not exist and adding one is a change
the compiler walks you through. The shim reads it by path beside itself, and
exits 78 naming the file if it cannot, because a shim with no tables reads every
value flag as bare and makes its value the workspace.

Both source parsers are gone with it. What is left is a roll call of tables on
each side, the same guard the rows have: the file's keys against the Rust
enum's, and the file's keys against the shim's own namespace. A seventh table in
one place fails by name, which it did not before.
# Conflicts:
#	CHANGELOG.md
#	test/fixtures/devpod_shim.py
@blooop

blooop commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

All three addressed, and the two minors turned out to share one fix. Head is cc3dbc4; all 15 checks green.

Spec 1 (blocking): the citation did not cover the row

Conceded without argument. #353's review enumerates seven behaviours and list against a provisioned workspace is not one of them; its only list measurement is the empty-machine row, already measured before this PR. Answered inline in full. The row takes the ticket's other branch now and opens with unverified, naming the half that was measured rather than averaging the two. Seven upgrades and one rewording, and the PR body, CHANGELOG and the corpus header note all say so.

Standards 1 and 2: one fix, because they were one problem

You flagged the table roll call being unguarded, and the source parsers mis-attributing behaviour-neutral edits, and suggested a shared fixture would delete both. It does, so I took it rather than the smaller step.

test/fixtures/devpod/value_flags.json now holds the six tables, and both fakes read it. The parsers are gone entirely, and with them the three mutations you found: there is no quote-counting to report a phantom flag, no frozenset({...}) to misread as a vanished table, no exact-line match for a formatter to break.

Two things made it viable, and I checked both before starting rather than assuming:

  • The shim is a wrapper, not a copy. shim_fixtures.py writes a sh shim on PATH that execs the program where it lives, so a path relative to __file__ holds in every harness that runs it. A copy would have broken this and I would have taken the smaller step instead.
  • The Rust side needed no new dependency. serde derive is dev-only in that crate, so the tables parse through serde_json::Value into a OnceLock.

On the Rust side the tables are reached through a Table enum rather than a string key, which is what closes your Standards 1 constructively rather than by scanning source: a call site cannot ask for a table that does not exist, key()'s match is exhaustive so a new variant must be given a name, and Table::ALL is checked against the file's keys. On the Python side the equivalent check reads the shim's own namespace (dir(shim)) rather than its text. Each driver checks the fake it drives against the shared file, and the file is the referee between them, which is the same argument you accepted for ROLL_CALL.

Your assert!(!parsed.is_empty()) suggestion is in, generalised: every table but stop must be non-empty, stop must be empty, and every entry must start with -.

What changed about the guarantee, stated plainly. The old diff detected the two fakes disagreeing. They now cannot disagree, which is strictly better, but it is a different claim and worth being explicit about: the mode where one side changes is unrepresentable rather than caught. The residual is unchanged from what you probed. A flag dropped from the shared file changes both fakes together, and for the ~14 covered flags the corpus rows catch it behaviourally on both drivers (verified: removing --init-env from the file fails up-init-env-before-source on both). For the ~30 uncovered ones it is unguarded, exactly as an identical edit to both tables was before.

New mutation probes

mutation Rust pytest
seventh table in the shared file only fails, listing the seven keys against the six the enum knows fails, same shape
seventh table in the Rust enum only (variant + ALL + key) fails, seven against six (its own side)
_MACHINE_VALUE_FLAGS = {...} private table in the shim (its own side) fails, naming every *_VALUE_FLAGS the shim defines
--init-env removed from the shared file corpus row fails behaviourally six up corpus rows fail
up table emptied non-empty assert fires, plus corpus rows non-empty assert fires, plus six rows

The three parser mutations from your table are not reproducible: there is no parser to fool.

Gates, and one thing about CI

cargo test --workspace, clippy --locked --all-targets -D warnings, fmt --check and pixi run test (612 passed) all green locally on the merged tree, and the 15 checks are green on the merge ref.

Main moved three times during this round, twice on the CHANGELOG's [Unreleased] and once on devpod_shim.py's own docstring. Each merge is resolved so that git diff origin/main -- CHANGELOG.md shows zero deletions, with my entry under [Unreleased] / ### Fixed and never inside [0.25.0] — the textual merge does land it in the shipped section, so it was checked each time. The shim conflict was main's citation cleanup (#519) landing on the same docstring line this PR edits; both changes are kept.

Not merging.

@blooop
blooop merged commit ee1eb0b into main Aug 29, 2026
15 checks passed
@blooop
blooop deleted the wayfinder/devlaunch-357 branch August 29, 2026 20:26
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.

The conformance corpus's guards can lose a row silently

1 participant