Skip to content

fix(lint-gate): reverted-to-stock rules stop the run as BLIND, not a rise (#264); BUG-156 + preflight row 28 (#263) - #308

Closed
MendixMau wants to merge 13 commits into
masterfrom
fix/lint-receipt-check
Closed

MendixMau wants to merge 13 commits into
masterfrom
fix/lint-receipt-check

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Bug (#264)

When mxcli init re-seeds .claude/lint-rules/, the stock-seeded toolkit rules lose their # mxtk-lint-rule: header. project-bin/lint-gate.sh never read .claude/.mxtk-lint-receipt, so the re-seed went unnoticed. Two field cases:

  • CONV010 went from 26 to 169, and the rise was blamed on the next exec, a pages-only script.
  • In another project the re-seed sat for five days, and the baseline absorbed the stock counts.

Fix

project-bin/lint-gate.sh

  • Check before linting. It reads the receipt. A rule the receipt marks stock counts as reverted when its md5 no longer matches and its header is gone. The header test is the anchored ^# mxtk-lint-rule:, the same one the installer uses. A receipt-listed rule of any kind whose file is missing is also reported.
  • What it prints. It names the files and prints the remedy, sync-project.sh <root>, with --upgrade-lint-rules all as the fallback. It then exits 1, the existing blind code, with BLIND — lint rules reverted to stock.
  • Recorded state. lint-last.json gets the verdict BLIND-RULES-REVERTED and no total.
  • Baseline refused. --update-baseline is refused over reverted rules, even under the look-anyway override LINT_RULES_REVERTED_OK=1.

project-bin/exec.sh

  • Logs this as a LINT BLIND row naming the files, not as LINT ROSE.

bin/lib/install-lint-rules.sh

  • Restores what lint-gate flags. A stock-seeded rule with a receipt row and no header is restored even when its body matches no known stock hash. That covers a seed from a newer mxcli, or a hand edit that also lost the header. It is labelled header missing — restored from toolkit, your copy kept as <rule>.reseeded-<ts>, and only the newest 3 copies are kept. Before this fix it was reported LOCALLY MODIFIED and never written, so lint stayed BLIND forever.
  • Leaves local edits alone. A headerless toolkit-only rule (ux001, txt*) or a headerless configured conv020 counts as a local edit. It is never overwritten and never makes the run BLIND.
  • Receipt maintenance. Receipt rows now carry a kind: stock, toolkit or configurable. Old receipts gain kinds on the next sync. Rows for rules the toolkit no longer ships are pruned.
  • Opt-out. .claude/lint-rules-disabled.txt takes one line per rule: <rule> # reason: …. Both tools accept #\s*reason:. Disabling a rule is the user's decision, asked in chat and recorded in PROJECT.md. Both tools ignore, with a warning, a line with no reason and a line naming a stock-seeded rule.

Docs

  • lint-rules/README.md: new "Opting out of a rule" section, replacing "just delete txt002".
  • agents/gate-agent.md and skills/lint-that-actually-runs.md: one sentence each.

Also in this PR (Refs #263, learned-rule half)

  • BUG-156 in bug-logs/mxcli-bugs.md.
  • STOP row 28 in skills/learned-mdl-preflight.md (60 words).

Dependency

origin/chore/baseline-word-trim (#315) is merged into this branch. #315 must land first.

Evidence

tests/wave2/test-lint-receipt-revert.sh: 55/55.

  • Real receipt. It is written by the real installer.
  • Genuine stock body. T9 uses entity_business_key with its header removed. Its md5 d66528c9 is the v0.24/v0.25 hash in STOCK-HASHES.txt, and the test runs under a scratch toolkit that does not list it.
  • Each review fix is tested: the #reason: syntax, the override plus --update-baseline, the backup cap, and the restore label. Each of these cases fails on the previous commit's scripts.
  • Against master: 28 cases fail on master's installer + lint-gate.

Field run: the installer side was checked by the maintainer on a copy of a v0.25.0 project (#264 comment). The new lint-gate check is proven by fixtures only.

Checks

Check Result
test-lint-receipt-revert.sh 55/55
test-lint-gate-accept.sh 20/20
tests/test-lint-delivery.sh ALL GREEN
bin/render-routing.sh --check (with #315 merged) rc 0, baseline 76,319 / 80,000
check-scripts, check-portability, check-no-private-citations, check-no-client-data, check-pr-discipline clean

No full suites were run.

Closes #264
Refs #263

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw

…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
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
claude added 10 commits October 13, 2026 14:27
…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
…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
… 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
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…s are BLIND, restored on sync (closes #264; refs #263)

Review: MERGE (HOLD on FIRST-SEEN, reverted; two fix rounds; confirmation) — restore limited to stock-seeded rules, opt-out needs a reason and refuses stock rules, baseline update refused while reverted; BUG-156 + preflight STOP 28.
MendixMau pushed a commit that referenced this pull request Oct 13, 2026
…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 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

Development

Successfully merging this pull request may close these issues.

[bug] A bundled lint-rule refresh shows up as a lint rise on the next exec

2 participants