From 1975106e9aa221af629da749ae95c572a8a84821 Mon Sep 17 00:00:00 2001 From: Hermes SysOp Date: Sat, 14 Nov 2026 18:04:08 +0100 Subject: fix(tooling): two checkers stopped manufacturing false audit signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both defects are parser bugs in tooling, and both were producing a signal the operator sees on every run. Plan 2026-11-13_062052, implemented as written. 1. `help coverage` WARN was a quote-pairing artefact (every run, always 367/368). `newos4-maintain.py:238-239` captured `this._baseWhitelist = [...]` with a regex and then pulled `'([^']+)'` out of it. A JS comment inside that array literal contains the apostrophe in "don't" (terminal.js:16), which desynchronised pairing: it matched forward to the opening quote of 'ui' and the pseudo-command `t work"\n // …` joined the audited set — so a genuine undocumented command was indistinguishable from parser debris. Fix: strip `/* */` and `//` before extracting, and keep only tokens matching `^[a-z][a-z0-9_-]{0,40}$`. The same shape bug existed for `const internals = [...]` and is fixed by the same helper. The WARN list is now single-line (newlines collapsed) so one bad token cannot reflow the digest. Proven both ways: injected `'zzz-probe-cmd'` into the whitelist -> `WARN: help coverage 367/368 — undocumented: zzz-probe-cmd`; reverted (md5 verified) -> `PASS: help coverage 367/367`. 2. Auto-review "still present in shipped code" fired on generic tokens. `STEER-20261112-195037-1eed [high] cmds.indexOf is not a function` was approved because `identifiers()` kept `indexOf` and `function` (hits: 36 and 74 files) and R4 approved on `any()` present token. `isCommand` — the only name that identifies the fault — was never considered, because stack frames were not parsed at all. It does not exist anywhere in the tree. Fix: JS keywords/builtins/DOM handler names join the skip set; `stack_functions()` extracts frame names (dropping `onclick`, `anonymous`, `eval`); R4 now requires a **distinctive** token (>=6 chars, present in modules/, not generic); and a stack whose TOP frame is `` is classified as generated code (`js-run` / `new Function`), which can never justify "still present in shipped source". The bottom `HTMLButtonElement.onclick (…/main.html)` frame proves only that a click started it — deciding on "does any frame name a real file" got this wrong once already. Proven: `identifiers('cmds.indexOf is not a function')` -> `[]`; `distinctive(['indexOf','function'], {...})` -> `[]`; the real report replayed into a fixture reports `nothing provable (1 left for the operator)` with **no** decision row written; production dry-run `nothing provable (0 left)`. `decisions.jsonl` still carries exactly 1 row for that report (append-only history untouched). Regression cover: `python3 scripts/newos4-feedback-autoreview.py --self-test` -> 6/6. maintain: node tests 42/42, help coverage 367/367, result OK. --- scripts/newos4-feedback-autoreview.py | 125 ++++++++++++++++++++++++++++++++-- scripts/newos4-maintain.py | 27 ++++++-- 2 files changed, 141 insertions(+), 11 deletions(-) diff --git a/scripts/newos4-feedback-autoreview.py b/scripts/newos4-feedback-autoreview.py index 2021de3..1bb6d25 100755 --- a/scripts/newos4-feedback-autoreview.py +++ b/scripts/newos4-feedback-autoreview.py @@ -75,13 +75,73 @@ def approved_today(): return n +# Generic vocabulary: JS keywords, builtins and DOM handler names. A token in here identifies the +# LANGUAGE, not the defect, so it can never justify "still present in shipped code" (2026-11-14). +# Before this, `cmds.indexOf is not a function` produced present_tokens ["indexOf","function"] with +# hits {indexOf: 36, function: 74} and became a HIGH steering directive for a function that does not +# exist anywhere in the tree (plan 2026-11-13_062052 §2). +SKIP = {'error', 'failed', 'fetch', 'undefined', 'cannot', 'reading', 'properties', 'uncaught', + 'invalid', 'unexpected', 'token', 'https', 'http', 'com', 'org', 'null', 'true', 'false', + # language / builtins + 'function', 'return', 'indexof', 'lastindexof', 'tolowercase', 'touppercase', 'tostring', + 'length', 'push', 'pop', 'slice', 'splice', 'split', 'join', 'replace', 'concat', 'shift', + 'map', 'filter', 'reduce', 'foreach', 'json', 'parse', 'stringify', 'object', 'array', + 'promise', 'async', 'await', 'then', 'catch', 'finally', 'typeof', 'constructor', + 'prototype', 'valueof', 'hasownproperty', 'assign', 'keys', 'entries', 'values', + # host objects and event plumbing + 'window', 'document', 'console', 'navigator', 'localstorage', 'settimeout', 'setinterval', + 'addeventlistener', 'removeeventlistener', 'queryselector', 'onclick', 'onload', 'onerror', + 'eval', 'anonymous', 'innerhtml', 'textcontent', 'classlist', 'dispatchevent'} + +# `at isCommand (:34:17)`, `at t.executeCommand (:66:12)`, +# `at HTMLButtonElement.onclick (…/main.html:954:454)` -> the function name is the only frame part +# that can identify code, and it was never parsed before. +STACK_FN = re.compile(r'at\s+(?:async\s+)?([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\(') + + def identifiers(msg): """Distinctive names in an error message: drop numbers/quoted parts, keep real tokens.""" cleaned = re.sub(r'["\'].*?["\']', ' ', str(msg)) cleaned = re.sub(r'0x[0-9a-f]+', ' ', cleaned, flags=re.I) - skip = {'error', 'failed', 'fetch', 'undefined', 'cannot', 'reading', 'properties', 'uncaught', - 'invalid', 'unexpected', 'token', 'https', 'http', 'com', 'org', 'null', 'true', 'false'} - return [t for t in IDENT.findall(cleaned) if t.lower() not in skip][:4] + return [t for t in IDENT.findall(cleaned) if t.lower() not in SKIP][:4] + + +def stack_functions(stack): + """Function names from stack frames, most specific last segment, generics removed.""" + out = [] + for name in STACK_FN.findall(str(stack or '')): + base = name.split('.')[-1] + if not base or base.startswith('<') or base.lower() in SKIP: + continue + if base not in out: + out.append(base) + return out[:4] + + +ANON_FRAME = re.compile(r'at\s+[^\n]*\((?::\d+:\d+)?\)') +REAL_FRAME = re.compile(r'at\s+[^\n]*\((?:https?:)?//?[^)\s]+\.(?:js|mjs|html)(?:[:?][^)\s]*)?\)') + + +def stack_generated(stack): + """True when the stack describes runtime-generated code rather than a shipped file. + + `` frames come from `new Function(...)` module loading and the `js`/`js-run` eval + path — a snippet someone pasted, not our source. A function name from such a frame says nothing + about shipped code, which is exactly how `cmds.indexOf` became a HIGH directive (2026-11-12). + Rule: anonymous frames present AND no frame pointing at a real .js/.html file. + """ + frames = [ln.strip() for ln in str(stack or '').splitlines() if ln.strip().startswith('at ')] + if not frames: + return False + # The ORIGIN is the top frame. The bottom `HTMLButtonElement.onclick (…/main.html)` frame proves + # only that a click started it, so deciding on "does any frame name a real file" got this wrong. + top = frames[0] + return '' in top or not REAL_FRAME.search(top) + + +def distinctive(tokens, hits): + """Tokens specific enough to justify 'still present': >=6 chars, in modules/, not generic.""" + return [t for t in tokens if len(t) >= 6 and hits.get(t) and t.lower() not in SKIP] def code_hits(tokens): @@ -181,6 +241,9 @@ def classify(rep, dec, fps, cap_left): return 'reject', 'noise: test artefact or client-side network failure', {'match': noise_hit.group(0)} tokens = identifiers(msg) + for fn in stack_functions(stack): # the frame names are the most specific identifiers we get + if fn not in tokens: + tokens.append(fn) hits = code_hits(tokens) if tokens else {} present = [t for t in tokens if hits.get(t)] absent = [t for t in tokens if not hits.get(t)] @@ -197,11 +260,21 @@ def classify(rep, dec, fps, cap_left): 'absent_tokens': absent} # R4 still-present: real stack error whose identifiers still exist in shipped code. - if stack and present and cap_left > 0: + distinct = distinctive(present, hits) + if stack and stack_generated(stack): + return None, 'stack frames are (generated code — js-run / new Function), not shipped source — needs a human', { + 'present_tokens': present, 'generated_stack': True} + if stack and distinct and cap_left > 0: return 'approve', 'still present in shipped code (stack + identifiers found)', { + 'present_tokens': present, 'distinctive_tokens': distinct, + 'hits': {t: hits[t] for t in present}} + if stack and distinct and cap_left <= 0: + return None, 'daily auto-approve cap reached — left for the operator', {'present_tokens': distinct} + if stack and present and not distinct: + # Generic-only match (indexOf/function/length...): a human must decide, because "the word + # appears in 74 files" is not evidence the defect is in shipped code. + return None, 'only generic identifiers in message — needs a human', { 'present_tokens': present, 'hits': {t: hits[t] for t in present}} - if stack and present and cap_left <= 0: - return None, 'daily auto-approve cap reached — left for the operator', {'present_tokens': present} # R5 stale artefact: one occurrence that never came back, and no stack to act on. # Distinct from R2 (identifier removed) — here the emitter is still there but the symptom is not. @@ -223,6 +296,43 @@ def classify(rep, dec, fps, cap_left): 'tokens': tokens, 'present': present, 'stack': stack} +SELF_CASES = [ + ("identifiers drop generic words", + identifiers('cmds.indexOf is not a function'), []), + ("stack names are the specific identifiers", + stack_functions("at isCommand (:34:17)\n" + " at t.executeCommand (:66:12)\n" + " at HTMLButtonElement.onclick (https://x/main.html:954:454)"), + ['isCommand', 'executeCommand']), + ("generics cannot justify 'still present'", + distinctive(['indexOf', 'function'], {'indexOf': 36, 'function': 74}), []), + ("a >=6-char shipped name still can", + distinctive(['indexOf', 'executeCommand'], {'indexOf': 36, 'executeCommand': 12}), + ['executeCommand']), + ("anonymous-origin stack is generated code", + stack_generated("TypeError: x\n at isCommand (:34:17)\n" + " at HTMLButtonElement.onclick (https://x/main.html:954:454)"), True), + ("shipped-origin stack is not generated", + stack_generated("TypeError: x\n at Terminal.dispatchCommand (https://x/core-bundle.js:1:2)"), + False), +] + + +def self_test(): + """Regression cover for the 2026-11-12 false positive (plan 2026-11-13_062052 §2). + + `--self-test` — run it after touching the identifier or stack parsers. A false "still present" + turns a non-defect into a HIGH steering directive, which is worse than a missed report. + """ + bad = 0 + for label, got, want in SELF_CASES: + ok = got == want + bad += 0 if ok else 1 + print(('PASS ' if ok else 'FAIL ') + label + ('' if ok else ' got=%r want=%r' % (got, want))) + print('%d/%d self-tests pass' % (len(SELF_CASES) - bad, len(SELF_CASES))) + return 0 if not bad else 1 + + def main(): global REPORTS, MODULES, DECISIONS, ROOTDIR, ERRORS ap = argparse.ArgumentParser() @@ -234,7 +344,10 @@ def main(): ap.add_argument('--cap', type=int, default=None) ap.add_argument('--errors', default=ERRORS, help='debug error store (fixtures)') ap.add_argument('--include-plans', action='store_true', help='never used in production') + ap.add_argument('--self-test', action='store_true', help='run identifier/stack regression checks and exit') a = ap.parse_args() + if a.self_test: + return self_test() ROOTDIR = a.root REPORTS, MODULES, DECISIONS = a.reports_dir, a.modules, a.decisions ERRORS = a.errors diff --git a/scripts/newos4-maintain.py b/scripts/newos4-maintain.py index acb81c3..fa20dfa 100644 --- a/scripts/newos4-maintain.py +++ b/scripts/newos4-maintain.py @@ -235,10 +235,24 @@ def check_help_coverage(): check makes the gap visible on every maintenance run. """ cmds, term = set(), open(os.path.join(ROOT, 'modules/core/terminal.js'), encoding='utf-8').read() - for m in re.finditer(r'this\._baseWhitelist = \[(.*?)\];', term, re.S): - cmds.update(re.findall(r"'([^']+)'", m.group(1))) - for m in re.finditer(r'const internals = \[(.*?)\];', term, re.S): - cmds.update(re.findall(r"'([^']+)'", m.group(1))) + + # A JS comment inside one of these array literals used to desynchronise quote pairing: the + # apostrophe in "don't" (terminal.js:16) matched forward to the opening quote of 'ui', and the + # pseudo-command `t work"\n // …` joined the audited set — 367/368 forever, so a genuine gap was + # indistinguishable from the artefact (plan 2026-11-13_062052, fixed 2026-11-14). + def _tokens(pattern): + out = set() + for m in re.finditer(pattern, term, re.S): + body = re.sub(r'/\*.*?\*/', '', m.group(1), flags=re.S) # block comments + body = re.sub(r'//[^\n]*', '', body) # line comments + for tok in re.findall(r"'([^']+)'", body): + # Shape guard: a real command is a lowercase word. Anything else is parser debris. + if re.fullmatch(r'[a-z][a-z0-9_-]{0,40}', tok): + out.add(tok) + return out + + cmds.update(_tokens(r'this\._baseWhitelist = \[(.*?)\];')) + cmds.update(_tokens(r'const internals = \[(.*?)\];')) cmds.update(re.findall(r"cmd === ['\"]([a-z0-9_-]+)['\"]", term)) for p in glob.glob(os.path.join(ROOT, 'modules/**/*.js'), recursive=True): if 'bundle' in p or '/test/' in p or '.bkp' in p: @@ -252,9 +266,12 @@ def check_help_coverage(): if first and re.fullmatch(r'[a-z][a-z0-9_-]{0,40}', first[0].lstrip('./')): documented.add(first[0].lstrip('./')) missing = sorted(cmds - documented) + # Tokens are shape-guarded above, but collapse whitespace anyway: one bad token must never be able + # to reflow the whole digest (plan task 3). + shown = ' | '.join(m.replace('\n', ' ') for m in missing[:8]) add('PASS' if not missing else 'WARN', f'help coverage {len(cmds) - len(missing)}/{len(cmds)} commands' - + ('' if not missing else f' — undocumented: {", ".join(missing[:8])}')) + + ('' if not missing else f' — undocumented: {shown}')) def check_regression_tests(): """Every node-runnable test under modules/test/ must pass (2026-10-27). -- cgit v1.2.3