From ddb65b32d2b9580ebadb72063fe93c6562597b39 Mon Sep 17 00:00:00 2001 From: Derek Miller <1340081+derek-miller@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:39:53 -0500 Subject: [PATCH 1/2] Close the three remaining local-config bypasses in the typecheck gate --configpath displaces individual settings rather than whole tables, so a suppression knob is only closed if the committed config names it. The previous four were found by guessing at candidates a round at a time. This set instead comes from enumerating what the server actually reads: grep -rhoE "config\.get\([^,]*, *'Lua\.[A-Za-z.]+'" \ script/core/diagnostics/*.lua script/provider/diagnostic.lua That is 17 keys on 3.19.0, of which six are live bypasses: globalsRegex and enableScheme join the four already declared under diagnostics, and special sits under runtime. enableScheme is the serious one. It decides whether a document is diagnosed at all rather than suppressing a single code, so a local ["git"] silences the entire check. Its default is ["file"], which also makes it the one key where declaring [] is not the fix, since an empty list silences the check just as completely. Verified against a planted return-type-mismatch: a hostile ["git"], a globalsRegex of [".*"] and a runtime.special all leave the finding in place, as does disable, which guards the earlier four. .gitignore widens to .luarc.json* so the .jsonc twin cannot be committed either. --- .gitignore | 2 +- .luarc-typecheck.json | 9 +++++++-- CLAUDE.md | 26 ++++++++++++++++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 2a291a0..c47579c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,4 @@ luac.out build/ # Personal language-server config; typecheck pins its own via --configpath -.luarc.json +.luarc.json* diff --git a/.luarc-typecheck.json b/.luarc-typecheck.json index 0ced7ac..b686efe 100644 --- a/.luarc-typecheck.json +++ b/.luarc-typecheck.json @@ -1,6 +1,7 @@ { "runtime": { - "version": "LuaJIT" + "version": "LuaJIT", + "special": {} }, "workspace": { "useGitIgnore": false, @@ -21,6 +22,10 @@ "enable": true, "disable": [], "severity": {}, - "globals": [] + "globals": [], + "globalsRegex": [], + "enableScheme": [ + "file" + ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 60793cd..8a1072b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,12 +72,26 @@ deprecation check the moment the server assumes a newer language. Libraries with such shims, lua-noiseprotocol and lua-bthome-ble, are indifferent to the key. `--configpath` displaces each individual setting the committed config declares, -not each table, so a suppression knob is only closed if it is named. `diagnostics` -therefore declares four: `enable`, `disable`, `severity` and `globals`. Each was -measured as a live bypass with a planted probe, `enable: false` silencing the check -entirely and the rest suppressing individual codes, and each is a no-op on a clean -tree. Anything under `diagnostics` not in that list is still reachable from a local -`.luarc.json`, so add it here rather than assume the list is complete. +not each table, so a knob is only closed if it is named. The candidate set is not a +matter of taste: `cli/check_worker.lua` derives `--check` suppression from +`diagnostics.disable` and `diagnostics.severity`, and every other vector is read by +a checker or the provider, so it can be enumerated with + + grep -rhoE "config\.get\([^,]*, *'Lua\.[A-Za-z.]+'" \ + script/core/diagnostics/*.lua script/provider/diagnostic.lua + +Of the 17 keys that turns up on 3.19.0, six were measured as live bypasses and are +declared here: `enable`, `disable`, `severity`, `globals`, `globalsRegex` and +`enableScheme` under `diagnostics`, plus `special` under `runtime`. + +`enableScheme` is the dangerous one and the reason "declare it empty" is not a rule +to apply blindly. It gates whether a document is diagnosed at all rather than +suppressing a code, its default is `["file"]`, and declaring `[]` silences the +entire check exactly as a local `["git"]` would. It is declared as `["file"]`. + +Any setting this file does not name, under any table, is still reachable from a +local `.luarc.json`. Re-run the enumeration above when upgrading the server rather +than assuming this list stayed complete. The server version is not pinned locally, though. `install-deps` takes whatever Homebrew has while CI pins 3.19.0, so compare the version the target prints if a From 32eef1fa5ff96acb70c71a2140c2c2457afe0df9 Mon Sep 17 00:00:00 2001 From: Derek Miller <1340081+derek-miller@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:54:45 -0500 Subject: [PATCH 2/2] Close the file-loading and per-code bypasses the grep could not reach Review found three more live vectors, and they expose two distinct holes in how the earlier set was derived. runtime.plugin is invisible to the grep by construction. It is read in script/plugin.lua, outside the diagnostics paths the enumeration covers, and check_worker.lua does require "plugin", so an OnSetText returning an empty edit blanks every file and the check passes having analysed nothing. The file scope of that grep is the shape of its blind spot. neededFileStatus and groupFileStatus were in the enumeration and were dismissed as inert, which was a measurement error rather than a scope one. downgrade_checks_to_opened force-overwrites only codes whose default status is Any, so everything defaulting to Opened stays under local control, which is exactly the type-check group. An undefined-global probe reports the key as inert; a return-type-mismatch probe shows groupFileStatus taking out the whole type-check group in one line. Immunity is per-code, so one probe does not measure a key. Verified against a planted return-type-mismatch, all holding at 1 problem and exit 1: runtime.plugin blanking every file, groupFileStatus type-check None, neededFileStatus return-type-mismatch None, enableScheme ["git"], globalsRegex [".*"], disable, runtime.special, preloadFileSize 0. Clean tree still reports no problems in all five repos. pluginArgs, groupSeverity, maxPreload and preloadFileSize are declared as belt and braces rather than measured bypasses. groupSeverity relabels a finding that is still counted and still exits non-zero, and preloadFileSize 0 fails loud rather than hiding anything. CLAUDE.md now says the grep is a floor rather than the candidate set, gives the separate enumeration for file-loading and source-rewriting vectors, and records both traps: empty is only inert if the read site falls back to a default, and immunity is per-code. Also corrects the count, which said six and listed seven. --- .luarc-typecheck.json | 11 ++++++++-- CLAUDE.md | 50 +++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/.luarc-typecheck.json b/.luarc-typecheck.json index b686efe..dece3fc 100644 --- a/.luarc-typecheck.json +++ b/.luarc-typecheck.json @@ -1,7 +1,9 @@ { "runtime": { "version": "LuaJIT", - "special": {} + "special": {}, + "plugin": "", + "pluginArgs": [] }, "workspace": { "useGitIgnore": false, @@ -16,7 +18,9 @@ "build", "dist", "node_modules" - ] + ], + "maxPreload": 5000, + "preloadFileSize": 500 }, "diagnostics": { "enable": true, @@ -24,6 +28,9 @@ "severity": {}, "globals": [], "globalsRegex": [], + "neededFileStatus": {}, + "groupFileStatus": {}, + "groupSeverity": {}, "enableScheme": [ "file" ] diff --git a/CLAUDE.md b/CLAUDE.md index 8a1072b..b1ca9bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,26 +72,48 @@ deprecation check the moment the server assumes a newer language. Libraries with such shims, lua-noiseprotocol and lua-bthome-ble, are indifferent to the key. `--configpath` displaces each individual setting the committed config declares, -not each table, so a knob is only closed if it is named. The candidate set is not a -matter of taste: `cli/check_worker.lua` derives `--check` suppression from -`diagnostics.disable` and `diagnostics.severity`, and every other vector is read by -a checker or the provider, so it can be enumerated with +not each table, so a knob is only closed if it is named. Suppression keys can be +enumerated from the diagnostics read sites: grep -rhoE "config\.get\([^,]*, *'Lua\.[A-Za-z.]+'" \ script/core/diagnostics/*.lua script/provider/diagnostic.lua -Of the 17 keys that turns up on 3.19.0, six were measured as live bypasses and are -declared here: `enable`, `disable`, `severity`, `globals`, `globalsRegex` and -`enableScheme` under `diagnostics`, plus `special` under `runtime`. - -`enableScheme` is the dangerous one and the reason "declare it empty" is not a rule -to apply blindly. It gates whether a document is diagnosed at all rather than -suppressing a code, its default is `["file"]`, and declaring `[]` silences the -entire check exactly as a local `["git"]` would. It is declared as `["file"]`. +Treat that as a floor, not a ceiling: its file scope is the shape of its blind +spot. Anything that gates file loading or rewrites source before analysis is read +elsewhere, and has to be enumerated separately from `script/plugin.lua` and +`script/workspace.lua`. `runtime.plugin` is the case that matters, and the grep +cannot surface it by construction. `check_worker.lua` does `require 'plugin'`, so +an `OnSetText` returning an empty edit blanks every file in the repo and the check +passes having analysed nothing. + +Two traps decide how a key gets declared, and neither is answered by the key's +type: + +Empty is not always inert, so read the read site. `neededFileStatus` and +`groupFileStatus` are per-key lookups that fall back to the built-in default, so +`{}` leaves behaviour untouched. `enableScheme` defaults to `["file"]`, which makes +`[]` silence the whole check exactly as a local `["git"]` would. It is declared as +`["file"]` for that reason. + +Immunity is per-code, so one planted probe does not measure a key. +`check_worker.lua`'s `downgrade_checks_to_opened` force-overwrites only codes whose +default status is `Any`, leaving everything defaulting to `Opened` under local +control, which is precisely the type-check group this gate exists for. An +`undefined-global` probe therefore reports `neededFileStatus` as inert while a +`return-type-mismatch` probe shows it silencing the check. Probe with a type-check +code. + +Declared here as measured live bypasses: `enable`, `disable`, `severity`, +`globals`, `globalsRegex`, `enableScheme`, `neededFileStatus` and `groupFileStatus` +under `diagnostics`, plus `special` and `plugin` under `runtime`. `pluginArgs`, +`groupSeverity`, `maxPreload` and `preloadFileSize` are declared as belt and +braces rather than measured bypasses: `groupSeverity` relabels a finding that is +still counted and still exits non-zero, and `preloadFileSize: 0` fails loud rather +than hiding anything. Declaring them costs nothing and saves re-deriving that. Any setting this file does not name, under any table, is still reachable from a -local `.luarc.json`. Re-run the enumeration above when upgrading the server rather -than assuming this list stayed complete. +local `.luarc.json`. Re-run both enumerations when upgrading the server rather than +assuming this list stayed complete. The server version is not pinned locally, though. `install-deps` takes whatever Homebrew has while CI pins 3.19.0, so compare the version the target prints if a