Skip to content

process(testing): per-project test strategy at CAC-5, unit tests off by default and enforced - #417

Merged
MendixMau merged 13 commits into
masterfrom
feat/test-strategy-docs
Oct 16, 2026
Merged

MendixMau merged 13 commits into
masterfrom
feat/test-strategy-docs

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Fast-tracked by the maintainer on 2026-10-16 because users report tests firing on every change. The baseline-text A/B is waived; the end-to-end A/B (E3) follows after merge as a check, not a gate.

Direct lane

What changed and why (one paragraph):
Split out of draft #409 (third of three; #413 is step timing, #416 is affected-tests.sh, both merged). Per change we test the gate plus what the change affects; a module's own journeys at module done; everything once at build complete and before every later deploy; everything any time the user says "run everything". The strategy is picked per project with the developer at CAC-5 and recorded in PROJECT.md ## Test strategy (Status CONFIRMED, or ASSUMED in auto mode, no new question). Build complete is declared by the developer, never by the agent alone. Unit tests are OFF by default and are on only for modules named at CAC-5 (calculation, money/date, mapping, REST/Java logic or a fixed bug); writing them is separate from running them.

Field test of the release candidate (#417 + #421 + #427) found "unit tests off by default" was not enforced. Findings: (1) Unit tests default: was written but read by no script; (2) affected-tests.sh defaulted to unit=affected and printed a run ./bin/test-scratch.sh ... .test.mdl row; (3) a grant execute on microflow X that describe appends to a microflow script counted as an access change and selected every unit test of the owning module; (4) the CAC-5 table put unit tests in "Every change"; (5) wording in testing-shape.md module close, brd-to-build-plan.md PROVE and existing-app-change.md could still send an agent to run tests per change. Fixes in this PR:

  • affected-tests.sh reads Unit tests default:; no or the line absent means unit is off for every module not flipped, printed as one unit off (...) line and never a run ... .test.mdl row. yes keeps the old behaviour. Build complete and "run everything" (--moment deploy / --moment all) run every existing unit test.
  • The ## Test strategy Unit cell is a scope only and never turns unit on: at the change and module moments the default and the per-module switches always filter it (a row recorded with the Offer defaults runs no unit for a module that is off; a row none plus a switched-on module still runs that module). The Offer table shows "modules on only". Unit modules: strips # and ( ... ) comments, reads a - bullet, none and any case, and warns on stderr for a name matching no module.
  • Turn unit on: whole project Unit tests default: yes in PROJECT.md; per module Unit modules: Shop, Admin in PROJECT.md (decided at CAC-5) or the line Unit tests: yes (or the Declared-shape row | Unit (...) | yes |) in the module brief.
  • A grant/revoke on a microflow or page the same script creates or modifies is part of that subject and selects nothing by module. A standalone grant (entity, module role) still counts.
  • Docs: Offer table renamed to Every change / Module done / Build complete with the "run everything" sentence and the developer-declared definition; testing-shape.md events table, module-done and build-complete rows; module-review.md PROVE now runs --moment module --module <M>; existing-app-change.md and the Offer name --module <M> (the bare call selected nothing). REST skills unchanged on purpose.
  • Review of the first head (CHANGES): HELD removed from title, CHANGELOG and test header; CHANGELOG conflict with master resolved; waiver in the Impact block.

Impact

Row Answer
Process YES. (1) Unit tests are off per project unless turned on (new enforcement in affected-tests.sh); (2) the full run moves from module close to build complete / before each later deploy; module done is module-scoped; (3) new stage artifact in PROJECT.md ## Test strategy, written inside the existing CAC-5 Offer (no new question; the end-of-build "full run now?" is a confirmation); (4) module-review.md PROVE gains one step. Nothing new blocks.
Dependencies project-bin/affected-tests.sh reads PROJECT.md (Unit tests default:, Unit modules:, ## Test strategy) and module briefs (architecture/modules/<M>/module-brief.md, <M>-brief.md); exec.sh lane text; producers are the CAC-5 Offer and the module brief. Absent means unit off. sync-project.sh does not write PROJECT.md.
Context cost bin/render-routing.sh --check, origin/master then this head: baseline 75,225 to 75,219 words (budget 80,000). Stage 5 69,795 to 69,789 (test role 21,667 to 21,666, gate 22,731 to 22,729, review 31,435 to 31,434); stage 6 45,835 to 45,832; stage 4 33,487 to 33,482; stage 0 28,247 to 28,245. Every stage and role is at or below master.
Behaviour NOT RUN, waived by the maintainer on 2026-10-16: users report tests firing after every change and that cost outweighs waiting. The baseline-text A/B and the end-to-end A/B (E3, master vs this branch, scripted changes on a lab copy) run after merge as a check. If E3 shows escapes at build complete that a module-done full would have caught, the default goes back in a follow-up PR.
Runtime n/a: advisory script reads two more files; no gate change.

Field evidence: a five-app lab project (Mendix 11.15, mxcli v0.26.0): one exec 28 to 41 s, a full mxcli test about 6 min. The RC field test: a logic fix selected a unit test by module through a trailing grant line. Named risk: a defect the full module-close run would have caught may now surface only at build complete.

  • No client data anywhere in the diff
  • Test tier: T1. tests/wave2/test-affected-tests.sh 79/0 on this head; the new cases (default absent, no, yes, module flip by line, table row and PROJECT.md Unit modules:, describe-style microflow with trailing grant, standalone entity grant, row is scope only, Unit modules: parsing) give 71 pass and 8 fail against the previous affected-tests.sh (origin/master). tests/wave2/test-test-strategy-docs.sh 15/0. exec change-class, subjects and step-time tests pass.
  • CHANGELOG.md line under Unreleased
  • Categorisation: kind idea (process change, maintainer decision) plus fix (field-test defect) · lane Direct · regression risk medium · toolkit change, not an upstream mxcli bug

🤖 Generated with Claude Code

…oped, deploy-ready runs everything (HELD)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MendixMau and others added 2 commits October 16, 2026 16:01
…orces it; align the moments

Field test of the release candidate found "Unit tests default: no" read by no script, so a
unit test was offered after every change. affected-tests.sh now reads the default (no, or the
line absent, means unit off, one "unit off" line, never a .test.mdl run row), flips per module
(PROJECT.md "Unit modules: A, B", or "Unit tests: yes" in the module brief), and lets an explicit
Test strategy row win. A grant on a microflow or page the same script changes is part of that
subject and no longer selects the owning module. Moments are now every change, module done, build
complete (developer-declared) and "run everything". module-review PROVE runs the module row.
The maintainer fast-tracked this on 2026-10-16; the baseline-text A/B is waived, E3 follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MendixMau MendixMau changed the title HELD: process(testing): per-project test strategy at CAC-5, module close scoped (409c) process(testing): per-project test strategy at CAC-5, unit tests off by default and enforced Oct 16, 2026
@MendixMau
MendixMau marked this pull request as ready for review October 16, 2026 08:04
MendixMau and others added 7 commits October 16, 2026 16:13
…st Unit modules parsing

Review r2: a recorded Offer table (affected, module) overrode the off-by-default gate,
and a table recording none could never run a flipped module. The gate now always filters
at the change and module moments. Unit modules: strips # and (...) comments, reads a
bullet, none and any case, and warns on names matching no module. Offer shows
'modules on only'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rding, warn on unknown rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow master

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nknown row labels warn after a match too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MendixMau
MendixMau merged commit 2bd53ed into master Oct 16, 2026
2 checks passed
MendixMau added a commit that referenced this pull request Oct 16, 2026
…triggers, --require-assertions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MendixMau added a commit that referenced this pull request Oct 16, 2026
…432)

Independent review: CHANGES in round 1 (432.md, Opus), all three items fixed; round 2 self-review APPROVE (432-r2).

# Review #432 round 2 (self-review of fix delta to 69ff044)
Round 1 (432.md, Opus) asked 3 changes, all addressed: default-no pointer now names PROJECT.md `## Test strategy` / `Unit modules:` from CAC-5 and states #417's run moments; reasons reduced to #417's three (calculation or mapping, Java or REST, fixed bug), the "branchy rule" reason removed; `--require-assertions` added for mxcli 0.21+. Fixer ran render-routing --check, check-skill-routing, test-render-routing 5/0, test-routing-tier 26/0, portability clean. Merge before #431.

APPROVE
MendixMau added a commit that referenced this pull request Oct 16, 2026
#454)

Opus review 454-r1: APPROVE

# PR #454 review, round 1

PR: fix(progress-report): polish counts, stage 0 pending, calm no-log line
Base: release/v1.0, head 98649f9 (branch fix/progress-report-polish). Up to date with base, merges clean.

Verdict: APPROVE

## Must-fix

None.

## What I checked

1. **"done" rows count as built (build-plan-status.sh).** Both `classify()` copies (the plan-table rollup at line 303 and the HTML tally at line 559) now use `startswith(("built", "done"))`, with "not built" still checked first. progress-report.py `classify_state` already treats `done` as built, so the two "done" readings now match. `**done** 2026-...` and `Done` both count in both parsers (probe run). `not done` does not match `done` in either.
2. **Future-stage placeholder FAIL shows PENDING (progress-report.py ~1475).** It only relabels when all of these hold: status is `FAIL`, the detail contains "placeholder", `info["current"]` is a known stage, and the stage index is strictly greater than the current one. The current stage and earlier stages keep FAIL. With an unknown current stage (no or unparseable PROJECT.md) nothing is relabelled, which is the safe default. Only two gate-check.sh FAIL messages contain "placeholder" (lines 827 and 835, both the `Confirmed by:` sign-off check), so a substantive failure on a later stage stays FAIL unless its detail happens to say "placeholder". The stage badge for a later stage is already "pending" (`stage_state` comes from PROJECT.md), so the sub line now agrees with the badge.
3. **Calm no-log line.** The orange `note-card` box is replaced by `<p class=nolog>`. The looked-in counts and the regenerate hint move into the `How this was measured` details. That block always exists when there is no log, so the line's pointer to it never dangles. `--no-log` drops the "Looked in" text and the regenerate hint. The ledger card still renders under the second `if not L:`.
4. **Test-moments wording.** Every change = "Gate check plus only the tests that touch the change" (impacted only). Build complete = "Everything, once". Before every later deploy = "Everything". Module done now names the per-flow unit tests chosen at CAC-5, listed in the module brief. That matches the rule (impacted-only per change, full run at build complete and before each deploy, unit-test choice at CAC-5) and the `Unit flows:` brief list from #417 and its follow-up.
5. **Prose and hygiene.** No em or en dashes in added prose. The only one is the CHANGELOG attribution separator ` — field review of the #434 report`, which follows the existing entry convention. No private or client names: the fixtures use Orders, Billing and Kickoff. No conflict markers. The CHANGELOG entry sits under Unreleased, is accurate and is credited, and check-pr-discipline is clean.

## Tests run (detached worktree at 98649f9, removed afterwards)

| Test | Result |
|---|---|
| tests/wave2/test-progress-report-polish.sh (new) | 20 / 0 |
| tests/wave2/test-progress-report.sh | 101 / 0 |
| tests/wave2/test-progress-report-field-fixes.sh | 101 / 0 |
| tests/wave2/test-progress-report-gitignore.sh | 6 / 0 |
| tests/wave2/test-build-plan-json.sh | 70 / 0 |
| tests/wave2/test-build-plan-status-args.sh | 6 / 0 |
| bin/check-portability.sh, bin/check-pr-discipline.sh | clean |

The new test against the base scripts (project-bin from release/v1.0): 12 of 20 fail. So the test pins the new behaviour. The 8 that pass on base are the pre-existing parts: report written, Stage 5 card count, Phase 2 count, and the `--no-log` "never claims it looked" checks. Discovery is already off under `--no-log`, which makes the new `and not args.no_log` guard defensive only.

## Nice-to-haves

1. **"One plan count" holds for `done`, not for the rest of the vocabulary.** progress-report.py also reads `complete`, `verified`, `proven` and `passed` as built, and `partial`, `in progress`, `started` and `wip` as partial. build-plan-status.sh reads all of these as unknown. That includes `partial` and `blocked`, which brd-to-build-plan.md line 333 lists as legal State values. Probe: one plan with rows `complete (...)`, `**done** ...` and `Done` gives "3 of 3 rows built" on the card. The plan header shows "2 built, 1 with a State cell this page cannot read" and the phase is labelled "unknown". This disagreement predates the PR, but the PR body promises the card, header and badge agree. Follow-up: share one vocabulary, or at least add `partial`/`blocked` to the sh classifier as non-built. Alternatively, reword the CHANGELOG to say "done rows" only.
2. **Narrow the PENDING relabel to `states[s] == "pending"`.** Today the relabel also applies to a later stage that PROJECT.md lists as passed, when the gate page is not stale. This happens when the current stage moves back during a change round. A real conflict, "passed but the sign-off is still a placeholder", would then read PENDING. Adding the state check costs one condition.
3. **Test gaps for item 2.** Add one test where a non-placeholder FAIL on a later stage stays FAIL, and one where an unknown current stage (no stage line) keeps FAIL.
4. **Module done wording is a little narrow.** Unit tests can also be switched on per module (`Unit modules:` in PROJECT.md, `Unit tests: yes` in the brief) or per project (`Unit tests default: yes`). "listed in the module brief" covers the per-flow default only. A shorter alternative: "plus the unit tests the CAC-5 test strategy selects for the module".
5. **Reading order.** The calm line renders after the collapsed details block, so the pointer "details in How this was measured" points upward. Consider writing the line first.
6. **Pre-existing, outside this PR's scope.** `build-plan-status.sh` called by a relative path (`bash project-bin/build-plan-status.sh <proj> --html`) says "progress-report.py is not next to this script". GEN is `$(dirname "${BASH_SOURCE[0]}")/...`, which probably breaks after the script changes directory (cause not confirmed). An absolute path works.
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