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 @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob
Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased
- fix(gate-check): **the stage-decision readers now read only the Decisions table, and Stage 7 accepts a dated CONFIRMED.** `has_confirmed_decision` (Stages 3 and 4) and the Stage 7 cutover check scanned every pipe row in the register, so an Open-questions row numbered 4 or 7 with Status CONFIRMED passed that stage with no decision behind it. Both now count only rows under a header whose first cell is `Stage`; a register with no such header keeps the old every-row scan. Stage 7 also matched the status string-exactly, so `CONFIRMED 2026-08-10` failed there while passing every other stage (TD-07); it now uses the same word-anchored match. Positive control: the pre-fix script passes Stages 4 and 7 on an Open-questions-only register and fails Stage 7 on the dated status. Fixture T13–T15 in `test-bug03-gates.sh` — MendixMau
- learn(bug-logs): **`BUG-DRAFT-grant-association-generalization-member`** — a member association owned by another module's entity cannot be named in a `grant` statement, so `revoke` + `re-grant` silently drops that member access and `mx check` reports CE0066; patch-only workaround provided. Found fixing a guest-groups association in a Mendix app (mxcli v0.23.0, Mendix 11.12.2). — MendixMau
- chore(inbox): **triage of `contrib/inbox/`: removed 15 promoted/obsolete files, gave the rest neutral names and scrubbed identifiers.** Gone: the ten machine-diff `*-patches.md` files from 2026-09-14, the 2026-09-27 field-project patch file, the built company-brain idea, and three already-promoted or superseded bug dumps. Kept for now: `2026-09-27-mxcli-upstream-issues.md`, because five drafts under `bug-logs/pending-github-issues/` cite it as their source. Four remaining files were renamed to `field-project-{a,b,c}` names; project and app names, home-style paths and personal names inside the 17 remaining entries were replaced with neutral labels or placeholders (technical content unchanged). — MendixMau
- learn(learned-mdl-preflight, learned-detection-gaps, microflow-preflight, testing-shape, learned-mdl-cannot-express, learned-page-patterns, rest-integration-first-time-right, journey-proof, learned-datagrid-customcontent-binding): **the skills side of BUG-142 to BUG-154.** STOP row 12 rewritten shorter for v0.24.0 — `ContentParams` takes a bare attribute or a quoted literal only, and the `toString(Attr)` fix it taught no longer builds; row 14 notes a narrowing `grant` merges, row 23 that `diff` prints false `-`/`~` lines. Detection gaps: the ContentParams row now covers any object-bound `dynamictext` and says check passes `if … then … else`; reserved words cover entities; new CE1870 row. microflow-preflight: CONV013 false positive and doc-comment round-trip resolved from "Not verified"; error handler without `return` is the second merge-overlap shape. testing-shape: `--test-endpoint` writes into the working model until a clean stop; `run --local` starts with scheduled events off (new register row). Also: five cannot-express rows (tab badge, validate-while-typing, AppTitle, always-open nav groups, sidebar toggle / page-load / Marketplace layout) and the DG2 `emptyplaceholder` correction; control-bar attributes, nested list view `Editable` and REPLACE-not-re-run-safe in page patterns; v0.24.0 REST bullets; two OQL journey traps. Triages three inbox notes (ContentParams, REST/JSON/page field notes, scheduled events) — card-disbursement requirements-driven build
Expand Down
42 changes: 31 additions & 11 deletions bin/gate-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1022,8 +1022,16 @@ has_confirmed_decision() {
local stage="$1"
local f="$REGISTER"
[ -n "$f" ] && [ -f "$f" ] || return 1
# Table-scoped: only rows of the Decisions table (a header row whose first cell is
# "Stage") count. Scanning every pipe row let the Open-questions table
# ("| 7 | ... | Raised at | CONFIRMED |") stand in for a stage decision. A register with
# no such header anywhere falls back to scanning every table, as before.
awk -F'|' -v want="$stage" '
/^\|/ {
!/^\|/ { intab = 0; next }
{
h = tolower($2); gsub(/^[ \t]+|[ \t]+$/, "", h)
if (h == "stage") { intab = 1; hdr = 1; next }
if ($2 ~ /^[ \t:]*-+[ \t:-]*$/) next
s = $2
gsub(/^[ \t]+|[ \t]+$/, "", s)
sub(/^[Ss]tage[ \t]*/, "", s)
Expand All @@ -1043,10 +1051,10 @@ has_confirmed_decision() {
# yet") does not qualify, and neither does "NOT CONFIRMED". What this newly
# admits is a field that BEGINS with CONFIRMED and then continues — which is
# the convention it exists to accept.
if (f ~ /^CONFIRMED([ \t]|$)/) found = 1
if (f ~ /^CONFIRMED([ \t]|$)/) { anyf = 1; if (intab) tabf = 1 }
}
}
END { exit !found }
END { exit !(hdr ? tabf : anyf) }
' "$f"
}

Expand Down Expand Up @@ -1212,30 +1220,42 @@ check_stage_7() {
echo "PENDING|no decision register (PROJECT.md) under $PROJECT_DIR — nothing to read a cutover decision from"
return
fi
# Field-exact on the STATUS (as has_confirmed_decision already is, since d8117be) and now
# Anchored on the STATUS (as has_confirmed_decision is, since d8117be) and now
# also column-aware on ROW SELECTION. `tolower($0) ~ /cutover/` scanned the whole row
# including the free-text Notes cell, so an unrelated CONFIRMED row whose notes said
# "groundwork for the eventual cutover" passed Stage 7 — the ✋ gate whose stated purpose is
# "ASSUMED does not pass". A row qualifies only if it IS the cutover decision: stage field
# (col 2) equal to 7, or the decision field (col 3) naming the cutover. Distinguish "no such
# row" from "row present but not CONFIRMED" — they need different fixes.
local verdict rows
#
# Table-scoped the same way as has_confirmed_decision: only the Decisions table (header
# row starting "| Stage |") is read, so an Open-questions row "| 7 | ... |" or any other
# table mentioning the cutover is not a candidate. Status matching is word-anchored like
# has_confirmed_decision (TD-07), so "CONFIRMED 2026-08-10" passes here as it does for
# every other stage; "NOT CONFIRMED" and a Notes cell mentioning the word still do not.
verdict=$(awk -F'|' '
/^\|/ {
!/^\|/ { intab=0; next }
{
h=tolower($2); gsub(/^[ \t]+|[ \t]+$/,"",h)
if (h=="stage") { intab=1; hdr=1; next }
if ($2 ~ /^[ \t:]*-+[ \t:-]*$/) next
s=$2; gsub(/^[ \t]+|[ \t]+$/,"",s); sub(/^[Ss]tage[ \t]*/,"",s)
d=(NF>=3)?tolower($3):""
if (s != "7" && d !~ /cutover/) next
rows++
for (i=2;i<=NF;i++){ v=toupper($i); gsub(/^[ \t]+|[ \t]+$/,"",v); if (v=="CONFIRMED") found=1 }
c=0
for (i=2;i<=NF;i++){ v=toupper($i); gsub(/^[ \t]+|[ \t]+$/,"",v); if (v ~ /^CONFIRMED([ \t]|$)/) c=1 }
anyr++; if (c) anyf=1
if (intab) { tabr++; if (c) tabf=1 }
}
END { print (found?"PASS":"FAIL") " " rows+0 }' "$f")
END { if (hdr) print (tabf?"PASS":"FAIL") " " tabr+0; else print (anyf?"PASS":"FAIL") " " anyr+0 }' "$f")
rows="${verdict#* }"
if [ "${verdict%% *}" = "PASS" ]; then
echo "PASS|a Stage-7/cutover decision row in $f has Status exactly CONFIRMED ($rows candidate row(s))"
echo "PASS|a Stage-7/cutover decision row in $f has Status CONFIRMED ($rows candidate row(s))"
elif [ "$rows" = "0" ]; then
echo "PENDING|no cutover decision row in $f — add a Decisions row whose Stage field is 7 (or whose Decision names the cutover) with Status exactly CONFIRMED"
echo "PENDING|no cutover decision row in $f — add a Decisions row whose Stage field is 7 (or whose Decision names the cutover) with Status CONFIRMED"
else
echo "FAIL|$rows cutover decision row(s) in $f, none with a field exactly CONFIRMED (✋ gate — UNCONFIRMED/ASSUMED does not pass)"
echo "FAIL|$rows cutover decision row(s) in $f, none with a field starting CONFIRMED (✋ gate — UNCONFIRMED/ASSUMED does not pass)"
fi
}

Expand Down
48 changes: 47 additions & 1 deletion tests/wave2/test-bug03-gates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ P="$(mkproj t7)"
} > "$P/PROJECT.md"
V="$(verdict "$P" 7)"
case "$V" in
*FAIL*) case "$V" in *'none with a field exactly CONFIRMED'*) ok "notes-only mention fails, and distinguishes 'row present, not CONFIRMED'" ;;
*FAIL*) case "$V" in *'none with a field starting CONFIRMED'*) ok "notes-only mention fails, and distinguishes 'row present, not CONFIRMED'" ;;
*) bad "fails but with the wrong diagnosis: $V" ;; esac ;;
*) bad "PASSED on a CONFIRMED row that merely MENTIONS the cutover in its notes: $V" ;;
esac
Expand Down Expand Up @@ -207,6 +207,52 @@ V="$(verdict "$P" 1)"
case "$V" in *'Path D'*) bad "migration project got the existing-app Stage 1 hint: $V" ;;
*) ok "other modes keep the extractor / kb-generation hint" ;; esac

echo "== T13: only the Decisions table counts — an Open-questions row numbered 7 is not a cutover decision =="
# Both register readers scanned every pipe row in the file, so the template's Open-questions
# table ("| # | Question | Raised at | Status |") could stand in for a stage decision whenever
# a question's number matched the stage and its Status cell said CONFIRMED.
P="$(mkproj t13)"
{ printf 'Toolkit commit: none\n\n## Decisions\n\n| Stage | Decision | Status | Notes |\n|---|---|---|---|\n'
printf '| 3 | Adopt Atlas design system | CONFIRMED | unrelated |\n'
printf '\n## Open questions\n\n| # | Question | Raised at | Status |\n|---|---|---|---|\n'
printf '| 7 | Who owns the cutover weekend? | Stage 5 | CONFIRMED |\n'
printf '| 4 | Build plan order ok? | Stage 4 | CONFIRMED |\n'
} > "$P/PROJECT.md"
V="$(verdict "$P" 7)"
case "$V" in *PASS*) bad "an Open-questions row passed Stage 7: $V" ;;
*'no cutover decision row'*) ok "Open-questions row #7 is not a Stage-7 candidate" ;;
*) bad "wrong diagnosis: $V" ;; esac
mkdir -p "$P/architecture"; printf '# Build plan\n' > "$P/architecture/build-plan.md"
V="$(verdict "$P" 4)"
case "$V" in *PASS*) bad "an Open-questions row passed Stage 4: $V" ;;
*FAIL*) ok "Open-questions row #4 is not a Stage-4 decision" ;;
*) bad "no Stage 4 FAIL: $V" ;; esac
{ printf '\n## Decisions (later)\n\n| Stage | Decision | Status | Notes |\n|---|---|---|---|\n'
printf '| 4 | Build plan approved | CONFIRMED | |\n'
} >> "$P/PROJECT.md"
V="$(verdict "$P" 4)"
case "$V" in *PASS*) ok "a Stage-4 row in a second Stage-headed table still counts" ;;
*) bad "false red on a real Stage-4 row: $V" ;; esac

echo "== T14: Stage 7 accepts a dated status, like every other stage (TD-07) =="
P="$(mkproj t14)"
{ printf 'Toolkit commit: none\n\n| Stage | Decision | Status | Notes |\n|---|---|---|---|\n'
printf '| 7 | Cutover plan | CONFIRMED 2026-08-10 | signed off |\n'
} > "$P/PROJECT.md"
V="$(verdict "$P" 7)"
case "$V" in *PASS*) ok "'CONFIRMED 2026-08-10' passes Stage 7" ;; *) bad "dated CONFIRMED rejected at Stage 7: $V" ;; esac
{ printf 'Toolkit commit: none\n\n| Stage | Decision | Status | Notes |\n|---|---|---|---|\n'
printf '| 7 | Cutover plan | NOT CONFIRMED | pending sponsor |\n'
} > "$P/PROJECT.md"
V="$(verdict "$P" 7)"
case "$V" in *FAIL*) ok "'NOT CONFIRMED' still fails Stage 7" ;; *) bad "'NOT CONFIRMED' did not fail: $V" ;; esac

echo "== T15: a register with no Stage-headed table falls back to scanning every row =="
P="$(mkproj t15)"
{ printf 'Toolkit commit: none\n\n| 7 | Cutover plan | CONFIRMED | |\n'; } > "$P/PROJECT.md"
V="$(verdict "$P" 7)"
case "$V" in *PASS*) ok "header-less register keeps the old behaviour" ;; *) bad "header-less register regressed: $V" ;; esac

printf '\n%s: %d ok, %d FAIL\n' "$(basename "$0")" "$PASS" "$FAIL"
rm -rf "$WORK"
[ "$FAIL" -eq 0 ]
Loading