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 @@ -17,6 +17,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased

- fix(dispatching, sync-project, obligation-check): **review follow-ups for the 2026-10-14 integration batch.** `dispatching.md` names app-restart exits 4 and 5 beside 3 and 1. `test-test-scratch.sh` drops `ls | grep -q` under pipefail for here-strings; sync step 4d no longer aborts on empty doctor output; `_ob_has_denominator` strips NUL bytes so bash stops warning. — integration 2026-10-14 reviews
- feat(lint): **Lint Batch 4 — SEC014 external role can write state, audit or ownership members; CONV009 reads its `maxObjects` option.** SEC014 ([#268](https://github.com/MendixMau/mxcli-project-toolkit/issues/268)): a partner role with `create, read *, write *` on self-owned entities passed every stock rule, so it could set its own record's status through the client API. Lint showed only CONV006 +4, which was re-baselined, and a manual review found it five days later. The new rule flags an external role (named in `lint-config.yaml` as `external_roles: "A, B"`, or `"none"`) that writes all members, a `Status`/`State`/`Result`/`Stage`/`Phase` enumeration, an `…At`/`…By` attribute, or the association its own XPath uses for ownership. It is a warning in the security tier. A `_rule` finding marks the rule blind when the option is blank or given as a YAML list (mxcli passes a list as `None`), when a role name does not exist, or when the catalog is not FULL. Each message is under 160 characters and names the place to fix it, because lint-gate prints only that much. An **absent** option is not a `_rule` finding, so it does not block every exec (the #229 pattern). Instead `bin/sync-project.sh` prints a NOTE on every sync through new `bin/lib/sec014-notice.sh` until the option is set. It is a NOTE rather than a counted warning, so a pristine scaffold still syncs clean under `--strict` (fixture `tests/wave2/test-sec014-notice.sh`, 14 cases; `test-bug12-sync.sh` 61/61). `"none"` switches a security rule off, so it is set only on the user's answer in chat and recorded in `PROJECT.md`. Right after configuring, re-baseline with `lint-gate.sh --update-baseline --reason "SEC014 configured" --approved-in-chat`, because the 0 → N jump would otherwise be blamed on the next script. CONV009 ([#267](https://github.com/MendixMau/mxcli-project-toolkit/issues/267)): the stock rule hard-codes 15, so the `maxObjects: 20` example in the config `mxcli init` writes did nothing. The toolkit copy reads `maxObjects` and `countLoopBodies`, with the **default kept at 15** (raising it is a maintainer call). It replaces the stock file through `STOCK-HASHES.txt`, which gains conv009 hashes for v0.17.0/0.24.0/0.25.0 and a full v0.26.0 set, captured by `mxcli init` from the official release assets; `test-stock-hashes.sh` T4 passes live on all four. `lint-that-actually-runs.md` now says `options:` reaches only rules that call `get_option()`. 32 cases in `tests/lint-rules/test-batch4-rules.sh`; the harness gained `get_option`, `permissions_for`, `attributes_for`, `user_roles` and `role_mappings`. Field run (official mxcli 0.26.0, two model copies): stock CONV009 stayed at 5 with `maxObjects: 20`, while the toolkit copy gave 5 at 15, 10 at 10 and 6 at 20 with loop bodies counted. SEC014 gave 1 true finding with the model's external role configured, and 7 with every role configured, matched by a SQL replay. Both rules load on 0.17.0 and 0.24.0 too. CONV009's "every flow reports 0 objects" self-check now needs `activities_for()` to find a real object in a zero-count flow, so an early greenfield model with empty microflows is not reported blind. `lint-rules/README.md` and the TXT002 header now give the supported way to turn a toolkit rule off. ISO dates in particular are a toolkit opinion, so a project with a non-ISO display standard sets `rules: TXT002: enabled: false` in `lint-config.yaml` and records that in `PROJECT.md`. It must not delete the file, which sync would re-install. On mxcli 0.26.0, a rule with 1 finding gave 0 once disabled. After #308, there is one opt-out story, always the user's decision (asked in chat, recorded in `PROJECT.md`), and it never deletes a `.star` file. `enabled: false` in `lint-config.yaml` stops a rule reporting; this is preferred. `.claude/lint-rules-disabled.txt` with a `# reason:` stops sync installing a toolkit rule, and refuses stock-seeded rules, a list that now includes conv009. `rules: SEC014: enabled: false` is the same decision as `external_roles: "none"`. While SEC014 is undecided, `project-bin/lint-gate.sh` also prints one uncounted `note: SEC014 not configured …` line on stderr on every run (no exit-code change, no lint row). Both it and the sync NOTE now count `external_roles` only when it sits under the `SEC014` key. `test-sec014-notice.sh` has 24 cases. — MendixMau
- fix(lint-gate): **rules that `mxcli init` reverted to stock now stop the lint run as BLIND instead of showing up as a rise on the next script** ([#264](https://github.com/MendixMau/mxcli-project-toolkit/issues/264)). `bin/lint-gate.sh` reads `.claude/.mxtk-lint-receipt` (written by `bin/lib/install-lint-rules.sh`) before linting: a stock-seeded rule (the file names `mxcli init` writes, now marked `stock` in the receipt) whose md5 no longer matches and whose `# mxtk-lint-rule:` header is gone, or any receipt-listed rule that is missing, is named, the remedy `bin/sync-project.sh <project>` (fallback `--upgrade-lint-rules all`) is printed, and the run exits 1 with `BLIND — lint rules reverted to stock` (exec.sh logs `LINT BLIND`, not `LINT ROSE`); `--update-baseline` is refused over reverted rules, also under the `LINT_RULES_REVERTED_OK=1` look-anyway override. Field cases: CONV010 26 -> 169 blamed on a pages-only exec, and a re-seed that sat five days while the baseline absorbed stock counts. So the remedy always clears it, `bin/lib/install-lint-rules.sh` restores such a stock-seeded rule even when its body matches no known stock hash (a newer mxcli's seed, or a hand edit that also lost the header: it was reported LOCALLY MODIFIED and never written), labelled `header missing — restored from toolkit, your copy kept as <rule>.reseeded-<ts>` with the newest 3 copies kept; a headerless toolkit-only or configurable rule is still a local edit, never overwritten. The installer also writes each receipt row's kind, prunes rows for rules no longer shipped, and honours `.claude/lint-rules-disabled.txt`: one line per rule with `# reason: …` (`#\s*reason:` in both tools), a user decision recorded in `PROJECT.md`; a line with no reason, or naming a stock-seeded rule, is ignored with a warning (`lint-rules/README.md` had told projects to just delete txt002). Fixture `tests/wave2/test-lint-receipt-revert.sh` builds its receipt with the real installer and uses a genuine v0.25.0 stock body: 55/55 here, 28 fail on master's lint-gate and installer. — AlvinnYeo
- learn(learned-mdl-preflight): **a microflow change near a `return` in a dense flow is planned as drop + create after `show access`; `check -p --references` does not run exec's free-room test** ([#263](https://github.com/MendixMau/mxcli-project-toolkit/issues/263)). STOP row 28 and BUG-156: on v0.25.0 `check` printed "no flow change exec would refuse" and exec then refused "there is no free room for the fragment". Upstream issue drafted, not filed. — AlvinnYeo
Expand Down
2 changes: 1 addition & 1 deletion bin/lib/obligation-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ _ob_find() {
# Deliberately loose — the wording belongs to the skill, only the shape is mechanical.
_ob_has_denominator() {
grep -qiE -- '[0-9]+[[:space:]]+of[[:space:]]+[0-9]+|[0-9]+[[:space:]]*/[[:space:]]*[0-9]+' \
<<<"$(head -c 4000 "$1" 2>/dev/null | tr -d '\r')"
<<<"$(head -c 4000 "$1" 2>/dev/null | tr -d '\r\0')"
}

# _ob_stale <project-root> <artifact> → prints "<hash>+<n>" and returns 0 if the mark has
Expand Down
4 changes: 2 additions & 2 deletions bin/sync-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1479,9 +1479,9 @@ if [ "$WIRED" -eq 1 ] && [ -x "$SCRIPT_DIR/doctor.sh" ] \
# rc 3 (an mxcli before v0.26 cannot check) and rc 4 (the check itself failed) are not
# findings about the model, so doctor's WARN line for them is relabelled, not shown as one.
if [ "$_ps_rc" -eq 3 ] || [ "$_ps_rc" -eq 4 ]; then
printf '%s\n' "$_ps_out" | grep -v '^[[:space:]]*$' | sed -e 's/^ WARN / info /' -e 's/^/ /'
printf '%s\n' "$_ps_out" | grep -v '^[[:space:]]*$' | sed -e 's/^ WARN / info /' -e 's/^/ /' || true
else
printf '%s\n' "$_ps_out" | grep -v '^[[:space:]]*$' | sed 's/^/ /'
printf '%s\n' "$_ps_out" | grep -v '^[[:space:]]*$' | sed 's/^/ /' || true
fi
case "$_ps_rc" in
0|2) ;;
Expand Down
4 changes: 3 additions & 1 deletion skills/dispatching.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ app." The helper stops only the pid that `run --local` recorded for that project
otherwise (`ui-loop.md` § "What watch picks up"). The old lead-only rule came from a broad kill
in one lane that took down another lane's app; the helper cannot do that, and the rule cost 24 implementer
restart-resumes (41.6M tokens, 8.5% of subagent tokens) in the 2026-10-12 run (#289). If the
helper refuses (exit 3) or the app does not come back (exit 1), the implementer reports
helper refuses (exit 3), the app does not come back (exit 1), a process it did not record
holds the app or admin port (exit 4, nothing started; the recorded app may already be stopped), or the stop left a live
MxTest after-startup in place (exit 5, nothing started), the implementer reports
`BLOCKED NEEDS_RESTART` with its output, as before.

**Prefix you do not control.** Every subagent turn also carries the harness's skill listing,
Expand Down
8 changes: 4 additions & 4 deletions tests/wave2/test-test-scratch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ printf '%s' "$R" | grep -qx '.mxcli' && bad "the live run --local record was cop
printf '%s' "$R" | grep -qx 'deployment' && bad "deployment/ was copied" || ok "deployment/ not copied"
[ "$(printf '%s' "$R" | tail -1)" = 1 ] && ok "port lock held during the run" || bad "no port lock during the run"
[ "$(cat "$P/Fixture.mpr")" = model ] && ok "live .mpr untouched" || bad "live .mpr was written"
ls "$WORK/tmp" | grep -q 'mxtk-test-ports' && bad "port lock left behind" || ok "port lock released"
ls "$WORK/tmp" | grep -q 'Fixture-test' && bad "scratch copy left behind" || ok "scratch copy deleted"
grep -q 'mxtk-test-ports' <<<"$(ls "$WORK/tmp")" && bad "port lock left behind" || ok "port lock released"
grep -q 'Fixture-test' <<<"$(ls "$WORK/tmp")" && bad "scratch copy left behind" || ok "scratch copy deleted"
[ -f "$P/.mpr-snapshots/.exec.lock" ] && bad "exec lock left behind" || ok "exec lock released"

echo "-- exit code passes through"
Expand All @@ -72,7 +72,7 @@ has "names the holder" "$OUT" "held by PID $H"
kill -KILL "$H" 2>/dev/null; wait "$H" 2>/dev/null

echo "-- dead holder's port lock is taken over (the SIGKILL above left it)"
ls "$WORK/tmp" | grep -q 'mxtk-test-ports' && ok "[setup] stale port lock present" || bad "[setup] no stale lock"
grep -q 'mxtk-test-ports' <<<"$(ls "$WORK/tmp")" && ok "[setup] stale port lock present" || bad "[setup] no stale lock"
rm -f "$P/.mpr-snapshots/.exec.lock"
OUT="$(run e -- tests/a.test.mdl)"; RC=$?
[ "$RC" -eq 0 ] && has "taken over out loud" "$OUT" "taking it over" || bad "stale port lock blocked (exit $RC)"
Expand Down Expand Up @@ -157,7 +157,7 @@ if command -v node >/dev/null 2>&1; then
has "prints the git repair" "$OUT" "git checkout -- 'mprcontents/4e/70/4e70063d-53e1-477a-ab92-6b763330c293.mxunit'"
has "prints the MDL repair with HEAD's value" "$OUT" "alter settings runtime (AfterStartupMicroflow: 'Orders.ASU_Startup');"
[ -f "$WORK/rec.k" ] && bad "mxcli ran on the injected model" || ok "mxcli not run"
ls "$WORK/tmp" | grep -q 'Fixture-test' && bad "a copy was made" || ok "nothing copied"
grep -q 'Fixture-test' <<<"$(ls "$WORK/tmp")" && bad "a copy was made" || ok "nothing copied"
node "$SETAS" "$GU" "$UNIT" "MxTest.TestRunner"
OUT="$(run l -- tests/a.test.mdl)"; RC=$?
[ "$RC" -eq 3 ] && ok "MxTest.TestRunner → refused (exit 3)" || bad "TestRunner exit $RC"
Expand Down
Loading