Skip to content

fix(findings): recover 13 lost pattern pages; close the digest's blind spot - #364

Merged
ako merged 7 commits into
mainfrom
docs/bug-patterns-recover
Sep 1, 2026
Merged

fix(findings): recover 13 lost pattern pages; close the digest's blind spot#364
ako merged 7 commits into
mainfrom
docs/bug-patterns-recover

Conversation

@ako

@ako ako commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Found by running make digest-status on main after the stack merged. Two problems.

13 pattern pages never reached main

All six PRs report MERGED, and main has 12 pages where it should have 25. GitHub was telling the truth: the stacked PRs each merged into their base branch, which by then was no longer on the path to main. Only #356 targeted main directly.

docs/bug-patterns-executor   17 pages
docs/bug-patterns-cmd-mxcli  20 pages
docs/bug-patterns-backend    23 pages
docs/bug-patterns-grammar    25 pages
docs/bug-patterns-visitor    25 pages
main                         12 pages   <-

This branch is docs/bug-patterns-visitor (all 25) merged with current main. Verified: 25 pages, 883 findings, every sources: path exists, every [[wiki-link]] resolves.

That's a process lesson, not a code one: a stacked chain has to be merged bottom-up with each PR retargeted to main as its base lands, or it merges into itself and reports success. Worth avoiding stacks of five next time.

The digest report said 0% outstanding while 249 findings were undated

Undated records were skipped by the "since last sync" comparison entirely, so an area could go completely undigested and the headline would still read current. That is the exact failure the report exists to prevent, occurring in the report.

  • The 249 undated findings are backfilled from git blame on the shards — mdl/executor had gained 250 records from other sessions since my pass.
  • An undated finding now counts as not digested rather than being skipped, and the headline says "not yet digested" rather than "added since", which is what it always meant.
  • date is now required by check-findings. A warning was not an option: 249 records accumulated undated, and nobody saw the warning that was not there.

Two limits documented rather than engineered away

Both make the number look better than reality, so they belong in the script header where the next reader will see them:

  • date is day-resolution and the comparison is strictly greater-than, so findings appended on a sync's own day count as digested. Measured: 256 mdl/executor findings carry the sync's date, ~250 of which landed after the pages were written.
  • The blame-based backfill invalidated the blame it read — every line's blame is now this commit. The date field is the only record from here on, which is why it is required rather than recovered.

Read the per-area in a page column as the actionable signal and the counts as a hint.

🤖 Generated with Claude Code

ako and others added 7 commits September 1, 2026 07:40
87 of the 95 cmd/mxcli findings (92%) now belong to a named class. This
is the tooling surface rather than the model, and the classes look
nothing like the executor's.

- test-runner-cannot-fail (14). The one class whose cost is confidence
  rather than debugging time. `@expect 1 = 2` passed; `@verify` was
  parsed and read by nothing but --list; --require-assertions exited 0
  on a suite that asserted nothing. The mechanism is silent ABSENCE —
  an annotation parsed into a field no consumer reads — amplified by
  two result-assembly paths. The remedies that held are structural:
  fail closed on an annotation that cannot be honoured, one constructor
  and one pre-run verdict function, each pinned to a single call site.

- local-loop-silence (16). The warm loop orchestrates mxbuild, a JVM, a
  bundler, PostgreSQL and a browser, so the thing that breaks is not
  the thing that reports: a missing client bundle answers HTTP 200, a
  dead runtime leaves the CLI spinning, and the log being captured is
  not the log the user needs.

- styling-compiles-to-nothing (11). Nothing validates CSS. Location
  decides whether SCSS compiles at all, an unrecognised token is
  indistinguishable from a design never applied, and contrast is a
  correctness property no check measures.

- package-operations-damage (11). Handing the project to tools mxcli
  does not control, and being told it went fine — the MPR v2 to v1
  collapse reporting 0 errors is the worst of them.

- cli-contract-defects (14). The class with no Mendix document in it.
  It matters disproportionately because agents take the tool's word:
  help that teaches unparseable syntax, an unqualified "Check passed!"
  that resolved nothing, a flag that parses and does nothing.

Every sources: path and [[wiki-link]] verified before committing. Seed
table extended and five rows appended to SYNC_LOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 of the 83 mdl/backend findings now fall under a named class. Only
three pages are new: most of the area reuses classes written for
mdl/executor and cmd/mxcli, which is the digest working rather than a
shortcut.

- engine-divergence (30). The dominant class here. Two implementations
  behind one interface, with the newer, less complete one as the
  default — so a gap is the behaviour most users get while tests formed
  against legacy still pass. A gap on one engine is invisible from
  inside that engine: everything is self-consistent, and the field that
  never existed is never missed. The cross-engine DESCRIBE matrix
  (write with A, read with B, all four) is the only check that sees it.
  Two failure modes and only one is honest — a refusal naming
  MXCLI_ENGINE=legacy costs a flag, a `-- Empty action` placeholder
  makes describe→exec delete the construct. The worst instance was a
  read that under-reported page access.

- mutator-addressing (19). ALTER edits a stored document in place,
  which means naming a node — and DataGrid2 columns, object-list items
  and layout regions store no Name at all. Derived names are unstable,
  ambiguity is refused rather than resolved, and hand-built BSON drifts
  from codec-built BSON in ways only a dump-diff shows. Getting a
  typed-array marker wrong turns a silent no-op into an unopenable
  project.

- access-rule-reconciliation (6). Small and graded high: GRANT looks
  additive and is a read-modify-write, so both widening and narrowing
  report success. Records two method points — the writer was innocent
  and a later reconcile removed the rules, and the reported trigger
  (WHERE) was a red herring that a scoped fix would have satisfied.

The ~33 MCP/PED findings are deliberately NOT given a page: that
subsystem already has architecture/mcp-backend.md and
models/ped-mutation-constraints.md, and restating them would break the
never-restate rule. Recorded as its own SYNC_LOG row so the omission
reads as a decision rather than an oversight.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
49 of the 53 mdl/grammar findings (92%) now fall under a named class.
Three pages are new; the rest reuse describe-round-trip-gaps and
silent-property-drop.

- capability-gap-as-parse-error (36 touched, ~20 primary). The
  distinctive one. Someone tries to express something Mendix supports,
  the parser says `no viable alternative`, and they conclude the FEATURE
  is impossible — a parse error is indistinguishable from a mistake they
  made. So the reports in this class are not feature requests, they are
  workarounds: an admin screen hand-rolled as five pages because a tab
  container was believed not to exist, "use a Java action" for a binary
  upload, "go to Studio Pro" as the standing answer to translation, a
  headless pipeline ending in a manual step.

  The gap is bidirectional — DESCRIBE of a document that HAS the
  construct must do something, and in the mapping findings it emitted
  MDL that parsed and rebuilt a different document, which is worse than
  a parse error. Also records that a narrow/wide statement pair (SET vs
  REPLACE) is a whitelist extended one bug report at a time, and that
  "not in the metamodel" is not a conclusion until the namespace is
  right.

- keyword-collisions (22 touched, ~8 primary). The distinguishing
  question is how a collision fails, not whether one happens: a parse
  error is recoverable, a different valid parse is not — a widget
  conditional calling trim() was silently dropped. Records that a
  grammar alternative and its visitor case are ONE change (accepting -7
  without the AST case serialized `[Amount > ]`), and the control-binary
  sweep for proving a relaxation causes no regressions.

- scripts-that-cannot-rerun (5). Statement-level idempotence, kept
  deliberately apart from ADR-0008's write-level idempotence, which is
  what people usually mean when they say mxcli is idempotent. `exec`
  halting on the first error turns "90% already applied" into "none of
  the remaining 10% applied", and the silent variant — a duplicated
  index — is worse than the error.

Coverage stated in CLAUDE.md was corrected to the measured 92% before
committing; the first draft claimed 98% from memory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
27 of the 28 mdl/visitor findings (96%). Two pages are new and one is a
re-sync — the first any page has had since the initial synthesis on
2026-05-24.

- expression-translation-drift (13). Deliberately distinct from
  platform-semantics-gaps: there the MDL is illegal Mendix, here the MDL
  is correct, the emitted Mendix expression is well-formed, and it says
  something else. The worst instance is the quietest — an additive chain
  rebuilt with its operators reordered, so a microflow computed a
  different number than its source said with mxcli check, mx check and
  the build all green, and the corruption in the stored document rather
  than in DESCRIBE.

  Three ways meaning is lost (a literal changes type, an operator
  changes, a function resolves to the wrong overload), plus the ANTLR
  trap behind the standard fix: GetText() excludes hidden tokens and a
  source-interval slice includes them, so preserving raw source inherits
  every comment in that span.

- misleading-diagnostics (6). Graded above "the message could be
  clearer" because a wrong hint costs however long the reader spends
  acting on it: they blamed their quoting, renamed an attribute that was
  fine, or concluded a construct was unsupported. A hint's precision
  matters more than its coverage — the "unescaped apostrophe" hint
  matched any short lowercase word and so fired on every genuine error
  at `on`, `in`, `as`, `to`, `by`.

- visitor-wiring-gaps — RE-SYNCED. It described one size of gap (a
  field); the findings show three. A field, a structure (ELSIF arms,
  which Mendix has no native form for and which must be lowered into
  nested ifs), and a whole statement that parses, exits 0 and dispatches
  to nothing — the last presenting as an empty result rather than a
  missing feature, which is what lets it survive. Added the neighbouring
  failure where a field is wired to the WRONG thing, which reports
  success and changes meaning rather than losing it.

Every sources: path and every [[wiki-link]] across all 25 pages verified
to resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d spot

Two problems, found by running `make digest-status` on main after the
stack merged.

**13 pattern pages never reached main.** All six PRs report MERGED and
only #356's content is on main: the stacked PRs each merged into their
BASE BRANCH, which by then was no longer on the path to main. GitHub was
telling the truth — they merged into what they targeted. This branch is
docs/bug-patterns-visitor, which holds all 25 pages, merged with current
main. Verified: 25 pages, 883 findings, every sources: path exists and
every [[wiki-link]] resolves.

**The digest report said 0% outstanding while 249 findings were
undated.** Undated records were skipped by the "since last sync"
comparison entirely, so an area could go completely undigested and the
headline would still read current — the exact failure the report exists
to prevent, in the report itself.

Three changes:

- the 249 undated findings are backfilled from git blame on the shards
  (mdl/executor gained 250 records from other sessions since the last
  pass);
- an undated finding now COUNTS AS NOT DIGESTED rather than being
  skipped, and the headline says "not yet digested" rather than "added
  since", which is what it always meant;
- `date` is now required by check-findings. Warning was not an option:
  249 records accumulated undated, and nobody saw the warning that was
  not there.

Two limits are documented in the script header rather than engineered
away, because both make the number look better than reality: `date` is
day-resolution and the comparison is strictly greater-than, so 256
mdl/executor findings carrying the sync's own date read as digested when
~250 arrived after the pages were written; and the blame-based backfill
invalidated the blame it read, so the field is now the only record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ako
ako merged commit 342083d into main Sep 1, 2026
13 of 14 checks passed
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