Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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
Expand Down
29 changes: 25 additions & 4 deletions project-bin/conformance-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
153 changes: 153 additions & 0 deletions tests/wave2/test-conformance-absent-marker.sh
Original file line number Diff line number Diff line change
@@ -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 <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 <empty module>` 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 <single|two> <crlf|lf> -> 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 <pointer> -> 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 ]
Loading