Skip to content

Close Stage 5: exit audit, eight overstated verdicts, and PyFlow 0.1.0 - #48

Merged
AdamClemens merged 1 commit into
mainfrom
audit/stage-5-exit
Aug 29, 2026
Merged

Close Stage 5: exit audit, eight overstated verdicts, and PyFlow 0.1.0#48
AdamClemens merged 1 commit into
mainfrom
audit/stage-5-exit

Conversation

@AdamClemens

Copy link
Copy Markdown
Owner

A separate auditor-stance pass (prompts/common/AUDITOR.md) over the
thirteen-criterion verdict table TASK-034's own session wrote as it
closed. Eight of the thirteen did not survive, and one of the eight
was a defect in shipped behaviour rather than only in a verdict.

Per-criterion record: docs/planning/roadmap.md's new Status as of
2026-08-29
section, placed where all four prior stages put theirs —
the generated status report had been flagging its absence on its own
("has no pending tasks recorded, but isn't marked complete").

Seven failures share one shape

Every one was a later clause of a criterion whose first clause was
genuinely met — a sentence the criterion had already written down.

Criterion Claimed Actually
13 Met Names two ADR-003 substitution checks; only the PressureCoupling one existed. register_linear_solver was never called outside assembly.py, so a PISO constructing its own solver would have passed every scenario in the repository. Confirmed by mutation, not argued.
10 Met Six stale claims, in six files no Stage 5 task opened. README.md said "Stage 5 — not yet started" on the day Stage 5 closed. docs/architecture/sequences.md — whose only stated job is "in what order do things actually happen when PyFlow runs" — contained no mention of pressure, predictor, corrector or navier_stokes_step at all.
11 Met Neither mvp.md nor releases.md recorded the MVP as reached, though releases.md names "Reaching the MVP" as a trigger it was told to act on the moment it fired.
6 Met Enumerates five rejection surfaces; four existed. A periodic boundary carrying a prescribed velocity or per-field override loaded cleanly and was then ignored outright.
5 Met "The absolute tolerance is stated and defended in the feature file against the mesh actually used" — none was stated. Errors of 10, 5 and 2 would have satisfied monotonic decrease exactly as well as the real ones.
7 Met The Ghia cavity fixture is square, at origin (0,0), lid speed 1.0 — three violations of the degenerate-fixture rule, taken and unrecorded.
3 Met The initial-divergence clause — the one that stops the two assertions after it being vacuous — was true (1.85 against 1e-4) and asserted nowhere.

The eighth was real

simulation.velocity_solved meant two different things. With no
scalar_pattern, _add_solved_velocity_rendering called
navier_stokes_step. With one, _add_passive_scalar_transport
transported velocity's components like any other scalar and never
pressure-corrected them
— so a configuration saying "solved" produced a
velocity that was not incompressible, with no error and nothing rendered
differently, selected by whether a scalar happened to be configured.

Measured rather than argued — maximum divergence over 1, 10 and 40
frames:

1 10 40
uncorrected 9.16 8.24 6.95
corrected 2.30 0.47 0.057

TASK-031 and TASK-034 both wrote the gap down — in two CLAUDE.md files,
and against no criterion, which is how it survived Criterion 12 being
marked met. docs/practices.md gains the rule that distinction produced.

Two checks that read like gates and were not

  • README's Current Phase had gone a full stage stale for the second
    time
    (the Stage 2 audit found the first). Now mechanical:
    generate_status_report.py's drift check fails make ci when the
    stage README names is not the roadmap's own frontier. No second source
    of truth — the roadmap decides, README is checked against it.
  • The Gherkin-scenario drift rule had been silently inert. Its
    pattern needed a literal space before "are Gherkin scenarios"; a later
    edit hard-wrapped the roadmap's line between the count and that phrase.
    A pattern that stops matching reports nothing to check, which reads
    exactly like a clean pass — the roadmap sat claiming 79 scenarios
    against a live 94, behind a green gate. All three claim patterns are
    now whitespace-tolerant, with a regression test quoting the real
    wrapped sentence.

Four maintainer decisions, all taken the thorough way

  • PyFlow 0.1.0. releases.md rewritten with a real process —
    semantic versioning with MINOR carrying stage completion, a release
    cut only after a stage's exit audit completes, an annotated tag on a
    two-platform-green commit, nothing published anywhere yet (a recorded
    decision, not an omission). planning/data/releases.yaml populated for
    the first time; its own stated trigger had fired.
  • Criterion 6's missing surface: built. A fourth rule in
    _validate_boundary_conditions_jointly, scoped to non-default values
    so every periodic configuration already in the repository stays valid.
    Six rejection tests, one acceptance test.
  • Criterion 7: fix what can be fixed, record what cannot. Ghia's
    Re = 100 profiles are nondimensionalised on a unit square at unit lid
    speed — two forced exceptions, now stated in the feature file. The
    origin was never forced, and the cavity now runs at a non-trivial one.
    Verified by a full three-resolution run first: identical errors
    (0.1433, 0.0874, 0.0578), vortex centre 0.0010 from Ghia's own.
  • velocity_solved: routed through. Both live paths call
    navier_stokes_step, with a regression test whose bound was chosen
    from both measurements above.

Verification

make ci green locally: 688 tests, 95 scenarios across 21 feature
files
, every check passing (14m07s — the Ghia validation is ~13 of
them). Stage 5's own Criterion 9 is discharged by two real runs, both
green on ubuntu-latest and windows-latest: 33269489214 (PR #47) and
33270312866 (its merge to main).

This branch's own CI run is what the Merge Gate requires before it
merges
— stated rather than assumed, since a local pass is not that
evidence. The v0.1.0 tag is deliberately not cut here: the process this
PR writes says a release is tagged on main after the stage's exit audit
completes, which is after this merges.

🤖 Generated with Claude Code

A separate auditor-stance pass over the thirteen-criterion verdict table
TASK-034's own session wrote. Eight of the thirteen did not survive, and
one of the eight was a defect in shipped behaviour rather than only in a
verdict. Per-criterion record: docs/planning/roadmap.md's new Stage 5
status section, in the place all four prior stages put theirs (the
generated status report had been flagging its absence on its own).

Seven failures share one shape: a later clause of a criterion whose first
clause was genuinely met. Criterion 13 names two ADR-003 substitution
checks and only the PressureCoupling one existed -- register_linear_solver
was never called outside assembly.py, so a PISO constructing its own
solver would have passed everything (confirmed by mutation). Criterion 6
enumerates five rejection surfaces and four existed. Criterion 5's Ghia
bullet ends by requiring a stated absolute tolerance and none was stated.
Criterion 3's initial-divergence clause -- the one that stops the two
assertions after it being vacuous -- was true and asserted nowhere.

The eighth was real: simulation.velocity_solved meant two different
things. With a scalar_pattern, bootstrap transported velocity's
components like any other scalar and never pressure-corrected them, so a
config saying "solved" produced a velocity that was not incompressible
(max divergence 9.16 -> 8.24 -> 6.95 over 1/10/40 frames, against
2.30 -> 0.47 -> 0.057 corrected). Both tasks that touched it wrote the
gap down -- in two CLAUDE.md files, and against no criterion, which is
how it survived Criterion 12 being marked met.

Fixed rather than recorded, at the maintainer's direction: both live
paths call navier_stokes_step; a periodic boundary may no longer
prescribe anything (the fourth rule of _validate_boundary_conditions_
jointly, scoped to non-default values); the Ghia cavity runs at a
non-trivial origin with a stated, measured error bound at its finest
resolution; and docs/planning/releases.md gains a real release process,
cutting PyFlow 0.1.0 as the MVP release now that reaching the MVP has
fired its own long-recorded trigger.

Two checks were not doing their job. README's Current Phase had gone a
full stage stale for the second time (the Stage 2 audit found the first),
so it is now gated: generate_status_report.py fails make ci when the
stage README names is not the roadmap's own frontier. And the
Gherkin-scenario drift rule had been silently inert since a line wrap
split its pattern -- the roadmap sat claiming 79 scenarios against a live
94, behind a green gate. All three claim patterns are now
whitespace-tolerant, with a regression test.

Also fixes six stale documentation claims in files no Stage 5 task
opened, adds the Navier-Stokes timestep sequence to sequences.md (whose
only job is runtime sequences and which had no mention of pressure at
all), and records three new rules in docs/practices.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AdamClemens
AdamClemens merged commit 0e1bd72 into main Aug 29, 2026
2 checks passed
@AdamClemens
AdamClemens deleted the audit/stage-5-exit branch August 29, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant