diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f59e0b63..4f9ccdd9 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -98,3 +98,5 @@ jobs: run: bash tests/lint-rules/test-batch2-rules.sh - name: Lint-rule unit suite, batch 3 (tests/lint-rules/test-batch3-rules.sh) run: bash tests/lint-rules/test-batch3-rules.sh + - name: Lint-rule unit suite, batch 4 (tests/lint-rules/test-batch4-rules.sh) + run: bash tests/lint-rules/test-batch4-rules.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 20342404..4c6faaca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,45 @@ 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 ` (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 .reseeded-` 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 +- fix(testing-shape, module-review, dispatching): **follow-ups to #290 and #292 — subject set reaches embedders, reviewers never re-run, dispatch guidance is its own small baseline file.** A UI-only change now re-runs the journeys of every page it reaches (pages embedding a changed snippet or layout, in any module), not "no other module's journeys"; `module-review.md`'s Prove row and "nothing grandfathers" line no longer say "re-run after any change" (implementer re-runs the subject set once, reviewer verifies the artifact, per `testing-shape.md` §3); the #274 cost anecdote appears once in `testing-shape.md` and is one clause in intake Q13, instead of told three times in full; `module-review.md` states the PROOF-OF-LOOK addenda rule and the final LOOK's `VALID AT:`/per-page proof/Stage-6 requirements that #321's checker enforces. `agent-roles.md`'s Dispatching section moved to `skills/dispatching.md` (981 words, with a note that forks/resumes/per-call models are Claude Code concepts — elsewhere, one fresh session per task); the lead loads that at P–6 and `agent-roles.md` returns to its pre-#292 setup row (P,4,5). Lead load at stages 1/2/3/6: −2,059 words each; baseline 76,259 → 76,191 ([#274](https://github.com/MendixMau/mxcli-project-toolkit/issues/274), [#288](https://github.com/MendixMau/mxcli-project-toolkit/issues/288)) — MendixMau +- feat(doctor, sync-project): **`mxcli fix property-sets` is wired into the upgrade path, report first: the Studio Pro merge break no gate could see.** mxcli up to v0.25 wrote units without Studio Pro's full property set. `mx check`, the build and the runtime stay green, and Studio Pro's merge/diff throws ("Objects with ID … do not have the same properties") once it has saved such a document. New `bin/doctor.sh --property-sets ` runs the read-only `--dry-run` and records the result with the mxcli version in `.claude/.property-sets-receipt` (machine-local, gitignored). It exits 0 clean, 1 needs repair, 2 not run, 3 for an mxcli before v0.26.0, and 4 when the check itself failed or its output could not be classified (never green by absence; a repair would not help). `sync-project.sh` runs it with `--if-stale`, which skips only a recorded clean or unsupported result for the installed mxcli (errors and timeouts are never recorded; the dry-run is bounded by `MXTK_PROPSETS_TIMEOUT`, default 300 s, 120 s under sync, which announces the bound first), and `doctor --quick` (which exec.sh runs when the mxcli version changes) warns while it is owed. The repair is `bin/doctor.sh --fix-property-sets `, which runs `mxcli fix property-sets` only through `exec.sh --patch` (snapshot, mxbuild gate, restore). It refuses on an exec.sh without `--patch`. Support is probed from `mxcli fix --help`, because `mxcli fix property-sets --help` exits 0 on v0.24/v0.25 too, printing the parent's help. New row in `learned-detection-gaps.md`; `platform-link.md` gains "Before a Team Server merge", covering `fix property-sets`, `fix hashes` and `git note`. Golden input is captured from the v0.24.0/v0.25.0/v0.26.0 release binaries. The clean/NEEDS/FAILED reports follow mxcli's source format strings, because no model was run. `tests/wave2/test-property-sets.sh` (61 cases). Field run still owed. `fix hashes` as an `exec.sh --patch` post-step is pending (Refs #295) — jopterhorst +- fix(sync-project): **sync names a `bin/model-stamp.sh` that predates the worktree hook blind-spot fix.** `known_fix_note` gains the `model-stamp.sh` arm from the #236 patch, keyed on `GIT_WORK_TREE`, which the fixed script pins. The arm says to upgrade `model-stamp.sh` and `_common.sh` together. `tests/wave2/test-model-stamp.sh` T9 adds the unstaged worktree `commit -a` and `commit -- app` cases. The silent rc 1 half of #236 landed earlier. The new note is asserted by `test-property-sets.sh` P10, which fails on master. — MendixMau (card-disbursement requirements-driven build, field task 21) +- chore(context): **baseline word budget freed — 79,997 → 76,259 words (−3,738) without dropping a rule.** The always-on tier sat 3 words under its 80,000 cap, so three open PRs could not all merge. Long incident narratives in `conversion-runbook.md`, `source-triage.md` and `interview-protocol.md` became one-line failure examples pointing at new on-demand `process/baseline-incident-history.md`, where the original text is kept verbatim per section (R1–R20, AR1, ST1–ST4, IP1–IP3); the runbook's copy of the open-questions status table now defers to `interview-protocol.md` §4; `agent-roles.md`'s six template shapes (setup-only, ~2,300 words) moved verbatim to `agents/README.md`, which the installers never copy (explicit `MXTK_AGENTS` list). Every STOP, rule, denominator and gate cell stays in place; files touched by open PRs were left alone. — MendixMau +- process(merging): **every PR gets an independent review before it merges — a merge go is not a review.** A reviewer that did not write the PR returns MERGE / MERGE-AFTER-FIX / HOLD on value, regression risk after sync, correctness and context cost; builder self-reports and green CI no longer count. Nine PRs merged on 2026-10-13 without one. — MendixMau +- process(context): **the baseline word budget is made room for by moving text, never by deleting rules.** Rules, STOP rows, denominators and failure examples are protected; stories and examples move behind pointers in a separate, independently reviewed trim PR; a trim PR lands first when a PR would take the baseline within 2,000 words of the cap. — MendixMau +- fix(page-fidelity): **text is matched against what the page shows, on whole words; ButtonStyle counts as `btn btn-