Skip to content

Lint Batch 4: SEC014 external-role writes, CONV009 reads maxObjects - #312

Closed
MendixMau wants to merge 18 commits into
masterfrom
feat/lint-batch4-security
Closed

MendixMau wants to merge 18 commits into
masterfrom
feat/lint-batch4-security

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

#268: SEC014, an external role that can write state, audit or ownership members

Problem. A partner, customer or portal role granted create, read *, write * … where [owner = CurrentUser] passes every stock rule:

  • CONV006 checks only create and delete.
  • CONV007 checks only for a missing XPath.
  • The SEC00x rules check only settings and PII reads.

Yet that role can set its own record's Status through the client API, skipping the approval microflow. In the field this showed up only as CONV006 +4, which was re-baselined. A manual review found it five days later.

Fix. New rule lint-rules/sec014_external_role_write.star: warning severity, in the security tier (the SEC* default in lint-gate.sh).

  • Configuration. The project names its external roles in .claude/lint-config.yaml: rules: SEC014: options: external_roles: "A, B". A name can be a user role or a module role, qualified or bare.
  • What it flags. One finding per entity × role when the role can write:
    • every attribute (write *);
    • an Enumeration attribute whose name ends in Status, State, Result, Stage or Phase;
    • an …At / …By audit attribute;
    • an association that the rule's own XPath uses for ownership.
  • Message. It names the fix: read on all members, write on input members only, and gate the rule on the initial state in its XPath.

Configuration states. These changed after review; see "Unset option: why it is not blocking" below.

Option state What exec / lint-gate sees What sync shows
absent (not decided yet) nothing a NOTE on every sync: the exact YAML to add, the rule for "none", and the re-baseline step
"A, B" findings —
"none" nothing; the rule is off —
a YAML list (mxcli passes a list as None), blank, an unknown role, or a catalog that is not FULL one _rule finding (PASS-BLIND); the message is under 160 characters and names .claude/lint-config.yaml or REFRESH CATALOG FULL —

"none" switches a security rule off. So the README, the rule header, the blank-value _rule message and the sync NOTE all say the same thing: it is set only on the user's answer, asked in chat and recorded in PROJECT.md, never to silence a finding.

Re-baselining. The first configured run jumps SEC014 from 0 to N, and lint-gate would blame that security-tier rise on whichever script runs next. The README, CHANGELOG and NOTE therefore say to re-baseline right after configuring, once the user has seen the findings: bin/lint-gate.sh --update-baseline --reason "SEC014 configured" --approved-in-chat. If #308 lands, its FIRST-SEEN handling covers part of this too.

Unset option: why it is not blocking. As first pushed, an absent option was a _rule finding. That would have made every synced project's exec.sh exit 1 (LINT BLIND) until someone edited a config file, which is the #229 pattern from an unconfigured CONV020. Moving the fix into the message (point 1) does not change that the exec still exits 1. So the undecided state now lives in bin/sync-project.sh, as a NOTE printed on every sync (new bin/lib/sec014-notice.sh).

  • It is a NOTE rather than a warn(). tests/wave2/test-bug12-sync.sh T0 requires a pristine scaffold to sync with exit 0 and no warning under --strict. The warn() version failed T0; the NOTE version passes 61/61.
  • I did not add an informational lint row. A counted row in an existing baseline would itself be a 0 → 1 security-tier rise on the next exec.
  • A value that someone wrote but that is unusable is still blocking. The issue's intent was "never a silent pass"; for the absent case that pass is now named on every sync rather than on every exec.
  • Backlog row 49 (new Batch 6) is linked to row 14, which covers the anonymous role only.

#267: CONV009 now reads its options

Problem. Stock conv009_max_microflow_objects.star (0.17.0–0.26.0) hard-codes 15 and never calls get_option(). The maxObjects: 20 example in the generated lint-config.yaml therefore does nothing.

Fix.

  • A toolkit-owned CONV009 reads maxObjects and countLoopBodies, and ships through the replace-the-stock path.
  • Default kept at 15 (raising it is a maintainer call). Setting maxObjects: 20 with countLoopBodies: true matches microflow-preflight.md.
  • An unusable value gives a _rule finding.
  • After review, the "every flow reports 0 objects" self-check fires only if activities_for() finds a real object (not a start, end or merge event) in a zero-count flow. An early greenfield model with empty microflows is no longer reported blind (new fixture).
  • STOCK-HASHES.txt gains conv009 for v0.17.0, v0.24.0 and v0.25.0, plus a full v0.26.0 set. I captured them with mxcli init into empty directories from the official mxcli-linux-amd64 release assets; the same run reproduced every hash already in the file.
  • skills/lint-that-actually-runs.md now says that options: reaches only rules that call get_option(), and that a list value arrives as None.
  • Backlog row 50.

Also: turning a toolkit rule off (TXT002)

The README and the TXT002 header now say that ISO dates are a toolkit opinion, not a Mendix rule. A project whose display standard is not ISO sets rules: TXT002: enabled: false in lint-config.yaml and records the decision in PROJECT.md. It must never delete the file, because sync re-installs it. Verified on mxcli 0.26.0: a toolkit rule with 1 finding gave 0 once disabled. The README points to #308's .claude/lint-rules-disabled.txt for stopping installation once that PR lands.

Evidence

Unit tests. tests/lint-rules/test-batch4-rules.sh: 32/32. It fails on master, where the rules are missing and the harness has no get_option. The stock conv009 also fails the maxObjects: 20 case. The harness gained mocks for get_option (with mxcli's conversion: a list becomes None), permissions_for, attributes_for, user_roles and role_mappings.

Field run (official mxcli 0.26.0, scratch copies of two models, catalog FULL):

Rule Configuration Model 1 (21 modules) Model 2 (9 modules)
CONV009, stock default 15 5 —
CONV009, stock maxObjects: 20 5 (bug reproduced) —
CONV009, toolkit default 15 5 0
CONV009, toolkit maxObjects: 10 10 0
CONV009, toolkit maxObjects: 20 5 0
CONV009, toolkit 20 + countLoopBodies 6 0
SEC014 option absent 0 0
SEC014 the model's external-style role 1 (true positive, same shape as #268) —
SEC014 every role (positive control) 7 0
SEC014 list / misspelt role 1 _rule each —
  • A SQL replay of the SEC014 logic over the catalog gives the same 7 / 0.
  • Model 2 has no external role, so its zeros are a null result.
  • Both rules also load and give the same counts on mxcli 0.17.0 and 0.24.0. SEC014 avoids associations(), which does not exist before 0.25.0 and stops the file loading.

Checks run

Check Result
tests/lint-rules/test-batch4-rules.sh 32/32
tests/lint-rules/test-batch3-rules.sh 23/23
tests/lint-rules/test-batch2-rules.sh 16/16
tests/lint-rules/test-batch1-rules.sh 29/29
tests/lint-rules/test-ux-rules.sh 36/36
tests/test-lint-delivery.sh ALL GREEN
tests/wave2/test-stock-hashes.sh 35/35; T4 live 40/40 on each of v0.17.0, v0.24.0, v0.25.0, v0.26.0
tests/wave2/test-sec014-notice.sh 14/14; fails on master
tests/wave2/test-bug12-sync.sh 61/61 (master 61/61)
tests/wave2/test-dryrun-writes-nothing.sh 6/6
tests/wave2/test-sync-dup-heading-exit.sh 6/6
tests/wave2/test-ritual-slim.sh 17/17
tests/wave2/test-slim-claude-md.sh 16/16
tests/wave2/test-change-log.sh 39/39
check-scripts, check-portability, check-no-private-citations, render-routing --check, check-no-client-data (no denylist), check-pr-discipline origin/master all clean

origin/master (#313) is merged in, with all CHANGELOG lines kept.

Risks

  • Ownership detection. A written member that is not one of the entity's own attributes is treated as an association. An inherited attribute would be misread this way, but it only matters if the XPath also names it.
  • write * on input-only entities. SEC014 flags write * even on an entity whose members are all input fields. That is deliberate per the issue; a reviewer can accept it.
  • Undecided projects. Until a project sets external_roles, SEC014 checks nothing on exec. The sync NOTE is the only reminder, and it does not fail --strict.

Field run: done (above). Status for both rules stays drafted until a second project reports.

Re-review round 2 (merges #308)

origin/fix/lint-receipt-check (#308) is merged into this branch. Its lint-rules/README.md conflict is resolved into a single "Opting out of a rule" section. That opt-out story is now the same everywhere:

  1. rules: <ID>: enabled: false in .claude/lint-config.yaml stops a rule reporting. This is the preferred route. The .star file stays installed and keeps refreshing; verified on mxcli 0.26.0, where a rule with 1 finding gave 0 once disabled.
  2. .claude/lint-rules-disabled.txt, one line per rule with a # reason:, stops sync installing a toolkit rule. Stock-seeded rules are refused; conv009 is now on that list with conv010, data_change_microflows and entity_business_key.
  3. Never delete the file. Deleting it reads as a re-seed and makes every exec report LINT BLIND.

Where this now appears:

SEC014 undecided is no longer silent in practice. While SEC014 is installed and has no external_roles under its key, project-bin/lint-gate.sh prints one stderr line on every run: note: SEC014 not configured — set .claude/lint-config.yaml rules: SEC014: options: external_roles (ask the user; 'none' is their decision). It changes no exit code and adds no lint row.

bin/lib/sec014-notice.sh and the lint-gate check both count external_roles, or enabled: false, only under the SEC014 key. The same awk is inlined in lint-gate because a project has no bin/lib. Cases covered in the fixture:

  • external_roles under another rule, or at top level, does not count;
  • an inline map, a quoted key, and a commented line all handled.

tests/wave2/test-sec014-notice.sh now has 24 cases. They include one note line, no note once configured or when SEC014 is not installed, and the same exit code either way.

Known gap (outside my files). exec.sh captures lint-gate output and shows it only on a failure, so the note does not appear during a clean exec. The sync NOTE and direct lint-gate runs carry it. A one-line exec.sh follow-up would surface it (grep '^note: SEC014' "$_lo").

Round 2 checks:

Check Result
tests/lint-rules/test-batch4-rules.sh 32/32
tests/lint-rules/test-batch3-rules.sh 23/23
tests/lint-rules/test-batch2-rules.sh 16/16
tests/lint-rules/test-batch1-rules.sh 29/29
tests/lint-rules/test-ux-rules.sh 36/36
tests/test-lint-delivery.sh ALL GREEN
tests/wave2/test-sec014-notice.sh 24/24
tests/wave2/test-lint-receipt-revert.sh 55/55
tests/wave2/test-stock-hashes.sh 35/35
tests/wave2/test-lint-gate-accept.sh 20/20
check-scripts, check-portability, check-no-private-citations, render-routing --check, check-no-client-data, check-pr-discipline all clean

Closes #268
Closes #267

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw


Generated by Claude Code

claude added 18 commits October 13, 2026 10:08
…rise (#264); BUG-156 + preflight row 28 (#263)

#264: `mxcli init` re-seeds .claude/lint-rules/ and the toolkit rules lose their header.
lint-gate.sh never read .claude/.mxtk-lint-receipt, so the re-seed showed up as a rise on the
next exec (CONV010 26 -> 169 on a pages-only script), or sat for days while the baseline
absorbed stock counts.

- project-bin/lint-gate.sh: before linting, each rule in the receipt is md5'd; a mismatch with
  the header gone (or a missing file) is named, the remedy `<toolkit>/bin/sync-project.sh
  <project>` is printed, and the run exits 1 with `BLIND — lint rules reverted to stock`.
  lint-last.json gets verdict BLIND-RULES-REVERTED and no total (no stale total for
  metrics-ledger). --update-baseline is refused over reverted rules. A headered edit is left to
  the installer's LOCALLY MODIFIED report; no receipt means no check;
  LINT_RULES_REVERTED_OK=1 lints anyway, with the warning.
- project-bin/exec.sh: run_lint_gate maps that output to LINT_RC 3 / "LINT BLIND: rules
  reverted to stock (<files>) — run sync-project.sh; delta not attributable".
- Guard rules: the receipt is the installer's evidence, not lint-gate's own stamp (rule 6);
  the remedy (sync-project.sh / the installer) is never blocked and clears it — fixture T6
  (rule 7).
- tests/wave2/test-lint-receipt-revert.sh: golden receipt from the real installer run into a
  scratch project; 23/23 here, 11 fail with master's lint-gate.sh.

#263 (learned-rule half): BUG-156 in bug-logs/mxcli-bugs.md (bin/bug-lookup.sh BUG-156 and
"free room" find it) and STOP row 28 in skills/learned-mdl-preflight.md, quoting the issue's
exec error verbatim. Upstream draft prepared, not filed.

Field run: the receipt/installer half was field-checked by the maintainer on a copy of a
v0.25.0 project (#264 comment: installer restores conv010/data_change, md5s then match the
toolkit). lint-gate's new check has not yet run on a live project — fixture only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…268, #267)

SEC014 (warning, security tier): an external user role, named in
lint-config.yaml as `external_roles: "A, B"` (or "none"), that can write
all members, a state enumeration, an audit ...At/...By attribute, or the
association its own XPath scopes ownership on. A `_rule` finding when the
option is unset, given as a YAML list (mxcli passes it as None), names a
role that does not exist, or the catalog is not FULL.

CONV009: toolkit-owned copy of the stock rule, which hard-codes 15 and
ignores the maxObjects option its own generated config advertises. Reads
maxObjects (default kept at 15) and countLoopBodies; replaces the stock
file through STOCK-HASHES.txt (conv009 at v0.17.0/0.24.0/0.25.0 plus a
full v0.26.0 set, captured with `mxcli init` from the official release
assets; test-stock-hashes.sh T4 passes live on all four).

Harness: get_option, permissions_for, attributes_for, user_roles,
role_mappings mocks. 26 cases in tests/lint-rules/test-batch4-rules.sh.

Field run (official mxcli 0.26.0, scratch copies of two models): stock
CONV009 5 -> 5 with maxObjects: 20; toolkit CONV009 5 / 10 / 6 at
15 / 10 / 20+loops. SEC014 1 finding with the external role configured,
7 with every role configured, matched by a SQL replay of the catalog.
Both rules also load on mxcli 0.17.0 and 0.24.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
The always-on baseline tier counted by bin/render-routing.sh --check was
at 79,735 of its 80,000-word cap. This frees 3,772 words (now 75,964)
by moving narrative and setup-only text out of baseline files, keeping
every rule, STOP, denominator and gate cell in place, each rule still
paired with a one-line failure example plus a pointer to the full text.

- conversion-runbook.md, source-triage.md, interview-protocol.md: long
  incident narratives -> one line + pointer; originals verbatim in new
  on-demand process/baseline-incident-history.md (R1-R20, AR1, ST1-ST4,
  IP1-IP3). Runbook's duplicate open-questions status table now defers
  to interview-protocol.md section 4.
- agent-roles.md: the six template shapes (setup-only) moved to
  agents/README.md, which installers never copy (explicit MXTK_AGENTS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…low", ST1 id

Independent review of #315: obligations.tsv still pointed at agent-roles.md for
step 8a, which moved to agents/README.md; agent-roles.md said the example bodies
are "below"; the history header cited a non-existent section id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…rate opt-out; prune stale receipt rows (#264 review)

Review of #308 (MERGE-AFTER-SMALL-FIX):

1. bin/lib/install-lint-rules.sh: a rule whose receipt row exists and whose `# mxtk-lint-rule:`
   header is gone is restored even when its hash is not in STOCK-HASHES.txt (an mxcli newer
   than the list, then init). It used to fall into the foreign branch -> LOCALLY MODIFIED, never
   written, so lint-gate stayed BLIND forever. Same test lint-gate uses; the found file is kept
   as <rule>.reseeded-<ts>. lint-gate's message adds the fallback
   `sync-project.sh <root> --upgrade-lint-rules all`.
2. Deliberate opt-out: .claude/lint-rules-disabled.txt. The installer never installs/restores a
   listed rule and prunes its receipt row; lint-gate skips it. lint-rules/README.md (txt002 row,
   CONV010 prefix note) now says list-then-delete, with an "Opting out of a rule" section.
3. The installer prunes receipt rows for rules no longer in MXTK_LINT_RULES /
   MXTK_LINT_RULES_CONFIGURABLE, so a retired rule is not "gone" forever.
4. skills/learned-mdl-preflight.md row 28 cut to trigger + rule + one-line failure (60 words,
   was ~230); the narrative stays in BUG-156.

Fixture tests/wave2/test-lint-receipt-revert.sh T9-T11 (INSTALLER= swaps the installer):
T9 uses a genuine stock body (toolkit entity_business_key minus header, md5 d66528c9 = the
v0.24.0/v0.25.0 stock hash in STOCK-HASHES.txt) under a scratch toolkit that does not list the
hash and whose rule has moved off stock. 38/38 here; 9 fail with master's installer, 21 with
master's installer + lint-gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…-SEEN, not a rise (#305 review)

A rule sync-project.sh installs after the baseline has no baseline count, so its first hit
compared against 0: every exec logged LINT ROSE and exited 1 until someone re-baselined, and
the Lint Batch 3 rules fire on ordinary text (TBD, XXX, v1.2, "12 Oct 2026").

- project-bin/lint-gate.sh: a rule absent from the baseline counts is FIRST-SEEN when it is a
  .star rule in .claude/lint-rules/ (RULE_ID = "...") the baseline did not know: not in the new
  `star_rules` list --update-baseline now writes, or, for an older baseline, its .star file is
  newer than the baseline. Printed as `FIRST-SEEN <rule> N finding(s) — new rule, first seen:
  review and re-baseline with --reason`; exit 0 when nothing else rose; verdict
  PASS-FIRST-SEEN. Re-baselining it needs --reason (it raises a count), after which it is an
  ordinary ratcheted rule. Built-in mxcli rules and .star rules known at baseline with zero
  findings still count as a rise (a baseline omits zero-count rules).
- project-bin/exec.sh: the rc-0 row carries `FIRST-SEEN: <rule> (N) — review, re-baseline with
  --reason`.
- tests/wave2/test-lint-gate-first-seen.sh: real toolkit .star files, LINT_JSON input; 17/17
  here, 8 fail on master's lint-gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Measured with LC_ALL=C on the branch after master (#313) was merged in:
79,997 -> 76,259 (-3,738); the old line used pre-#313 figures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Review put #308 on hold over it: the legacy fallback compares .star mtimes, which checkout,
sync and the #264 remedy all refresh, so real rises (bundled SEC004-SEC009 included) passed
rc 0; and even with star_rules a first-seen rule stayed rc 0 at any count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…n every exec

- SEC014 with no external_roles reports nothing on exec; bin/sync-project.sh warns on every
  sync via new bin/lib/sec014-notice.sh (exact config lines, "none" only on the user's answer
  recorded in PROJECT.md, re-baseline right after configuring). Fixture
  tests/wave2/test-sec014-notice.sh (13 cases).
- Remaining SEC014 _rule messages (list, blank, unknown role, not FULL) fit lint-gate's
  160-char print and name the place to fix it.
- CONV009's "all flows at 0 objects" self-check needs activities_for() to find a real object
  in a zero-count flow, so an early greenfield model is not reported blind (+fixture).
- README / TXT002 header: turning a toolkit rule off is `rules: <ID>: enabled: false` in
  lint-config.yaml (verified on mxcli 0.26.0), never deleting the file; ISO dates are a
  toolkit opinion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…needs a reason and refuses stock rules (#264 review 2)

- The REVERTED path (installer restore and lint-gate BLIND) applies only to
  MXTK_LINT_RULES_STOCK, the file names `mxcli init` seeds. A headerless toolkit-only rule
  (ux001, txt*) or configurable conv020 is a local edit: still LOCALLY MODIFIED / configured,
  never overwritten, not BLIND. The receipt row gains a kind column (stock | toolkit |
  configurable) so lint-gate, which has no manifest, can tell; rows without one are healed by
  the next sync and not checked until then. A receipt-listed rule that is missing is still
  reported for every kind.
- One header test in both: anchored `^# mxtk-lint-rule:` (lint-gate used a substring match).
- .claude/lint-rules-disabled.txt: each line needs `# reason: …` (a user decision, asked in chat
  and recorded in PROJECT.md); a line with no reason, or naming a stock-seeded rule, is ignored
  with a warning by both the installer and lint-gate. The BLIND message no longer suggests
  opting out. lint-rules/README.md rewritten to match (CONV010 cannot be opted out).
- The installer no longer advises adding a project's hash to STOCK-HASHES.txt.
- tests/wave2/test-lint-receipt-revert.sh: kinds in the receipt (T0), reasonless and reasoned
  opt-out (T10), stock opt-out refused (T12), headerless ux001 and configured headerless conv020
  not overwritten and not BLIND (T13, T14). 51/51 here; 14 fail with master's installer, 25 with
  master's installer and lint-gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
… backups, honest restore label (#264 re-review)

- Opt-out reason: the installer now accepts `#\s*reason:` like lint-gate (it required
  `# reason:` exactly, so `#reason: x` was honoured by one tool and ignored by the other).
- LINT_RULES_REVERTED_OK=1 no longer gets past the --update-baseline refusal: the override is
  for looking at counts, and baselining stock counts is how the five-day field case went in.
- `.reseeded-<ts>` backups are capped at the newest 3 per rule.
- A stock-seeded rule restored because its header is gone and its body matches no known stock
  hash is labelled "header missing — restored from toolkit, your copy kept as <file>", not
  "REVERTED TO STOCK": a newer mxcli's seed and a hand edit that lost the header look the same.
- tests/wave2/test-lint-receipt-revert.sh: `#reason:` case, override + --update-baseline,
  backup cap, label. 55/55 here; each new case fails on the previous commit's scripts; 28 fail
  on master's installer + lint-gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
A pristine scaffold has not chosen its external roles yet, and test-bug12-sync.sh T0 requires
a pristine sync to exit 0 with no warning under --strict (61/61 with this change; the warn()
version failed T0). Wording aligned in the rule header, README, CHANGELOG and backlog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…ory; SEC014 lint-gate note

- Merge origin/fix/lint-receipt-check; README conflict resolved into one "Opting out of a rule":
  `enabled: false` in lint-config stops reporting (preferred, file stays);
  .claude/lint-rules-disabled.txt with `# reason:` stops installing toolkit rules and refuses
  stock ones (conv009 added to that list); never delete the file. TXT002 header and every
  rule's "remove this rule deliberately" suggestion say the same.
- project-bin/lint-gate.sh: one uncounted stderr note while SEC014 is installed and undecided
  (no exit-code change, no lint row).
- bin/lib/sec014-notice.sh and lint-gate count external_roles / enabled: false only under the
  SEC014 key. test-sec014-notice.sh 24 cases.
- SEC014 enabled: false documented as the same user decision as "none".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…09 with maxObjects option (closes #268 #267)

Review: MERGE (review + 2 fix rounds + confirmation) — unset SEC014 never blocks exec (sync NOTE + lint-gate note); one opt-out story with #308; field run on mxcli 0.26: SEC014 1 true positive, CONV009 counts unchanged at default.
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
PR #312 makes lint-gate.sh print a non-blocking `note: SEC014 not
configured` on stderr. run_lint_gate captured that output and showed it
only on failure, so the note never appeared on a clean exec. Always echo
that one line; exit code unchanged. Fixture: stub lint-gate with the
note on stderr and rc 0 -> exec output shows it (fails on the prior head).

Refs #287

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau added a commit that referenced this pull request Oct 14, 2026
)

Every constituent PR was reviewed by a fresh agent that did not write it (CLAUDE.md "Merging a PR"); only MERGE verdicts joined. CI green on each constituent head and on this head (40814fc); full suites run on the branch.

Review lines (per merged PR):
- #315 context trim — MERGE; 360/385 removed lines found verbatim in their new homes, rest read by hand; baseline 79,997 -> 76,259.
- #316 independent-review + word-budget rules — MERGE-AFTER-FIX (3 rounds), fixes applied as specified.
- #318 page-fidelity displayed text — MERGE after fix round + confirmation.
- #317 interaction-model skill (#291 part 1) — MERGE after fix round + confirmation; no client names leaked.
- #321 PROOF-OF-LOOK per section; final-look not due before derived Stage 6 — MERGE after fix round + confirmation.
- #307 graph-sweep guard; per-BRD conformance ledgers — MERGE after two fix rounds + confirmation.
- #325 extraction-quality section; token-burn fixes — MERGE after fix round + confirmation.
- #310 e2e OQL by alias, negative-span liveness, APP-DOWN, #302 credential regression fix — MERGE after fix round + 3 confirmations.
- #323 inbox promotions — MERGE after fix round + confirmation.
- #308 lint gate BLIND on reverted stock rules — MERGE (FIRST-SEEN reverted) after two fix rounds + confirmation.
- #328 pipefail + grep -q flake — MERGE; 0/30 flakes vs 15/100 on master. Follow-up: run-all resolves bin/lib/ subjects (reviewed, MERGE).
- #319 test-stack-up command-line ownership proof — MERGE after fix round + 2 confirmations.
- #327 change-intake core + on-demand detail; CRLF-safe change log — MERGE after fix round + confirmation.
- #320 follow-ups to #290/#292; Dispatching split out — MERGE after fix round + confirmation.
- #326 status delivery rounds (existing-app mode) — MERGE after fix round + confirmation.
- #312 lint batch 4: SEC014, toolkit CONV009 — MERGE after review + 2 fix rounds + confirmation; field run on mxcli 0.26.
- #324 exec.sh change classes, shared stale-lock takeover, SEC014 note echo — MERGE (joint review); follow-up owed for a takeover race after a mid-takeover crash.
- #322 MxTest after-startup guard, mxcli-run pid check — MERGE (joint review).
- #314 doctor property-set exit code; sync announces its bound — MERGE (joint review).
- #330 review follow-ups (exit 4/5 docs, pipefail, NUL strip) — MERGE-AFTER-FIX, fixes applied; takeover re-check dropped.

Field runs are still owed for most instruments in this batch (stated in each PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau

Copy link
Copy Markdown
Owner Author

Landed on master as part of the integration squash #329 (commit 31e90ae), after independent review — its Review: line is in that commit message. Closing as superseded.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants