Skip to content

Commit dffaf6f

Browse files
committed
Merge #307: graph-sweep/app-facts edge guard; per-BRD conformance ledgers (closes #259 #260; #313 follow-up)
Review: MERGE (two fix rounds + confirmation) — no-call models sweep again; per-BRD reports read by review/verify; BRD ids with spaces; verify stack rung exit 4 = FAULT.
2 parents 7ff741a + 8fc7c6a commit dffaf6f

13 files changed

Lines changed: 902 additions & 68 deletions

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.
2626
- fix(obligations): **Each `PROOF-OF-LOOK` line is checked against the `VALID AT` stamp of its own section, and the report's newest stamp is chosen by commit time ([#155](https://github.com/MendixMau/mxcli-project-toolkit/issues/155)).** A review file that keeps dated addenda was still FAULTing: every proof line was compared with the single newest stamp, so the first section's honest screenshots read "older than VALID AT". The field project's only way back to green was to re-shoot every page and delete its older proof lines. A citation now belongs to the nearest stamp above it (one above every stamp belongs to the headline's), on pretty-printed and one-line HTML alike. "Newest" was last-in-file, so an addendum written above the original stamp went STALE; it is now the stamp with the newest commit time. If any stamp no longer resolves (squash, rebase, shallow clone), the last stamp in the file is used, as before, and staleness reads as unmeasured. A stale shot inside a section still FAULTs. Motivated by the field report in #155 (a requirements-driven build, 2026-09-26). The checker is proven by fixtures only (`test-obligation-valid-at.sh` T3b, T4-T8; 3 fail on master); no field run against a real report yet — MendixMau
2727
- fix(obligations): **The final LOOK is held to the per-module LOOK's proof rules, and is not owed before Stage 6.** Found in an independent review of #303. `final-look` (from-stage 6) read PENDING on a project still at Stage P, because the from-stage column was only compared with `--adopt`. It now reads `NOT YET DUE` while gate-check's own derived `## Current stage` readout (the line carrying "(derived by gate-check") is below 6. A hand-written or scaffold stage line counts as unreadable, so the row stays owed; hand-written lines disagreed with the gates in 8 of 12 field projects. Every other obligation keeps its verdict. `module-review.md` § "The final LOOK" requires `PROOF-OF-LOOK` lines, but the check ran only for `look`; it now runs for `final-look` too. An unstamped final-look report could never go stale and so passed forever; it is now FAULT ("NO VALID AT"). Fixture `test-obligation-final-look.sh` T4-T11 (3 fail on master). — MendixMau
2828
- learn(brd-to-build-plan): **No `LOOK` row kind; the paragraph next to the Kind list says why ([#68](https://github.com/MendixMau/mxcli-project-toolkit/issues/68), [#188](https://github.com/MendixMau/mxcli-project-toolkit/issues/188) item 6).** A look is a `HARNESS` row naming the LOOK pass. Whether it happened is read from the `look` and `fidelity` obligations and the look-ledger, never from a State cell a person can set by hand. A second record could say "looked at" while the gate says nobody did. — MendixMau
29+
- fix(graph-sweep, app-facts): **the catalog guard checks the refs table, not the `build_mode` label; mxcli v0.24+ labels a complete catalog `source` and both scripts refused every one** ([#259](https://github.com/MendixMau/mxcli-project-toolkit/issues/259)). Field report: four module reviews recorded the sweep as "Instrument FAULT" on a fresh catalog with 5,368 refs rows. Now only an empty or kind-less refs table FAULTs, naming the mode and the per-kind edge counts; a model with no microflow-to-microflow calls (reached only from pages and buttons) still sweeps, and graph-sweep warns when no orphan-read kind is present. `manifest.json` records the real mode. `tests/wave2/test-catalog-edge-guard.sh` (20 checks, both layouts) fails 16 on master's scripts, including the verbatim FAULT line. Synthetic catalogs; a field run on a v0.24 project is still owed. — AlvinnYeo
30+
- fix(conformance-check, review-module, verify-module, coverage-ledger): **per-BRD ledgers (`<Module>/coverage-ledger/<BRD>.md`) are all measured, one report per BRD, review/verify read every report of the run and accept a directory-only ledger, and an index file is named as one instead of "0 measurable rows"** ([#260](https://github.com/MendixMau/mxcli-project-toolkit/issues/260)). Field report: every module with an index ended in `FAULT: 0 measurable rows extracted from 1 ledger(s).`, while `LEDGER_FILE` on one per-BRD file measured 128 rows. `LEDGER_FILE` (file or directory) is in the usage text. **Upgrade notes:** (a) a `LEDGER_FILE` that does not exist now FAULTs instead of silently falling back to another ledger, and `verify-module.sh` reads `LEDGER_FILE` and passes it on to conformance through the environment, so a stale export now FAULTs the conformance rung; (b) baseline pointers of per-BRD rows are re-keyed `<BRD>:<pointer>`, so a project that had both a filled index and a per-BRD directory sees its old rows go unmatched and the new ones re-seeded as "newly baselined" on the first run after upgrade, with no regressions reported: run conformance once, then re-check. Per-BRD reports reach `docs/report.json` via #310, whose `report-normalize.js` filter is extended there (it reads only `report-<date>.tsv` today). Fixtures: `test-conformance-per-brd-ledger.sh` (19, both layouts), `test-review-conformance-per-brd.sh` (6, both layouts), `test-verify-ledger-hint.sh` (4), all failing on master. A field run on a per-BRD project is still owed. — AlvinnYeo
31+
- fix(verify-module): **a stack rung exit 4 from `test-stack-up.sh` (port held by another process) is an instrument FAULT, not a FINDING** — follow-up to [#313](https://github.com/MendixMau/mxcli-project-toolkit/pull/313), whose header says to record it as blocked, not as a test failure; review of #313 found verify-module grading it as findings. Scoped to the stack rung (exit 4 is NOT APPLICABLE from conformance-check). `tests/wave2/test-verify-stack-blocked.sh` (6) fails 2 on master. — AlvinnYeo (port-holder report), review of #313
2932
- learn(testing-shape, test-result-audit): **three test-strength rules, each with a denominator: refusals probed on the server, every undeclared 4xx a failure, one clean-database final run** ([#172](https://github.com/MendixMau/mxcli-project-toolkit/issues/172)). A field comparison of two builds against one frozen spec found the toolkit build checked permission refusals only in the client (a hidden menu item), logged unexpected 400/422 responses as information, and reached 20/20 by stitching three runs. `testing-shape.md` §7 gains three report lines (`N of N restricted pairs probed server-side`, `undeclared 4xx/5xx: 0 of M requests — each fails its step`, `one clean-DB run <id>: N of N scenarios incl. timers, not stitched`); `test-result-audit.md` gains check 2e, which carries the reasons and the field evidence and asks for those numbers. Both baseline skills stay inside the 80,000-word baseline budget (79,997): the reasons live in the on-demand audit skill, and two redundant sentences in `testing-shape.md` §4's verdict paragraphs were folded (the empty-set corollary is stated in the Trace row, `harness-architecture.md` and `report-schema.md`). The backing inbox note is trimmed to its unpromoted "Keep" items. — MendixMau
3033
- learn(learned-detection-gaps, module-review): **two UI defects every model gate passes: a lazy-loaded combo box filter hides its preset, and a theme that pins row heights by position lets the next row cover an inserted widget** ([#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299)). Both passed check, mxbuild, lint and the scripted checks on a 127-module production app and were found only in the running app. Two register rows with the first oracle and the pre-build check (pluggable properties that change *when* data loads; grep the theme for fixed `height` on positional row classes before an insert), and two LOOK lines in `module-review.md` §4c with denominators (`N of N filters show their preset on first load`, `N of N widgets inserted into an existing container fully visible`). — jopterhorst
3134
- fix(app-restart, test-stack-up): **a port held by another process before `run --local` is named and reported as "instrument blocked" (exit 4), never killed** ([#262](https://github.com/MendixMau/mxcli-project-toolkit/issues/262)). Studio Pro had started its own runtime on the app/admin ports a restart needed; `mxcli run --local` then failed with "Authentication failed" (the admin API answering was Studio Pro's), the next journey ran against Studio Pro's runtime, and four more runs were NOT RUN. `app-restart.sh` now checks the app port and the admin port (record's `adminPort`, else app + 10) before stopping anything — a holder outside the recorded process tree exits 4 with its name, pid and parent — and again after the stop, where anything still holding a port is by definition not ours. `test-stack-up.sh` runs the same check wherever it would tell you to start `run --local` (`--no-docker`, no Docker daemon, `--check` with a leftover record), on the record's ports, `RUN_LOCAL_APP_PORT`/`RUN_LOCAL_ADMIN_PORT`, or mxcli's defaults 8080/8090. Holder lookup: lsof, then ss, then netstat + tasklist on Windows; unnamed holders still block when nothing of ours is running. Fixtures: `test-app-restart.sh` +2 cases (14 assertions, all fail on the old script), `test-stack-up-local.sh` T7 (4 of 6 fail on the old script). — AlvinnYeo

‎agents/review-agent.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,10 @@ current — that block is the single source of truth and gets corrected independ
141141
Both are read-only. Neither touches the `.mpr`. Both refuse to report a clean sweep from a stale
142142
or empty catalog rather than silently passing.
143143

144-
**Stale-catalog self-heal:** if `graph-sweep.sh` faults because `catalog_meta.build_mode` is not
145-
`full`, you may run `./mxcli -p <mpr> -c 'REFRESH CATALOG FULL'` yourself and retry once — this
146-
rebuilds the catalog database, it does not write to `.mpr` content, so it's outside what the
144+
**Stale-catalog self-heal:** if `graph-sweep.sh` faults because the catalog is stale, its refs table
145+
is empty, or its refs carry no `RefKind` (its `build_mode` label is reported, not judged — mxcli v0.24+ writes
146+
`source` for a complete catalog), you may run `./mxcli -p <mpr> -c 'REFRESH CATALOG FULL'`
147+
yourself and retry once — this rebuilds the catalog database, it does not write to `.mpr` content, so it's outside what the
147148
project's write-approval rule protects. If the retry still faults, report it; don't loop.
148149

149150
## Four hard rules — STOP, not suggestions

‎project-bin/app-facts.sh‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,13 @@ fi
246246

247247
q() { sqlite3 -noheader "$DB" "$1"; }
248248
BUILD_MODE="$(q "SELECT Value FROM catalog_meta WHERE Key='build_mode';")"
249-
if [ "$BUILD_MODE" != "full" ]; then
250-
echo "FAULT: catalog build_mode is '$BUILD_MODE' after graph-report; refs/activities are not trustworthy." >&2
251-
exit 2
252-
fi
249+
# The build_mode LABEL is not checked (fixed 2026-10-13, field report 2026-10-11): mxcli v0.24.0
250+
# labels a complete catalog 'source', and the old `!= full` test refused it on every run. What
251+
# this script needs is checked instead, below: the tables by name, non-empty refs/activities,
252+
# and at least one refs row that carries a RefKind (the dependency facts group by it). The label
253+
# is recorded in manifest.json as found, so a reader can still see it. Zero 'call' edges is NOT a
254+
# fault: an early module reached only from pages and buttons has none, and its call graph is
255+
# legitimately empty — requiring them (the first cut of this fix) refused such apps.
253256
MISSING=""
254257
for t in modules_data entities_data microflows_data activities_data pages_data scheduled_events_data refs catalog_meta projects; do
255258
[ "$(q "SELECT count(*) FROM sqlite_master WHERE name='$t';")" = "1" ] || MISSING="$MISSING $t"
@@ -264,10 +267,17 @@ N_MODULES=$(q "SELECT count(*) FROM modules_data;")
264267
N_REFS=$(q "SELECT count(*) FROM refs;")
265268
N_ACTS=$(q "SELECT count(*) FROM activities_data;")
266269
if [ "${N_MODULES:-0}" -eq 0 ] || [ "${N_REFS:-0}" -eq 0 ] || [ "${N_ACTS:-0}" -eq 0 ]; then
267-
echo "FAULT: catalog is vacuous (modules=$N_MODULES refs=$N_REFS activities=$N_ACTS). An empty graph is not a clean app." >&2
270+
echo "FAULT: catalog is vacuous (modules=$N_MODULES refs=$N_REFS activities=$N_ACTS, build_mode '${BUILD_MODE:-unset}'). An empty graph is not a clean app." >&2
271+
exit 2
272+
fi
273+
KINDS="$(q "SELECT k || '=' || n FROM (SELECT COALESCE(NULLIF(RefKind,''),'(none)') k, count(*) n FROM refs GROUP BY k ORDER BY n DESC LIMIT 8);" | paste -sd' ' -)"
274+
N_KINDED=$(q "SELECT count(*) FROM refs WHERE COALESCE(RefKind,'') <> '';")
275+
if [ "${N_KINDED:-0}" -eq 0 ]; then
276+
echo "FAULT: catalog refs carry no RefKind (build_mode '${BUILD_MODE:-unset}', $N_REFS refs by kind: ${KINDS:-none})." >&2
277+
echo " The dependency and call-graph facts are read by kind. Refusing to write facts without one." >&2
268278
exit 2
269279
fi
270-
log "catalog ok: modules=$N_MODULES refs=$N_REFS activities=$N_ACTS (${T1}s)"
280+
log "catalog ok: build_mode=${BUILD_MODE:-unset} modules=$N_MODULES refs=$N_REFS (${KINDS:-none}) activities=$N_ACTS (${T1}s)"
271281

272282
# ---------------------------------------------------------------------------------------------
273283
# Step 2 — inventory + dependencies, straight from the catalog through the REAL module column.
@@ -546,7 +556,7 @@ fi
546556
FACTS_DIR="$FACTS_DIR" MX_VERSION="$MX_VERSION" MPR_BASE="$MPR_BASE" DB="$DB" \
547557
T1="$T1" T2="$T2" T3="$T3" TALL="$(( $(date +%s) - START_ALL ))" \
548558
LOOP_STATUS="$LOOP_STATUS" LOOP_REASON="$LOOP_REASON" N_CAND="$N_CAND" N_DESC="$N_DESC" N_FAILED="$N_FAILED" N_RULES="$N_RULES" \
549-
N_MODULES="$N_MODULES" N_REFS="$N_REFS" N_ACTS="$N_ACTS" WORKERS="$WORKERS" MAX_LOOPS="$MAX_LOOPS" "$PY" - <<'PY'
559+
BUILD_MODE="$BUILD_MODE" N_MODULES="$N_MODULES" N_REFS="$N_REFS" N_ACTS="$N_ACTS" WORKERS="$WORKERS" MAX_LOOPS="$MAX_LOOPS" "$PY" - <<'PY'
550560
import json, os, datetime, hashlib
551561
F = os.environ["FACTS_DIR"]
552562
def sha(p):
@@ -563,7 +573,7 @@ man = {
563573
"generated": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
564574
"mpr": os.environ["MPR_BASE"], "mpr_sha256_16": sha(mp) if os.path.exists(mp) else "",
565575
"mxcli": os.environ["MX_VERSION"],
566-
"catalog": {"modules": int(os.environ["N_MODULES"]), "refs": int(os.environ["N_REFS"]), "activities": int(os.environ["N_ACTS"]), "build_mode": "full"},
576+
"catalog": {"modules": int(os.environ["N_MODULES"]), "refs": int(os.environ["N_REFS"]), "activities": int(os.environ["N_ACTS"]), "build_mode": os.environ.get("BUILD_MODE", "")},
567577
"timings_s": {"catalog_and_graph_report": int(os.environ["T1"]), "inventory_and_dependencies": int(os.environ["T2"]), "loops": int(os.environ["T3"]), "total": int(os.environ["TALL"])},
568578
"options": {"workers": int(os.environ["WORKERS"]), "max_loops": int(os.environ["MAX_LOOPS"])},
569579
"sections": {

0 commit comments

Comments
 (0)