diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a253d7..15039e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(conformance): **a definition that merely contains "(0 " or "not found" is no longer scored ABSENT** (#470). `conformance-check.sh` matched both markers anywhere in mxcli's answer, so a built microflow whose documentation read "HttpStatus (0 = no response or time-out)" went STALE (five false STALEs in one module), and so does every `describe navigation` with a not-found page, which mxcli prints as " not found page ". It now reads an error only on the first non-blank line (`Error:…`, the old start-of-output meaning; enumeration, scheduled-event and image-collection docs print raw, so a doc line may start `Error:`), and an empty set only when the last non-blank line is exactly the `(0 …)` summary (also `(0 workflow group(s))`); CRLF output reads the same. New fixture `tests/wave2/test-conformance-absent-marker.sh` replays two verbatim captures already in the repo (the v0.25.0 navigation describe of mxcli's public test app, and a v0.23.0 microflow describe with the issue's doc sentence spliced in), plus an enumeration with a raw `Error:` doc line and CRLF-only cases, both layouts; 6 FAIL on master, 24/24 with the fix. — jopterhorst - fix(progress-report): **the build plan is shown once: the phase cards are the plan.** Expanding a card now shows its full rows (#, Kind, Step, Produces/Proves, Depends on, State, Scripts) and Claims, which only the duplicate "Full plan tables" block carried; that block is gone and views A (script folders) and B (module proof) are their own section. The `now` phase renders open with a selected style, opening another phase closes the previous one (accordion; `#phase-N` links still open their card), and a viewer's saved open/closed state survives the 30 s reload and wins over the default (#474) — field feedback from a requirements-driven project at Stage 5 — MendixMau (field feedback 2026-10-20) ## v2026.10.19 diff --git a/project-bin/conformance-check.sh b/project-bin/conformance-check.sh index 531bbb94..f5520cc4 100755 --- a/project-bin/conformance-check.sh +++ b/project-bin/conformance-check.sh @@ -360,10 +360,31 @@ probe() { esac if [ $rc -ne 0 ]; then echo "ABSENT"; return; fi if [ -z "$(printf '%s' "$out" | tr -d '[:space:]')" ]; then echo "ABSENT"; return; fi - case "$out" in - Error:*|*"not found"*) echo "ABSENT"; return ;; - *"(0 "*) echo "ABSENT"; return ;; # empty result set, e.g. "(0 entities)" - esac + # Read the two absence markers only where mxcli prints them, never in the definition body. + # A substring match read a DESCRIBE whose documentation said "HttpStatus (0 = no response or + # time-out)" as an empty set, and the verbatim v0.25.0 `describe navigation` of mxcli's public + # test app carries " not found page Pages.Vehicle_Overview" (a navigation setting): both + # scored ABSENT, so built rows went STALE (#470). + # Error — mxcli puts its error FIRST (stderr is merged above, and `-c` pairs it with exit 1, + # which the rc test already scored); so only the first non-blank line is read, the + # old `Error:*` start-of-output meaning. Not any line: a body line CAN start at + # column 0 — enumerations, scheduled events and image collections print their + # documentation raw inside `/** … */` (one ` * ` on the first line only), and a + # multi-line string prints raw too, so a doc line "Error: …" is still PRESENT. + # (0 …) — a table's summary is printed as its own LAST line, `fmt.Fprintf("\n%s\n", Summary)` + # with Summary = "(%d entities)", "(%d workflow group(s))", …; a definition ends in + # `/`, `;` or a grant, never in a parenthesised count. + # CR stripped for a Windows-built mxcli (printed "\n%s\n", so nothing else trails the summary). + # Here-strings, not `printf | grep -q`: under pipefail + # that pipe can lose to SIGPIPE on a long definition (test-pipefail-grep-q.sh). + local first last + out="$(tr -d '\r' <<<"$out")" + first="$(sed -n '/[^[:space:]]/{p;q;}' <<<"$out")" + case "$first" in Error:*) echo "ABSENT"; return ;; esac + last="$(sed '/^[[:space:]]*$/d' <<<"$out" | tail -n 1)" + if grep -Eq '^\(0 .*\)$' <<<"$last"; then # no trailing class: the CR strip above is what lets CRLF output match + echo "ABSENT"; return # empty result set, e.g. "(0 entities)" + fi echo "PRESENT" } diff --git a/tests/wave2/test-conformance-absent-marker.sh b/tests/wave2/test-conformance-absent-marker.sh new file mode 100644 index 00000000..e0dd5b71 --- /dev/null +++ b/tests/wave2/test-conformance-absent-marker.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# Usage: bash test-conformance-absent-marker.sh [path-to-conformance-check.sh] +# +# Fixture for project-bin/conformance-check.sh's probe(): where it looks for the two "absent" +# markers in mxcli's answer (issue #470). +# +# The defect: any "(0 " or "not found" ANYWHERE in the output scored the element ABSENT, so a +# built row went STALE when the definition merely contained those characters. The issue's field +# case: a microflow whose documentation reads "HttpStatus (0 = no response or time-out)" — five +# false STALEs in one module. The same substring match hits every `describe navigation` with a +# not-found page set, because mxcli prints that setting as " not found page ". +# +# Golden input. No mxcli binary was available when this fixture was written, so it replays +# captures already in this repo, verbatim: +# - fixtures/design-audit-nav/describe-navigation-responsive-v025.txt — `describe navigation +# Responsive`, mxcli v0.25.0 on mxcli's public test app testapp-views/TestApp.mpr (carries +# " not found page Pages.Vehicle_Overview"); replayed without its four "-- GOLDEN" header lines; +# - fixtures/context-pack/capture/describe/MICROFLOW__Sales.ACT_ApprovalStep_Approve.out — +# `DESCRIBE MICROFLOW`, mxcli v0.23.0. The issue's doc text is quoted verbatim from #470 and +# spliced into that capture's documentation line (the only edit), since #470 quotes the +# sentence, not the whole definition. +# The empty-set reply follows mxcli's own printer (mdl/executor/format.go: the summary is +# printed as "\n%s\n" after the table, Summary = "(%d entities)", "(%d workflow group(s))", …), +# not a capture — re-capture it from a real `SHOW … IN ` when a binary is at hand. +# +# Cases, each on BOTH layouts (single-tree, and two-tree with the .mpr under app/): +# 1. a built microflow whose documentation says "(0 = …)" is PRESENT (was ABSENT -> STALE) +# 2. a built navigation profile with a not-found page is PRESENT (was ABSENT -> STALE) +# 3. the unedited microflow capture stays PRESENT +# 4. an empty result set, "(0 entities)" as the last line, stays ABSENT +# 5. an empty set whose noun has parentheses, "(0 workflow group(s))", stays ABSENT +# 6. a missing element ("Error: … not found", rc 1) stays ABSENT +# 7. an "Error:" line with rc 0 is still ABSENT +# 8. CRLF output (a Windows-built mxcli): the doc'd microflow PRESENT, and the empty set +# ABSENT — the summary regex has no trailing-space class, so this passes only because of +# the CR strip (delete the `tr -d '\r'` and it fails); an rc-0 "Error:" first line ABSENT +# 9. an enumeration whose documentation has a raw line starting "Error:" stays PRESENT: mxcli +# prints enumeration docs as "/**\n * %s\n */" with the multi-line text raw +# (mdl/executor/cmd_enumerations.go), so a doc line can start at column 0. Built from that +# printer, not captured. + +set -uo pipefail + +SUT="${1:-}" +[ -z "$SUT" ] && SUT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/project-bin/conformance-check.sh" +if [ ! -f "$SUT" ]; then + echo "SKIP: subject not found at $SUT" + echo "SCORE: 0/0 — nothing to test" + exit 0 +fi +SUT="$(cd "$(dirname "$SUT")" && pwd)/$(basename "$SUT")" +[ -f "$(dirname "$SUT")/_common.sh" ] || { echo "FAIL — _common.sh not beside $SUT"; exit 1; } +FIX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fixtures" +NAV="$FIX/design-audit-nav/describe-navigation-responsive-v025.txt" +MF="$FIX/context-pack/capture/describe/MICROFLOW__Sales.ACT_ApprovalStep_Approve.out" +for f in "$NAV" "$MF"; do [ -f "$f" ] || { echo "FAIL — golden capture missing: $f"; exit 1; }; done + +PASS=0; FAIL=0 +ok() { PASS=$((PASS+1)); echo " ok — $1"; } +bad() { FAIL=$((FAIL+1)); echo " FAIL — $1"; [ -n "${2:-}" ] && echo " got: $2"; } + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/conf-absent.XXXXXX")" +trap 'rm -rf "$WORK"' EXIT + +setup() { # setup -> sets P + P="$WORK/$1-$2" + rm -rf "$P"; mkdir -p "$P/architecture/modules/Sales" "$P/cap" + if [ "$1" = two ]; then mkdir -p "$P/app"; : > "$P/app/App.mpr"; else : > "$P/App.mpr"; fi + grep -v '^-- GOLDEN\|^-- built from\|^-- testdata/\|^-- the same text' "$NAV" > "$P/cap/nav.out" + cp "$MF" "$P/cap/mf.out" + # The one edit: the issue's verbatim documentation text in place of the capture's. + sed 's#^ \* Page button: approve the current step with the comment typed on it\.$# * Page button: approve. HttpStatus (0 = no response or time-out).#' \ + "$MF" > "$P/cap/mfdoc.out" + printf '| Name |\n|------|\n\n(0 entities)\n' > "$P/cap/empty.out" + printf '| Name |\n|------|\n\n(0 workflow group(s))\n' > "$P/cap/emptyparen.out" + printf '/**\n * Payment states.\nError: shown when the provider rejects the payment.\n */\ncreate or modify enumeration Sales.PayStatus (\n Paid '"'"'Paid'"'"',\n Error '"'"'Error'"'"'\n);\n/\n' > "$P/cap/enum.out" + printf 'Error: microflow not found: Sales.ACT_Rc0\n' > "$P/cap/rc0.out" + if [ "$2" = crlf ]; then + for f in "$P"/cap/*.out; do sed 's/$/\r/' "$f" > "$f.tmp" && mv "$f.tmp" "$f"; done + fi + cat > "$P/mxcli" <<'EOF' +#!/usr/bin/env bash +cmd=""; while [ $# -gt 0 ]; do [ "$1" = -c ] && cmd="$2"; shift; done +c="$(dirname "$0")/cap" +case "$cmd" in + "DESCRIBE NAVIGATION Responsive") cat "$c/nav.out" ;; + "DESCRIBE MICROFLOW Sales.ACT_Plain") cat "$c/mf.out" ;; + "DESCRIBE MICROFLOW Sales.ACT_Doc") cat "$c/mfdoc.out" ;; + "SHOW ENTITIES IN Empty") cat "$c/empty.out" ;; + "SHOW WORKFLOWS IN Empty") cat "$c/emptyparen.out" ;; + "DESCRIBE MICROFLOW Sales.ACT_Rc0") cat "$c/rc0.out" ;; + "DESCRIBE ENUMERATION Sales.PayStatus") cat "$c/enum.out" ;; + *) echo "Error: microflow not found: ${cmd##* }" >&2; exit 1 ;; +esac +EOF + chmod +x "$P/mxcli" + cat > "$P/architecture/modules/Sales/coverage-ledger.md" <<'EOF' +| Pointer | Kind | Name | Row | Proof | Acceptance | Status | +|---|---|---|---|---|---|---| +| `/microflows/1` | microflow | ACT_Doc | 1.1 | exec.sh | `DESCRIBE MICROFLOW Sales.ACT_Doc` | built | +| `/navigation/0` | navigation | Responsive | 1.2 | exec.sh | `DESCRIBE NAVIGATION Responsive` | built | +| `/microflows/0` | microflow | ACT_Plain | 1.3 | exec.sh | `DESCRIBE MICROFLOW Sales.ACT_Plain` | built | +| `/entities/0` | entity | Nothing | 1.4 | exec.sh | `SHOW ENTITIES IN Empty` | not-built | +| `/workflows/0` | workflow | Nothing | 1.5 | exec.sh | `SHOW WORKFLOWS IN Empty` | not-built | +| `/microflows/2` | microflow | ACT_Gone | 1.6 | exec.sh | `DESCRIBE MICROFLOW Sales.ACT_Gone` | not-built | +| `/microflows/3` | microflow | ACT_Rc0 | 1.7 | exec.sh | `DESCRIBE MICROFLOW Sales.ACT_Rc0` | not-built | +| `/enumerations/0` | enumeration | PayStatus | 1.8 | exec.sh | `DESCRIBE ENUMERATION Sales.PayStatus` | built | +EOF +} + +observed() { # observed -> the observed column of today's report + P_PTR="\`$1\`" awk -F'\t' '$2==ENVIRON["P_PTR"] {print $4}' "$P"/docs/conformance/report-*.tsv 2>/dev/null | head -1 +} + +for layout in single two; do + for eol in lf crlf; do + setup "$layout" "$eol" + ( cd "$P" && PROJECT_ROOT="$P" bash "$SUT" --quiet ) > "$WORK/out" 2>&1; rc=$? + tag="[$layout/$eol]" + if [ "$eol" = lf ]; then + [ "$rc" = 0 ] && ok "$tag run exits 0" || bad "$tag run exit $rc" "$(tr '\n' ' ' < "$WORK/out" | cut -c1-300)" + [ "$(observed /microflows/1)" = PRESENT ] && ok "$tag 1. doc with '(0 = …)' is PRESENT" \ + || bad "$tag 1. doc with '(0 = …)' read as $(observed /microflows/1) (the #470 false STALE)" + [ "$(observed /navigation/0)" = PRESENT ] && ok "$tag 2. navigation with a not-found page is PRESENT" \ + || bad "$tag 2. navigation read as $(observed /navigation/0)" + [ "$(observed /microflows/0)" = PRESENT ] && ok "$tag 3. unedited capture PRESENT" \ + || bad "$tag 3. unedited capture read as $(observed /microflows/0)" + [ "$(observed /entities/0)" = ABSENT ] && ok "$tag 4. '(0 entities)' is ABSENT" \ + || bad "$tag 4. '(0 entities)' read as $(observed /entities/0)" + [ "$(observed /workflows/0)" = ABSENT ] && ok "$tag 5. '(0 workflow group(s))' is ABSENT" \ + || bad "$tag 5. '(0 workflow group(s))' read as $(observed /workflows/0)" + [ "$(observed /microflows/2)" = ABSENT ] && ok "$tag 6. Error + rc 1 is ABSENT" \ + || bad "$tag 6. missing element read as $(observed /microflows/2)" + [ "$(observed /microflows/3)" = ABSENT ] && ok "$tag 7. Error line with rc 0 is ABSENT" \ + || bad "$tag 7. rc-0 Error read as $(observed /microflows/3)" + [ "$(observed /enumerations/0)" = PRESENT ] && ok "$tag 9. enum doc with a raw 'Error:' line stays PRESENT" \ + || bad "$tag 9. enum doc line 'Error: …' read as $(observed /enumerations/0)" + else + [ "$(observed /microflows/1)" = PRESENT ] && ok "$tag 8a. CRLF: doc'd microflow PRESENT" \ + || bad "$tag 8a. CRLF doc'd microflow read as $(observed /microflows/1)" + [ "$(observed /entities/0)" = ABSENT ] && [ "$(observed /workflows/0)" = ABSENT ] \ + && ok "$tag 8b. CRLF: '(0 …)\\r' summaries are ABSENT (the CR strip, not a trailing-space class)" \ + || bad "$tag 8b. CRLF empty set misread" "entities=$(observed /entities/0) workflows=$(observed /workflows/0)" + [ "$(observed /microflows/3)" = ABSENT ] && [ "$(observed /enumerations/0)" = PRESENT ] \ + && ok "$tag 8c. CRLF: rc-0 'Error:' first line ABSENT, enum doc 'Error:' line PRESENT" \ + || bad "$tag 8c. CRLF Error handling" "rc0=$(observed /microflows/3) enum=$(observed /enumerations/0)" + fi + done +done + +echo "" +echo "PASS=$PASS FAIL=$FAIL" +[ "$FAIL" -eq 0 ]