Four instruments that were installed and not working, and the obligations to notice - #32
Conversation
workflow-structure-rules.md section 12 is a ten-row count with a denominator on every line, and it is the only place in the pipeline where a workflow's design is checked against the requirement it came from. Nothing required it to run. The row that fixes it is one line in obligations.tsv, and it is the first obligation below stage 5. That is the real finding: every existing row - look, sweep, journeys, coherence, skeleton - is from-stage 5, so this table, the only mechanism here for 'a judgement pass owes a mark', did not reach the design stages at all. obligation-check.sh ranks from-stage generically, so no code change was needed. Measured on a requirements-driven app replacement. The agent read section 11 because it had a construct question, filled section 12 row 10, and never ran rows 1-9. Row 5 - user tasks whose targeting mechanism is named, with the sentence it came from quoted - would have caught a build plan specifying a role XPath for four user tasks whose assignees are data on the record. Section 6 calls that 'not a near miss, it is a different application': one Approver role covered five populations, so every stage of every project would have landed in every approver's inbox. It was found at Stage 4 by a human asking whether the workflow skills were being used, with the plan already written. Deliberately not an optin row. An artifact a project must think of in order to owe it is an artifact nobody owes, which is the disease this table exists for. A workflow-free project discharges it in one line - '0 workflows, nothing to count' - exactly how section 12's own rows 2, 3, 4 and 9 are written. Proven to go red both ways against a real project rather than asserted: PASS with the numbers present, PENDING with the artifact removed, FAULT on a mark that carries no denominator. Second change, the cheap half. The architect stub cited workflow-structure-rules.md once and inlined nothing, against this repo's own authoring rule 2 - a citation is not a read. It now carries the ten rows, the artifact it writes, and the two that bite: row 5 with section 6's targeting warning, and row 10's construct denominator. Added outside the ROUTING markers so render-routing --check stays in sync. CI guards clean: 71/71 shell and 17/17 node parse, routing in sync, check-portability clean over 110 files, leak guard clean over 453. The one fixture referencing either changed file only deletes architect-agent.md to test the missing-agent path and never greps its content, so it was verified by inspection rather than run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
The second proof of the same finding as the workflow-count row on this branch:
obligations.tsv did not reach the design stages, so a design pass could be
skipped entirely and every gate stayed green.
WHAT HAPPENED (field run, 2026-09-09, a MOC/PSSR app replacement)
55 design tokens, sampled from the customer's own screenshots, were ported into
the project's theme module correctly and at the stage that owed them. The app
then rendered every screen in Atlas's default blue for two more phases of
build.
Three independent failures, none visible to anything in this toolkit:
1. The bridge assigned SCSS variables ($brand-primary: var(--brand)) to a
theme running $use-css-variables: true, so it compiled to nothing.
Measured in the built sheet: --brand present as the sampled red,
--brand-primary untouched at the Atlas default, var(--brand) appearing
0 times.
2. The 20 component classes were never ported at all. ds-muted sat on 12
live widgets and resolved to the body colour.
3. A theme module's :root is emitted BEFORE theme/web/custom-variables.scss,
so even what it did set lost the cascade.
mx check 0 errors. mxcli lint 0 errors. MDL suite green. Both e2e journeys
green. check-design-portability.sh had nothing to say — and its header opened
by asserting the premise that failed: "a design system's tokens port into
Mendix perfectly". Found by a human opening a screenshot and asking why the UI
was poor.
WHAT THIS ADDS
project-bin/check-design-reaches-app.sh reads the BUILT stylesheet, which is
the one thing nothing else here reads, and reports four passes with
denominators: framework knobs bound to a design token, tokens arrived, classes
arrived, dead SCSS bridges. Plus a WARN for classes the MODEL asks for that the
built sheet never defines.
It deliberately does NOT look for a known default hex. That is a cache of one
framework's one version, which CLAUDE.md's authoring rule 5 forbids and which
goes stale on the next Atlas release. The question is inverted instead: is the
winning definition of each knob the framework declares in its own
customization file pointed at a design-system token? A framework enumerates
its own knobs; that file is what it is for. Zero bound is not a style opinion,
it is a wire nobody connected.
It requires a build and exits 2 without one. "Did it reach the app" cannot be
answered from source, and answering it from source is how this shipped.
The obligation row is why this is not only a script. The check was never hard;
what was missing is that nobody was owed an answer, so every gate it passed
through was green-by-absence. design-reaches-app is mdl/project/from-stage 3
with a denominator, degradable — a project with no build yet discharges it by
saying "binding UNPROVEN, no build", and silence does not. It is owed by mdl
rather than review on purpose: the agent that performed the port shows it
landed.
check-design-portability.sh's falsified premise is corrected in place rather
than quietly deleted, with what it cost and a pointer to the new sibling. A
header that states a wrong assumption confidently is worse than no header.
ui-preflight-pages.md gains hard rule B0 at the consumer's seat: before the
first page of the project, prove the design system reached the app. The
ordering is the finding — the port happened when it was owed and the pages were
built on top of it unproven.
FIELD-PROOF
Ran against the real project, both states. Before: 0 of 35 knobs bound, 0 of 20
classes present. After: 29 of 35 bound, 43 of 43 tokens, 28 of 28 classes, exit
0. On its first field run the WARN pass found 17 of 23 class names carried by
live widgets that no stylesheet defines, ds-help among them — a real, new,
unreported defect on a project that had passed every other check.
The obligation was proven to go red both ways on that project: PASS with the
numbers, PENDING with the artifact moved away, FAULT on a mark carrying no
denominator.
tests/wave2/test-design-reaches-app.sh, 23 assertions, all passing. It pins the
state that shipped as a FAILURE, pins "tokens arrived" apart from "the design
system arrived", pins a theme-scoped binding as NOT a binding in both
directions (the instrument's first draft read "last occurrence in the file" and
reported a correctly bound knob as unbound, because the real Atlas sheet
declares --brand-primary inside :root.theme-neutral after the bare :root), and
pins "no build" at exit 2 rather than 0. Fixture provenance and the counts
measured on the real build are in fixtures/design-reaches-app/CAPTURE.md,
including exactly what was changed from the capture and why: the colour
literals only, because the real palette is a customer's and this repo is
public. No line reflowed, no block reordered, no selector altered.
Both layouts: every path resolves relative to the project root or is passed as
an argument, same contract as check-design-portability.sh. Both platforms:
POSIX shell, no Node, no mac-only tools; check-portability clean over 112 files.
CI guards: check-scripts 72/72 shell and 17/17 Node parse, render-routing
--check in sync (baseline 73,122 words against an 80,000 budget), leak guard
clean over 461 tracked files. Only the one new fixture was run, and it is
self-contained on temp directories it creates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
…e page body
It is routed baseline for mdl/gate/review and installed into every project, it
had no fixture, and until today it had never passed once on a project whose
wireframes are named after the source system's screens. Both defects are the
same shape as the design-system one on this branch: an instrument that was
installed and silently not working.
1. IT COULD NOT RESOLVE ITS OWN INPUT.
The wireframe path was hardcoded design/wireframes/<PageName>.html.
Wireframes drawn from a legacy system are named after that system's screens
(moc-project-detail.html) while the Mendix pages are named in Mendix
convention (MOCProject_Detail). Measured on a MOC/PSSR app replacement: 15
wireframes, 8 built pages, "no wireframe" reported for every page, exit 2.
Now an optional two-column design/wireframes/PAGE-MAP.tsv. Absent map,
behaviour is unchanged. A page the map does NOT name is still a miss and is
reported naming the map, so the map cannot hide a page by omission. A row
pointing at a wireframe that does not exist is reported too, because a stale
row is how a rename turns into a page nobody checks.
2. IT COULD NOT BOUND THE PAGE BODY.
"To the closing brace at column 0" reads the rest of the FILE for a page
whose body is on one line. A nav-shell script declaring five such stubs
reported "page MOCProject_Overview declares 5 H1 titles; its wireframe draws
one". Every number was the count for the whole file.
That is the worse kind of false positive: it fires on correct code, it cites
a real rule, and the fix it asks for would have broken four pages.
Two line-based repairs were tried and each broke the other case. A "started
on any brace" scan exits on a balanced one-line construct, so a multi-line
declaration carrying Params: { $X: Mod.Entity } ended the body before it
opened and a page with a correct RenderMode: H1 was reported as having none.
A "started only when depth > 0" scan never starts on a one-line body, where
the braces balance within the line. Only the paren state separates the two,
so the extractor counts characters and tracks parens.
FIELD-PROOF
Ran against the real project. Before: 8 violations across 22 scripts, of which
3 were false and 5 real. After: 5 pages inspected, 0 violations, with 4 pages
carrying an explicit per-line opt-out and a reason.
What it found once it could run: 9 title widgets across 4 page scripts using
Class: 'h1' instead of RenderMode: H1. That is the same defect the screenshots
showed — Class: 'h1' leaves a DYNAMICTEXT an inline <span> that Atlas merely
sizes, so the page title and its caption rendered on one line. The CSS rule
added earlier on this branch is a safety net; RenderMode is the fix.
tests/wave2/test-page-shell-body.sh, 18 assertions, all passing. It pins both
extractor cases in one file because that is the pair that has to hold at once,
and it pins that the rules still FIRE on a real Class-only title and on two H1s
in one body — a fixture that only proved the false positives were gone would
have let a dead check through.
CI guards: check-scripts 72/72 shell and 17/17 Node parse, check-portability
clean over 113 files, render-routing --check in sync, leak guard clean.
POSIX shell and awk only; no Node, no platform-specific tooling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
It writes a full copy of the .mpr and every mprcontents unit before each exec and keeps the five newest. Nothing ignored it, so a project commits the client's whole model several times over — and then mxcli exec refuses to run, because exec.sh reads the pruned snapshot as uncommitted model changes. The snapshotter's output blocking the snapshotter's own guard. Measured on a MOC/PSSR app replacement, 2026-09-09: 2,089 snapshot files tracked, and an exec that could not proceed until they were untracked by hand. Written here rather than in init-project.sh deliberately: here it also reaches every project that already exists, on its next exec, instead of only the ones scaffolded after today. A producer guarantees its own output is ignored. Guarded on the project actually being a git repo, appends only when the line is absent, and says on stderr when it adds it — a silent .gitignore edit is its own surprise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
workflow-structure-rules.md §11's own instruction is "do not trust the date on this table — check the binary". The table was probed on v0.20.0, this binary is v0.21.0, and one project's build plan already CLAIMED it had been checked against v0.21.0 without anyone running it. Running it found two facts that are in no skill, no bug log, and no mxcli syntax output. 1. A USER TASK's PAGE must take a System.WorkflowUserTask parameter, not the workflow's context entity. A context-parameterised page passes mxcli check --references completely clean, execs, and round-trips through DESCRIBE. The native build then says: "The selected page 'X' should accept a parameter of type 'WorkflowUserTask', but expects parameters of types 'MOCProject' instead." 2. A TARGETING MICROFLOW must accept TWO parameters, System.Workflow and the context entity, in that order. One parameter is the natural thing to write: "should accept parameters of type 'System.Workflow' and 'MOC.MOCProject'. Instead the selected microflow expects 'MOC.MOCProject'." Neither appears in mxcli syntax workflow.user-task.targeting, whose own example is TARGETING MICROFLOW HR.GetApprovers with no signature stated at all. The expensive half is the planning consequence, and it is now written in both skills: the task pages are a PREREQUISITE of the workflow row, not a later UI row. A plan that builds the workflow at row 41 and its task pages at row 45 cannot make row 41 green. WITH A KNOWN-BAD CONTROL, which is the only reason the pass results are worth anything. With the signatures corrected, four constructs a real approval chain needs all build clean on v0.21.0: user task with a targeting microflow, multi-user task, CALL MICROFLOW with a quoted WITH, and a backward JUMP TO inside an outcome. The same run also scripted a DECISION, which passed mxcli check, printed "Created workflow", and left the project unopenable — StorageLoadException, then BUILD FAILED. So BUG-76 is open on v0.21.0 and the probe demonstrably tells a working construct from a broken one. A probe with no known-bad control cannot distinguish "it works" from "the probe is blind". This repo has already published one wrong conclusion for exactly that reason: BUG-121, where a sequentially-built control only showed that a task looks like a task. Run on a throwaway copy of a real 181MB project, because BUG-76's recovery is DROP WORKFLOW and this spelling leaves the model unopenable. Guards: routing in sync, leak guard clean over 462 tracked files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
…uld not reach
Third instrument in this family with the same bug.
1. The page body was matched by a regex ending at a closing brace at COLUMN 0,
which finds nothing for an indented page. Measured on a MOC/PSSR app
replacement: two wizard pages wrapped in a LAYOUTGRID — which mxcli's own
MPR010 advises for any DataView holding inputs — close at two spaces of
indent, so the tool exited 2 with "no declaration of page X found in input"
for pages plainly there. A project that follows mxcli's advice could not
score any page.
Now the same character-counting, paren-tracking extractor check-page-shell.sh
got, for the same reason: a multi-line declaration's Params: { $X: Mod.Entity }
opens and closes a brace before the body does.
2. `.anno` is not reached by the existing `annot` pattern, because \b after
"anno" needs a non-word character and "t" is one. A project whose annotation
block is div.anno scored its own annotation as missing page content, reporting
"heading: Annotation" as the first miss on a page whose real H1 is correct.
Two faithful pages at 9%.
WHAT I DELIBERATELY DID NOT ADD, having first added and then reverted it: a
fourth content-boundary name. The project's wireframe generator emitted wf-wrap
as the screen itself with the annotation inside it, and wf-head for the page
header — which this tool's own "every wf-* div is annotation chrome" rule then
ate, so the page header disappeared and the score went to null%. Teaching the
tool a name that contradicts its own convention was the wrong half to change.
The generator now emits the .wf-wrap > .wf-screen shape this file already
documents, with the annotation as a SIBLING, and renames its page structure to
page-head / page-actions.
Both pages then score 100%, and the honest caveat is that the denominator is
thin for a reason worth knowing: that project inlines its whole ds.css into
every wireframe so they open standalone, which makes every design-system class
look "wireframe-local" and moves the button labels into the not-scored
bound-data set. The action dimension reads 0/0 there — unusable rather than
wrong, and better as 0/0 than as a number nobody can trust.
check-scripts 72/72 shell and 17/17 Node parse; leak guard clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
Two more commits landed after the description above was writtenThe body describes commits 1–4; the PR now carries 6. Rather than rewrite it, here are the two additions. Same theme: an instrument or a table that was trusted and not checked. Commit 5 — two workflow signatures
|
`.page-head` used once, subtree 0.513 of the page — the old rule (`uses === 1 && kept < 0.4`) classed it a bound-data mock and deleted the subtree, taking the page's only <h1> with it. Every dimension then reported 0 of 0, which normalizes to `null%`: a clean report over an empty corpus, on a page that scores 100% once it is not gutted. Same silent-null failure the file's own header note describes, arriving a second time through the other conjunct. The threshold is removed rather than retuned. It was a guess, and any value for it splits once-used wrappers on a measurement unrelated to whether their content is bound data. A repeated region is still a mock at any size. New fixture, 14 assertions, two captured wireframe shapes, CAPTURE.md stating that only literal text and class prefixes changed. The first assertion is that the score is not null, not that it is high. Known-bad control carrying the old rule: 8 of 14 fail. Field run: a MOC/PSSR app replacement's grouped project overview, 2026-09-09 — null% before, 100% after, page script unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M
…k, both task-page params, fixtures take relative paths - snapshot-mpr.sh: the .gitignore append can no longer abort the snapshot (it ran under set -e before the snapshot, and exec.sh calls it bare). - ui-preflight-pages.md B0 names docs/design-reaches-app.md as the mark that discharges the design-reaches-app obligation — a consumer with no producer. - workflow-structure-rules.md / learned-workflow-patterns.md: the task page takes $WorkflowUserTask IN ADDITION to $WorkflowContext (§4 rule 1, CE7412); the new wording read as task-only and contradicted the same file. - The three new fixtures absolutise $1 so the documented relative form works. - check-page-shell.sh / page-fidelity.js: header note that the brace counter does not skip quoted strings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
Direct lane (changes to
skills/,bug-logs/,bin/,project-bin/,project-tests/, pipelines)Four commits, one finding, found four times in two days. Each is an instrument or a pass that was installed, routed, believed in, and silently doing nothing — while
mx check,mxcli lint, the MDL suite and the e2e journeys were all green. Three of the four were found by a human looking at the app, not by the harness.The commits are independent and any can be dropped. If time is short, read commit 2.
obligations.tsvdid not reach the design stages, so a Stage-3 pass owed no markcheck-page-shell.shcould not resolve its own input, and could not bound a page bodysnapshot-mpr.shdid not gitignore its own outputexecthat refused to run because the snapshotter's output looked like uncommitted model changesmxcli execblocking itselfCommits 3 and 4 were found while building on top of commit 2's fix, which is the honest reason they are in the same PR: once you start opening the instruments, they keep being empty.
Commit 2 —
check-design-reaches-app.sh: did any of the design system ARRIVE in the built app?What changed and why (one paragraph):
The gap this closes is a sentence in its own sibling.
project-bin/check-design-portability.sh's header opened by asserting "a design system's tokens port into Mendix perfectly — colours, radii and spacing variables survive the SCSS port intact. Its rules may not." The first half is false, and it was the sentence that made everyone comfortable. Tokens survive the port into a file; nothing about landing in the file makes the framework read them. On a real run, 55 tokens sampled from the customer's own screenshots were ported correctly, into the right file, at the stage that owed them — and the app rendered every screen in Atlas's default blue for two more build phases. New instrumentproject-bin/check-design-reaches-app.shreads the BUILT stylesheet, which is the one artifact nothing else in this repo reads, and reports four passes with denominators: framework knobs bound to a design token · tokens arrived · classes arrived · dead SCSS bridges, plus a WARN for classes the model asks for that the built sheet never defines. New obligation rowdesign-reaches-app(mdl/project/from-stage 3,denominator yes, degradable) makes someone owe the answer, because the check was never the hard part — nobody was owed an answer, so every gate it passed through was green-by-absence.check-design-portability.sh's falsified premise is corrected in place, with what it cost and a pointer to the new sibling; a header that states a wrong assumption confidently is worse than no header.ui-preflight-pages.mdgains hard rule B0 at the consumer's seat — before the first page of the project, prove the design system reached the app — because the ordering is the finding.Why it does not check for a known default colour. The tempting check is "is
--brand-primarystill#264ae5". That is a cache of one framework's one version — the defectCLAUDE.md's authoring rule 5 forbids — and it goes stale on the next Atlas release. So the question is inverted and then needs no table at all: is the winning definition of each knob the framework declares in its own customization file pointed at a design-system token? A framework enumerates its own knobs; that file is what it is for. Zero bound is not a style opinion, it is a wire nobody connected.It requires a build and exits
2without one. "Did it reach the app" cannot be answered from source, and answering it from source is exactly how this shipped.Field evidence — which real project this ran against, and what it measured there (CLAUDE.md → "Shipping an instrument", rule 4):
A MOC/PSSR app replacement (requirements-driven, single module, Mendix 11.14, Atlas 3 with
$use-css-variables: true), 2026-09-09.Three independent failures, none visible to anything in this toolkit:
$brand-primary: var(--brand). This Atlas reads CSS custom properties and never reads that SCSS variable, so the assignment compiled to nothing. Measured indeployment/web/theme.compiled.css:--brandpresent as the sampled red ·--brand-primaryuntouched at the Atlas default ·var(--brand)appearing 0 times.ds-mutedsat on 12 live widgets and resolved to the body colour, so every caption on every page rendered at full ink.:rootis emitted beforetheme/web/custom-variables.scssin the compiled sheet, so a knob override written in the module could never win. The mapping has to live in the framework's own customization file.What was green the whole time:
mx check0 errors ·mxcli lint0 errors · 7 of 7 MDL tests, 16 assertions · two e2e journeys, 3/3 and 4/4 ·check-design-portability.shnothing to say. Every one of those reads the model or the authored stylesheet, and both defects live between the authored stylesheet and the built one. It was found by a human opening a screenshot and asking why the UI was poor.Measured before and after, same project, same instrument:
var(--brand)occurrences in the built sheetThe 6 knobs still unbound are Atlas defaults this design system deliberately does not claim. Unclaimed is not unbound, and inventing an opinion to make a number go green would be worse than the number.
It found a real, new, unreported defect on its first field run. The WARN pass reported 17 of 23 class names carried by live widgets that no stylesheet defines —
aggregate-banner,banner-label,banner-value,card-name,card-row,card-who,chip,chip-labeland nine more, plusds-help. Class names typed from memory instead of read out ofds.css, on a project that had passed every other check. Each is now on the record with its denominator in that project'sdocs/design-reaches-app.md, promoted intods.cssor removed at its own build row.The obligation was proven to go red both ways, not asserted. Against the real project: PASS with the numbers · PENDING with the artifact moved away (
0 of 1 discharged; NOT DONE: project — owner: mdl-agent) · FAULT on a mark carrying no denominator (a pass that cannot say what it covered has not covered it).tests/wave2/fixtures/design-reaches-app/reproduces the structure of a real mxbuild 11.14.0theme.compiled.css, andCAPTURE.mdstates exactly what was changed from the capture and why: the colour literals only, because the real palette is a customer's and this repository is public. No line reflowed, no block reordered, no selector altered, no structure simplified. It also lists the counts measured on the real build. Every structural fact the parser depends on is verbatim, because each is a fact nobody would have imagined —:root {alone on its line; a bare:rootand a scoped:root.theme-neutralboth declaring--brand-primary, with the scoped one appearing later in the file; shades derived at runtime viacolor-mix(in srgb, var(--brand-primary), …), which is why binding the one knob suffices and why a hardcoded-default check is the wrong check; comment lines inside the:rootblock.--brand-primarysits inside:root.theme-neutral, a theme the app is not wearing.built-scoped-only.csspins that a theme-scoped binding is not a binding; thefixedcase pins that the bare-:rootbinding is. An instrument that miscounts its own denominator teaches people to ignore it.tests/wave2/test-design-reaches-app.sh— 23 assertions, all passing. It pins the state that shipped as a failure (if that fixture ever passes, the instrument has stopped instrumenting); pins "tokens arrived" apart from "the design system arrived", because that is precisely the distinction the old header collapsed; pins no build → exit 2, never 0; and pins the dead-SCSS-bridge check as firing only where the theme runs on custom properties — the same line is the correct bridge where it does not, and reporting it there is how a check earns being switched off.check-design-portability.sh;CUSTOM_VARSis overridable for a project that renamed the framework file. Both platforms: POSIX shell, no Node, no mac-only tools.deployment/web/theme.compiled.css, produced by the mandatory build step, and the instrument says so and exits 2 when it is absent.architect,mdl,gate,reviewat stages 3 and 5, and added toMXTK_PROJECT_BINso it installs into projects. Trigger-phrased per authoring rule 1: "after the FIRST build that follows any design-system port, and before any page is built on it."Honest limits.
check-design-reaches-app.shandcheck-design-portability.shremain two scripts, deliberately. They read different artifacts and answer different questions — can the rules match the DOM versus did any of it arrive — and merging them would give the merged thing a build dependency the linting half does not need.0 of Nfails. I have one project's data on what a healthy ratio looks like (29 of 35), which is not enough to set a threshold, and a threshold guessed from one project is how a check starts crying wolf.$use-css-variablesprobe is an Atlas fact. It is confined to pass 4, skipped when the flag is absent, and the flag's absence is reported asunknownrather than assumed either way.Commit 3 —
check-page-shell.shcould not find its input, and could not bound the page bodyIt is routed baseline for
mdl/gate/review, installed into every project, and it had no fixture at all — which is why both defects below sat in it.1. It could not resolve its own input. The wireframe path was hardcoded
design/wireframes/<PageName>.html. Wireframes drawn from a legacy system are named after that system's screens (moc-project-detail.html) while the Mendix pages are named in Mendix convention (MOCProject_Detail). Measured on the same project: 15 wireframes, 8 built pages, "no wireframe" reported for every page, exit 2. It had never passed once.Now an optional two-column
design/wireframes/PAGE-MAP.tsv. Absent map, behaviour is unchanged. A page the map does not name is still a miss and is reported naming the map, so the map cannot hide a page by omission. A row pointing at a wireframe that does not exist is reported too, because a stale row is how a rename turns into a page nobody checks.2. It could not bound the page body. "To the closing brace at column 0" reads the rest of the file for a page whose body is on one line. A nav-shell script declaring five such stubs reported "page MOCProject_Overview declares 5 H1 titles; its wireframe draws one". Every number was the count for the whole file.
That is the worse kind of false positive: it fires on correct code, it cites a real rule, and the fix it asks for would have deleted four working pages' titles.
Two line-based repairs were tried and each broke the other case. A "started on any brace" scan exits on a balanced one-line construct, so a multi-line declaration carrying
Params: { $X: Mod.Entity }ended the body before it opened and a page with a correctRenderMode: H1was reported as having none. A "started only when depth > 0" scan never starts on a one-line body, where the braces balance within the line. Only the paren state separates the two, so the extractor now counts characters and tracks parens.What it found once it could run: 9 title widgets across 4 page scripts using
Class: 'h1'instead ofRenderMode: H1. That is the same defect the screenshots showed —Class: 'h1'leaves aDYNAMICTEXTan inline<span>that Atlas merely sizes, so the page title and its caption rendered on one line, the caption starting mid-word against the heading. All nine retrofitted on the project; both journeys green after.tests/wave2/test-page-shell-body.sh— 18 assertions, all passing. Both extractor cases are pinned in one file, because that is the pair that has to hold at once. It also pins that the rules still FIRE on a realClass: 'h1'title and on two H1s in one body — a fixture that only proved the false positives were gone would have let a dead check through. And it pins the "inspected nothing" exit 2 as not a pass.Commit 4 —
snapshot-mpr.shgitignores its own outputIt writes a full copy of the
.mprand everymprcontentsunit before each exec and keeps the five newest. Nothing ignored it.So a project commits the client's whole model several times over — and then
mxcli execrefuses to run, becauseexec.shreads the pruned snapshot as uncommitted model changes. The snapshotter's output blocking the snapshotter's own guard. Measured: 2,089 snapshot files tracked, and an exec that could not proceed until they were untracked by hand.Written in the snapshotter rather than in
init-project.shdeliberately: there it also reaches every project that already exists, on its next exec, instead of only the ones scaffolded after today. A producer guarantees its own output is ignored. Guarded on the project actually being a git repo, appends only when the line is absent, and says on stderr when it adds it — a silent.gitignoreedit is its own surprise.Commit 1 —
workflow-count: the first obligation below stage 5One row in
obligations.tsv, and the row's own novelty is the finding: it is the first obligation below stage 5. Every existing row —look,sweep,journeys,coherence,skeleton— isfrom-stage 5, so this table, the only mechanism in the repo for "a judgement pass owes a mark", did not reach the design stages at all.workflow-structure-rules.md§12 is a ten-row count with a denominator on every line, and it is the only place in the pipeline where a workflow's design is checked against the requirement it came from — and it fired only if someone happened to run it. The table's own header already names this disease: "PROSE HAS NO EXIT CODE. A skill that is never invoked leaves no mark, and a missing mark reads as a clean one." The new row isworkflow-count,architect,projectscope,denominator yes,from-stage 3, artifactarchitecture/workflow-count.md;obligation-check.shranksfrom-stagegenerically, so no code change was needed. It is deliberately not anoptinrow: an artifact a project must think of in order to owe it is an artifact nobody owes, which is the disease the table was built for — and a workflow-free project discharges it in one line, "0 workflows, nothing to count", exactly how §12's own rows 2/3/4/9 are written. The second, cheap half:agents/architect-agent.mdcitedworkflow-structure-rules.mdonce and inlined nothing, against this repo's own authoring rule 2 ("a skill only works when its text is in the acting agent's context — a citation is not a read"). The agent that owns Stage 3 now carries the ten rows, the artifact it writes, and the two that actually bite — row 5 (targeting mechanism named with the sentence it came from quoted, plus §6's warning that a role XPath over a nominated assignee delivers the task to everyone holding that role) and row 10 (N constructs in the diagram, N rows in the plan). Added outside theROUTING:BEGIN/ENDmarkers sorender-routing.sh --checkstays in sync.Field evidence: a no-code app replacement (requirements-driven, single module, three workflow definitions), 2026-09-08.
workflow-structure-rules.mdwith a construct question ("can MDL write a parallel split?"), took §11, filled §12 row 10 only, and never ran rows 1–9. Its build plan then specified "targeting XPath on all four tasks ([%UserRole_X%])" — for four user tasks whose assignees are data on the record. §6 is explicit that this is "not a near miss, it is a different application", and this project had deliberately collapsed five approver populations into oneApproverrole, so a role XPath would have put every stage of every project in every approver's inbox. It was caught at Stage 4, with the plan written, by a human asking whether the workflow skills were actually being used — not by any gate. Corrected to §6's preferred shape (one shared targeting microflow plus four thin wrappers, fallback chain nominee → eligibility → backstop withLOG WARNING), which also satisfies §12 row 6 structurally.$WorkflowContext. Four of the ten rows are legitimately0 of 0and say so.obligations.tsvis read from the toolkit at gate-check time rather than copied into projects. The inlined §12 reaches new projects and projects whose architect stub is still an untouched stub —sync-project.shcorrectly keeps a completed agent file, so an existing project with a finished architect agent gets the mechanical catch but not the inline text. That is the accepted install contract, not a defect, but it is worth knowing which half travels.process/preflight-skill-baseline-2026-08-31.mdmeasured exactly that trap: the "failing" skill scored zero violations when actually read, and a blind rewrite would have churned proven text.modularize-domain.md's five boundary criteria,architecture-blueprint.mdStep 4's Buy/Build verdicts, anddesign-artifacts.mdStep 3b's scope crosscheck. On that project two happened to be run properly and §12 happened not to be; that difference was luck, not process. Commits 2, 3 and 4 are what happened to three more of them over the next two days.Repo-wide checks
bin/check-no-client-data.shclean over 462 tracked files; the project appears only as "a MOC/PSSR app replacement", and the fixture's colour literals are placeholders, stated as such inCAPTURE.md.bin/check-scripts.sh— 72/72 shell scripts and 17/17 Node instruments parse.bin/render-routing.sh --check— surfaces in sync, every skill routed or exempted; baseline 73,342 words against an 80,000 budget.bin/check-portability.sh— clean over 113 files. POSIX shell and awk throughout; no Node, no mac-only tooling, no new platform assumption.CHANGELOG.mdline per change, crediting the source project.## BUG-DRAFT-<slug>:— n/a, no bug entries.🤖 Generated with Claude Code
https://claude.ai/code/session_01Jj5uvTmmcBQT8Uu3asm21M