Repository navigation
fix(infrastructure): make a missing script dependency fail loudly, and guard the class (#14041, #14867, #14518, #14172) - #14877
Merged
Conversation
This was referenced Aug 23, 2026
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
…lently (#14172) 56 scripts sourced lib/ssot-config.sh as `2>/dev/null || true`. With the failure discarded, a missing config bootstrap was indistinguishable from a successful one: every ${VAR:-literal} took its hardcoded right-hand side and the script reported success. That is how #14041 stayed invisible for as long as it did, and nothing stopped it recurring on a bad deploy, a gitignore regression or a rename. Scope decision (#14172 asked for one): change the call-site shape rather than rely on a repo guard alone. A guard only sees this checkout at PR time; it cannot see a deploy that fails to ship the file. The sibling libraries in the same scripts -- lib/db-update-classify.sh, scripts/lib/project_root.sh -- were already sourced bare, so this converges on the convention already in the tree. - 51 single-path sites: `source X 2>/dev/null || true` -> `source X || { FATAL; exit 1 }` - 5 two-path sites: only the first attempt keeps 2>/dev/null (the miss is expected -- callers sit at two depths); the last attempt is loud and fatal, replacing a fallback that re-read .env and carried on - 2 bare sites: same explicit die block, so one shape ships everywhere - die uses `return 1 2>/dev/null || exit 1` so a sourced caller returns rather than killing the parent shell (the idiom ssot-config.sh already uses) test_ssot_config_lib.sh asserted the old contract ("missing library degrades to literal"). It now asserts the inversion, and executes the blocks AS SHIPPED -- extracted with sed out of status-all-vms.sh, check_status.sh, check-health.sh and verify-ssh-security.sh -- so reintroducing `|| true` on a real script trips the test rather than passing a re-typed copy of the idiom.
… that cannot resolve (#14518) pyflakes never checks that an import TARGET resolves. It reports F401 for an unused import and F821 for an undefined name, so every script in this tree could be F821-clean -- which #14405 / PR #14504 made them -- and still die with ModuleNotFoundError on its own import block, before reaching a single line of the code that was repaired. "0 findings" is not "runs". Three shapes, per the #14518 enumeration: - stale `backend.` prefix over the autobot-backend/ tree, plus the legacy `src.`, `llm_interface` and `chat_history_manager` names: the import is rewritten to the real module and the script gains the sys.path insert the other operator entry points in this tree already use (#14129, #14507). parents[N] is computed per file from its own depth. - target exists but is not on the importing script's path: the owning directory is inserted. Two of these pointed at directories that do not exist (`shared/docker/npu-worker`, a `src/` beside the script). - undeclared third-party: `aioredis` is migrated to `redis.asyncio` -- the alias this repo already uses at autobot-backend/knowledge/base.py:20 -- so it adds no dependency; `docker` gains the same ImportError guard its five other call sites in this repo already carry. The remaining eight sites in that group were already correctly guarded optional imports, verified by AST rather than by eye, and are left alone. Also fixed, found by the sweep but outside #14518's own enumeration: four `utils.redis_database_manager` / `utils.logging_manager` imports naming a package that exists nowhere. Both symbols are re-exported from autobot_shared/redis_client.py and autobot_shared/logging_manager.py, which are the canonical entry points this repo mandates. NOT closing #14518: five sites remain, each naming a symbol that was deleted or never landed (SSHManager, add_api_routes, monitoring_compat). No import spelling reaches them -- the fix is a logic change -- so they are tracked in #14870 and carried as self-guarding exemptions rather than guessed at. Guessing would replace a loud ModuleNotFoundError with a quiet wrong answer, which is the defect this issue exists to remove. Discovered and filed rather than folded in silently: #14871 (langchain_redis undeclared, and its absence silently drops an arm of the comparison the scripts measure), #14872 (the docker SDK is imported by five production backend modules and declared nowhere), #14873 (optimize_agents forked across two modules sharing one import name).
… and reporting success (#14867, #14172) Eight deployment, setup and diagnostic scripts embed Python in heredocs and `python3 -c`. Nine of those inline imports named modules that do not exist in this tree -- there is no src/, no backend/ and no backup/ package -- so the block raised ModuleNotFoundError on every run. No linter sees any of it: the Python is a shell string, invisible to pyflakes and to any *.py sweep. The half that made it survivable is the more dangerous one. Every script discarded the failure and answered anyway, so it exited 0 having validated nothing. Both halves are fixed in all eight scripts: - 7 of 9 imports now name a real module, with a PYTHONPATH derived from each script's own location rather than a hardcoded path. - 9 of 9 swallowed failures are gone: the traceback reaches stderr and the exit status reflects it. Several summary lines ("Health check complete", "All checks passed", "Startup coordinator is ready for use") were printed unconditionally and are now gated on a failure counter. Three latent defects surfaced while doing it, each the same shape: - reindex_claude_md.sh interpolated ${PROJECT_ROOT} inside a QUOTED heredoc, so the path was the literal string -- "CLAUDE.md not found!" was guaranteed, and it returned 0. - restore_kb_backup.sh put ${REPO_ROOT}/autobot_shared on PYTHONPATH -- the package's contents, not its parent -- so every autobot_shared.* import failed silently. - test_startup_coordinator.sh opened with a Python \"\"\" docstring in a bash script, which bash executed as commands on every run, masking real errors. reindex_claude_md.sh also carries its lib/ssot-config.sh source conversion from #14172, kept with this commit because the two edits touch adjacent lines. The #14867 issue text states that validate_access_control.sh and access_control_monitor.sh were already fixed. They are NOT fixed at this base: that work is on the unmerged #14866 branch. Those two files are deliberately untouched here to avoid colliding with PR #14868, and are carried as self-guarding exemptions in the new guard. NOT closing #14867: two imports remain, naming a backup engine and an agent orchestrator accessor that were never built (zero hits repo-wide for BackupEngine, get_agent_orchestrator and get_task_specific_model). Tracked as #14875; the swallowed-failure half IS fixed for both, so what remains fails loudly. Two undeclared third-party imports found by the same sweep are #14876.
… invisible (#14041, #14172, #14518, #14867) Fixing the instances is half the job; nothing stopped any of them recurring. Three guards, one per surface the existing checks cannot see, all under repo_tests/ because autobot-infrastructure/ is in no pytest testpaths entry and in no CI pytest invocation -- a test beside the scripts would be as dormant as the defects it watches. shell_lib_sources_resolve_test.py (#14041, #14172) Every `source .../lib/*.sh` under autobot-infrastructure/ must resolve, and the decisive attempt in each `||` chain must keep its stderr and end in a block that exits non-zero. Measured against the pristine base it flags 57 of 57 sites across 56 of 56 scripts -- the exact population #14041 enumerated. infra_script_imports_resolve_test.py (#14518) Generalises first_party_imports_resolve_test.py (#14839) to the scripts tree: an import must resolve against a first-party root, the stdlib, a declared requirement, or the importing file's own directory. Flags 34 of the 42 sites present on the base; the other 8 are the tracked residue. deployment_script_imports_resolve_test.py (#14867) Extracts Python out of `python3 -c` and heredocs and resolves its imports -- a surface no linter reads at all -- and separately forbids answering a failed check with a fabricated value, which is what let #14866's monitor report 0|0|0 violations because its imports were broken. Anti-fail-open measures, because a guard that reports clean on an empty walk reproduces the bug it is watching for: - every sweep asserts discovery floors before asserting cleanliness (files walked, sites matched, inline blocks extracted, declared distributions found, first-party roots still present). The block-count floor is the load-bearing one for the shell guard: an extractor that stops recognising `python3 -c` would otherwise pass having read nothing. - skip lists match on paths RELATIVE to the tree root, never absolute, so a checkout living under a directory named .worktrees or venv cannot skip every file and report clean. - every exemption is parametrized and asserted STILL broken, and additionally asserted to still be REPORTED by the sweep. That second assertion is the positive control for each file: if a detector regresses, the exemption test goes red instead of the whole file passing vacuously. - the shell guard excludes documentation and nested shell strings structurally -- an unbalanced quote or a backslash-escaped quote, neither of which occurs in a real source statement -- rather than by naming files, so test_ssot_config_lib.sh can keep driving the very shapes it tests. A dedicated test asserts those exclusions exclude exactly what they claim. Verified by running all three against the pristine base and against this branch: 0 failures here, and on the base the shell guard fails 57 of 57 parametrized sites plus both aggregate assertions, with the other two guards red on their own populations.
4 tasks
…shipping a second one (#14867, #14877) PR #14868 (#14866) landed while this branch was open and added its own repo_tests/deployment_script_imports_resolve_test.py -- the filename #14867 predicted. Rather than keep both, this branch's copy is merged INTO the landed file (consolidate, never fork). The landed version is the survivor because it carries a call-layer check this branch's did not: an awaited sync Redis client is a failure that moves from import time to call time, which an import guard cannot see. Three things merged in from this branch's copy: - the importing script's OWN directory now counts as an import root. Several of these scripts export ${SCRIPT_DIR} on PYTHONPATH and import a sibling; without it the guard reported a false positive on test_startup_coordinator.sh, whose startup_coordinator.py sits right beside it. - test_no_inline_check_fabricates_its_own_result, which is #14867's second acceptance criterion and the half #14868 left undone: it removed the `2>/dev/null` but kept `|| echo "{}"`, so a check that cannot run still reports a value indistinguishable from a clean one. Filed as #14880 and carried as a self-guarding exemption. - seven of the nine _KNOWN_BROKEN entries are retired, because the imports they exempted are fixed on this branch. The two that remain are repointed at #14875. The new fabrication detector was wrong on its first draft: it matched `python3 -c` to end-of-line, which finds nothing for a MULTI-LINE block, because the `|| echo` sits after the closing quote several lines down. It reported zero fabricated results on a file that has three. Its own positive control -- "this exemption must still be broken" -- is what caught it, which is the reason that test exists. It now reuses the two block regexes already proven in this file. Also resolves the rebase collision on validate_access_control.sh and access_control_monitor.sh: #14866's PYTHONPATH export is kept in full, with only its `source ... 2>/dev/null || true` replaced by the #14172 fail-loud form.
mrveiss
force-pushed
the
issue-14041-script-imports
branch
from
August 23, 2026 18:57
13d2e9e to
817d7d5
Compare
mrveiss
added a commit
that referenced
this pull request
Aug 23, 2026
…s logic went (#14371) Rebased onto d70a1f9. One file overlapped: #14877 (35f48ba) converted autobot-infrastructure/shared/scripts/detect-hardcoded-values.sh's `source lib/ssot-config.sh 2>/dev/null || true` into the fail-loud abort, and #14371 retires that file. Resolved as a union of capabilities rather than a survivor-pick. The abort idiom travels; the ssot-config.sh dependency deliberately does not, and the reason is recorded in scripts/lib/hardcoded-value-rules.sh rather than left to be re-derived. ssot-config.sh `set -a`-loads the master .env and exports all of it, AUTOBOT_REDIS_PASSWORD included. The consolidated detector is a scanner whose entire output is matched source lines, printed to a public CI log and posted to pull requests: pulling a live secret into that process is a disclosure risk it gains nothing from. Its SSOT tables are detection patterns — the literals it hunts and the config key that replaces each — not runtime config it reads to work, which is why detector 2's tables were literal too, and PROJECT_ROOT now resolves from each entry point's own location, needing no .env and working on a deployed install with no checkout. What did travel is the capability: every `source` in the library and both entry points ends in an explicit failure block that exits non-zero, and an absent or empty baseline is fatal for the same reason — an unread exemption set and an empty one are indistinguishable to every caller. The hook's repo-root variable is renamed REPO_ROOT -> PROJECT_ROOT to match the spelling ssot-config.sh exports and the only one repo_tests/shell_lib_sources_resolve_test.py can expand. Stated precisely in the comment: this buys nothing today, because that guard walks `*.sh` and every hook in the directory is extensionless, so it cannot see the file at all. The spelling means the site is verified rather than silently skipped once that walk is widened. Retiring the fork takes that guard's distinct-script count from 56 to 55, its exact floor. The floor is left where it is rather than lowered for headroom, so it still catches a regressed matcher, with the retirement recorded next to it so the next legitimate retirement is a deliberate edit and not a mystery.
This was referenced Aug 23, 2026
mrveiss
added a commit
that referenced
this pull request
Aug 23, 2026
…#14371, #13984, #12986, #14034) (#14883) * fix(hooks): make the argv and git file-type filters agree, and pin it (#14034) get_staged_files' argv branch was fixed to apply its $pattern, but the two tests covering that branch still asserted the OLD behaviour — that the pattern is ignored — so the one-line fix had nothing holding it in place and the tests were red against the code they cover. Both now pin the fix, and a third asserts the property that was actually broken: the git and argv branches return the same set for the same input. pre-commit-no-module-singletons carried the identical asymmetry in Python. Its .py filter lived inside the git branch only, so CI (which passes an explicit wide-net file list as argv) fed .ts/.vue/.yml paths to ast.parse. It survived only because check_file swallows SyntaxError — a silent mis-scan rather than a loud one. The filter now applies to both branches. Audited every other get_staged_files caller and every argv-vs-git branch in hooks/, tools/lint/ and pipeline-scripts/: no further instance of the shape. * refactor(ci): one canonical backend-Python path filter, and a guard for the copies GitHub forces (#12986) The backend-Python path list was duplicated across six workflows with no source of truth. Adding a backend directory meant remembering every place, and a miss was SILENT: the gate simply stopped running on the new path, nothing failed, and the coverage quietly shrank. .github/filters/backend-python-paths.yml now defines the set once, following the frontend-paths.yml / python-paths.yml precedent already in that directory, and composes each consumer's filter from it with a YAML alias rather than a retyped copy. Five `changes` jobs load that file by path, so their inline copies are gone outright; their output key names are unchanged, so every `steps.filter.outputs.<name>` reference keeps working verbatim. GitHub offers no include mechanism for `on.*.paths` triggers, so those six copies cannot be removed. pipeline-scripts/check_workflow_path_filters.py polices them instead: every declared inline copy must contain the whole canonical set, every declaration must resolve to a file and a real `paths:` list, a workflow that loads the canonical file must be declared, and api-wiring.yml must keep having NO `pull_request.paths` (#12934 — a path-filtered required check never reports and deadlocks the pull request, and that asymmetry is exactly what a later "tidy the filters" pass would erase). Measured coverage fix found on the way: api-wiring's `wiring` filter omitted autobot_shared/**/*.py while scripts/audit_api_wiring.py imports autobot_shared.api_routing.router_prefixes and autobot_shared.openapi_schema from that tree. Editing those modules changed what the gate computes while the required api-wiring context self-skipped and reported green. It now adopts the canonical set. The guard runs blocking in enforce-precommit.yml rather than code-quality: enforce-precommit carries no `paths:` filter, so its reach is the whole repo, whereas code-quality's `changes` filter does not cover .github/workflows/** and the guard would never run on the exact edit it exists to catch. Verification is 14 mutation tests, not a re-run: dropping a canonical entry from any one of the six inline copies reds that copy, adding an entry to the canonical set alone reds all six, and reintroducing api-wiring's pull_request.paths reds too. * refactor(tooling): one base-ref resolver in scripts/lib, sourced by every changed-files guard (#13984) Five guards each resolved a base ref and status-checked their own git calls: check-pre-commit-hook-pr.sh (5 call sites), code-quality.yml (two inline copies), enforce-precommit.yml (a fifth copy the issue had not counted), lint-conventions.sh and verify-done.sh. All of them encode the same hard-won rules, so the next correction had to be applied five times or the copies drifted. scripts/lib/git-scope.sh now carries the UNION of what those five did, not a pick of one of them. Nothing was dropped: the merge-commit rule, the event payload fallback, the origin/<base> branch-name fallback (only check-pre-commit-hook-pr.sh had it), the last-resort parent, the explicitly-supplied-base rule, three-dot range semantics and range splitting (only lint-conventions.sh had them), the anti-no-op zero-files guard (only code-quality's conventions step had it), the deleted-path filter, the explicit --base validation (only verify-done.sh had it), and "an unresolvable ref is FATAL, never 'nothing changed'" throughout. The explicit-base rule is a SEPARATE entry point from the payload fallback, and deliberately so. Collapsing them was the first version of this change, and check-pre-commit-hook-pr_test.py caught it: an explicitly supplied base that does not resolve was being silently replaced by "<head>^" — a different, smaller scope — and the scan of it reported success. That is precisely the fail-open shape the library exists to prevent, so the two are distinct functions with the difference documented on both. verify-done.sh's branch_state() held six decisions in 113 lines. It is now six named predicates (_bs_commits_ahead, _bs_merges_are_judgeable, _bs_cherry_tally, _bs_collect_diff_paths, _bs_touched_paths, _bs_content_in_base) plus a 22-line decision that combines them; every function in the file is under 30 lines and every guard comment moved with its guard. Not one rule changed. scripts/lib/git-scope_test.sh builds REAL throwaway repositories rather than stubbing git — the rules are all about what git actually does with merge commits and unresolvable refs, so a stub would only re-assert this file's own assumptions. 33 assertions, registered in repo_tests/shell_lib_test.py (whose own guard fails on an unregistered suite). Removing the merge-commit rule reds it; so does weakening the explicit-base rule. lint_conventions_test.py's fixture now ships scripts/lib/git-scope.sh into its throwaway repo, because a checkout has scripts/lib/ in it, plus one new assertion that a missing library is fatal rather than a clean report. * refactor(ci): one hardcoded-value detector carrying all three rule sets (#14371) Three independent detectors implemented "find hardcoded values". Measured, not assumed: each had rules the other two did not, and only one of the three ever ran over the repository tree — the CI one, which reported ZERO violations. scripts/lib/hardcoded-value-rules.sh is now the single rule set, carrying the UNION of all three. Two thin entry points read it and differ only in where the lines come from and what they do with the verdict: pipeline-scripts/detect-hardcoded-values.sh tree scan; always exits 0, because ssot-coverage.yml has decided pass/fail from the reported counts since #2874 .../hooks/pre-commit-hardcoded-values staged files (or an argv list from CI); exits 1, because it is the one that can stop a commit autobot-infrastructure/shared/scripts/detect-hardcoded-values.sh — the dormant third fork — is retired. Nothing invoked it; the only references were in HARDCODING_PREVENTION.md under a path prefix that never existed. Its rules are not gone, they are in the library: the per-value SSOT suggestion tables, the generic model-tag regex, the hardcoded-URL rule and six context skips nothing else had. The docs now point at the canonical entry points. Detector 1's PORT_PATTERN was declared and never referenced by any scan function — 8443, 5432 and 9090 existed only as a string. A rule that existed only as a string is still a rule the merge has to carry, so it is wired in. scripts/lib/hardcoded_value_rules_test.py is the union proof. It names each former detector and a rule ONLY that one had, and fails by name if the merge lost it. It also asserts every registered rule has a fixture that trips it AND that the fixture survives hv_prefilter_pattern — which caught a real defect: the role and category call-argument rules fired under hv_scan_line and were UNREACHABLE on a tree scan, because the prefilter anchored on an `=`/`:` that a `.get("category", "general")` call does not have. Registered, tested, and no sink. Running the merged rules over the tree found 1977 findings where the CI detector reported 0. Every one was already true; the rules that would have found it lived in a fork nothing invoked or in a hook that only sees staged files. pipeline-scripts/hardcoded_values_baseline.txt records them so the merged set can be enforced today without reddening ssot-coverage on every pull request and blocking every local commit. It only ever shrinks, the count is part of the key so a second occurrence is a new finding, every run prints how many it suppressed, and a new --audit-baseline CI step fails on entries that no longer match anything. Performance is a correctness property here: the first version used `printf | grep` per rule per line and had not finished a tree scan after ten minutes, and a scan that times out reports nothing. The per-line path now uses bash's own `[[ =~ ]]` with no subprocess — 1m36s for six trees. Two hook tests that pinned documented false negatives are flipped, as they each asked to be: YAML/shell files are in scope (the tree scanner has covered them since #14316) and an unlisted model tag is caught by the generic regex. The hook's `types:` widens to match its own rules. A third stale test in lib/_common_test.py, red on the base branch since #14151, is fixed with the other two from #14034 — the Python suite is not a required check here, so a red test in it blocks nothing, which is how three of them accumulated. * docs(hardcoding): show the rule library as sourced, not invoked (#14371) check_script_exec_bits.py read the bare path in the new "Script Location" block as a documented direct invocation of a mode-644 file, and failed code-quality for it. The checker was right: the block did not say how the library is used. scripts/lib/hardcoded-value-rules.sh is SOURCED by both entry points and never executed, so mode 644 is correct and the fix belongs in the prose, not the index. The block now shows each artifact with the form it is actually invoked by, and says why the library's mode differs. Verified by mutation, not by a re-run: dropping `source` from that line puts the checker back to exit 1 on the same line. * perf(ci): prefilter the staged-file scan too, so both entry points agree (#14371) `pre-commit run --all-files` hands the staged-files entry point every tracked file, and `verify-precommit-config` is a REQUIRED check. Measured without a prefilter on that path: 200 files in 78s — about 50 minutes over the 7639 tracked in-scope files. That is not a slow check, it is a check that never reports, which is the same fail-open outcome the rules exist to catch. The tree-scan path already applied hv_prefilter_pattern. Applying it per line on the staged path too is the smaller half of why: the larger half is that the two entry points must agree about what is in scope. A tree scan that filtered lines and a staged scan that did not would judge the same file differently depending on which one looked at it — the exact shape of the get_staged_files defect in #14034 that reported a Vue file as a direct Redis connection. Verified it costs no coverage rather than assuming: the same 200-file sample produces byte-identical output before and after (43 findings both ways), and hardcoded_value_rules_test.py already asserts per rule that the prefilter is a true superset of that rule's trigger. Line numbers stay exact because the counter increments before the filter. 78s -> 16s on the sample. * fix(ci): keep #14877's fail-loud contract where the retired detector's logic went (#14371) Rebased onto d70a1f9. One file overlapped: #14877 (35f48ba) converted autobot-infrastructure/shared/scripts/detect-hardcoded-values.sh's `source lib/ssot-config.sh 2>/dev/null || true` into the fail-loud abort, and #14371 retires that file. Resolved as a union of capabilities rather than a survivor-pick. The abort idiom travels; the ssot-config.sh dependency deliberately does not, and the reason is recorded in scripts/lib/hardcoded-value-rules.sh rather than left to be re-derived. ssot-config.sh `set -a`-loads the master .env and exports all of it, AUTOBOT_REDIS_PASSWORD included. The consolidated detector is a scanner whose entire output is matched source lines, printed to a public CI log and posted to pull requests: pulling a live secret into that process is a disclosure risk it gains nothing from. Its SSOT tables are detection patterns — the literals it hunts and the config key that replaces each — not runtime config it reads to work, which is why detector 2's tables were literal too, and PROJECT_ROOT now resolves from each entry point's own location, needing no .env and working on a deployed install with no checkout. What did travel is the capability: every `source` in the library and both entry points ends in an explicit failure block that exits non-zero, and an absent or empty baseline is fatal for the same reason — an unread exemption set and an empty one are indistinguishable to every caller. The hook's repo-root variable is renamed REPO_ROOT -> PROJECT_ROOT to match the spelling ssot-config.sh exports and the only one repo_tests/shell_lib_sources_resolve_test.py can expand. Stated precisely in the comment: this buys nothing today, because that guard walks `*.sh` and every hook in the directory is extensionless, so it cannot see the file at all. The spelling means the site is verified rather than silently skipped once that walk is widened. Retiring the fork takes that guard's distinct-script count from 56 to 55, its exact floor. The floor is left where it is rather than lowered for headroom, so it still catches a regressed matcher, with the retirement recorded next to it so the next legitimate retirement is a deliberate edit and not a mystery. * fix(ci): assert the baseline only shrinks, instead of only saying so (#14371) Review found a HIGH bypass of the gate this change exists to build, and it was right. pipeline-scripts/hardcoded_values_baseline.txt suppresses findings, so anything that can append to it can silence the detector. `--audit-baseline` only ever checked one direction — that no entry had been STRANDED. Nothing checked that none had been ADDED: hardcode a fleet IP, and in the SAME change append 1|ssot|autobot-backend/new_file.py|<ip> to the baseline hv_partition suppresses it as already-known, ssot-coverage reports ssot_violations=0, and the pull request merges green over a finding the detector made correctly. The count-in-key design already made a bump on an existing key cost something; a brand-new key was the more direct route and was undefended. The comment at hardcoded-value-rules.sh states the invariant exactly — "the baseline would grow silently while appearing to hold" — and asserted it nowhere. A rule that lives in a comment is the shape of defect this whole change is about, so it is now an assertion. pipeline-scripts/check_baseline_no_growth.sh diffs the baseline against the base ref and fails on any new key or increased count. Base resolution comes from scripts/lib/git-scope.sh, built in this same PR: a hardcoded origin/main would be wrong in a repository whose pull requests target Dev_new_gui. Shrinking stays silent — removals and decreases are how a fixed violation leaves, and a guard that blocked those would make the file unmaintainable. Fail-closed throughout, and hv_baseline_growth reuses hv_parse_baseline_into rather than carrying a second parser — the guard needs two baselines in one process, and a private parser would be the exact fork this change exists to stop. That refactor also adds parse validation the loader never had: a non-numeric count and a line with no separator are now FATAL. Mutation-proved in both directions, in a scratch repo, nothing committed: fabricated new key -> RED; count bump -> RED; entry removed -> GREEN; count decreased -> GREEN; corrupt count -> RED; baseline missing -> RED; unresolvable base -> RED. Mutation also caught the guard's own untidiness: with the totals computed before the verdict, a missing baseline exited out of `awk` under `set -e` and never reached the designed FATAL. Fail-closed either way, but a failure path nobody can reason about is not one to keep. ssot-coverage.yml gains the blocking step and fetch-depth: 2, without which the merge-commit rule resolves nothing and the guard reds every run instead of comparing. Also from review, both non-blocking and both taken because this PR is where the secret-exposure thread lives: * _hv_rule_dsn and _hv_rule_url emitted the matched literal verbatim, so `postgresql://user:pass@host/db` reached a tracked file and a public CI log. Demonstrated, not hypothetical — two such values were already in the generated baseline. One shared _hv_redact_userinfo now serves both rules (they had the same defect for the same reason); scheme and host survive so the finding stays actionable, and a path like `https://h/a@b` is untouched. Baseline regenerated accordingly. * enforce-precommit.yml's git-scope calls use the explicit `|| exit 1` form rather than leaning on the runner's default shell flags, matching every other call site in this change.
mrveiss
added a commit
that referenced
this pull request
Aug 23, 2026
…t success or health (#14851, #12969, #13585, #14843, #14880) (#14898) * fix(monitoring): a failed access-control check must not report a clean one (#14880) Three sites answered a failed inline check with a literal that impersonates a measurement -- "UNKNOWN", "{}" and "0|0|0". #14868 removed the stderr suppression, which made the failure visible in a log but left the reported VALUE indistinguishable from a real one, so anything reading "{}" still saw "no findings" rather than "the check did not execute". Each getter now prints nothing and returns non-zero, which forces every consumer to decide what to report for a check that did not run. A counter carries that decision into a summary line and into the exit status (2), so a scraper cannot read a monitor that measured nothing as a clean report. Also fixed in the same file, same defect class: * the pre-flight imported `backend.services.feature_flags`, a package that has never existed here, so it failed on every run and the dashboard was never reached at all; * an empty denial list and a failed denial query printed the same "No denied access attempts" -- the most reassuring line the dashboard emits; * a missing curl or redis-cli reported the backend and Redis as DOWN, a value that did not come from a measurement either. Guard: _KNOWN_FABRICATING is now empty, so the script is guarded again. With no script fabricating, "the sweep found offenders" can no longer prove the detector works, so the floor became a positive control over synthetic samples of both block shapes plus a negative control for an honest "unavailable" fallback. The import pattern now also matches plain `import X` (scanning extracted blocks, since a single-line block hides the statement mid-line), and the stdlib allowlist is derived from sys.stdlib_module_names rather than hand-listed. Refs #13852 * fix(security): count the audit records the file-backed denial path drops (#14843) There are two permission-denied audit paths. #14750 instrumented the DB-backed one in both backends' RBAC middleware; the file-backed one is reached through a different decorator -- auth_rbac's _deny_permission_access, _deny_any_permission_access and _deny_role_access all audit through SecurityLayer.audit_log, which appends a JSON line and swallowed any write failure with nothing counting it. A full disk, a permissions change or a rotated-away directory dropped every record there while requests kept returning correct 403s. The swallow stays deliberately -- an audit problem must not turn a correct 403 into a 500 -- but it now calls the same record_audit_write_failure_safely the middleware uses. A second counter would let the two drift exactly as the two handlers did. The guard is now derived rather than listed, which is the actual lesson of this one: #14750's guard was correct and complete for the two files it named, and the invariant it protected had a second home it could not see. The sweep finds every function that audits a permission denial, resolves the sink each writes through (the function itself for the DB-backed row, one call down for the file-backed append), and asserts a sink that swallows a write failure counts it. It keys on the enclosing FUNCTION, not the call arguments -- keying on arguments finds audit_log(action="permission_denied") and misses _emit_permission_denied_audit(user_id, permission, path) entirely. Tests drive the real denial entry points with a real write failure (an audit path that is a directory) and assert the counter moved, pinning the error label so the assertion names which branch ran. A negative control asserts a record that WAS written is not counted as lost. Refs #13852 * fix(errors): find the deployed error catalog, and say so when it is not found (#12969) The deployed error_messages.yaml was present, 9794 bytes, written thirteen minutes before the log line saying it could not be found -- and both the backend and celery ran on the 42 built-ins on every start. It went unnoticed for as long as the install existed because the built-ins mirror the file entry for entry, so nothing observable differed. It stops being invisible the moment the YAML is edited: the edit silently does not take effect. Two defects, both fixed. The lookup was anchored only on PATH.STATIC_DIR and PATH.CONFIG_DIR, which derive from wherever autobot_shared happens to sit. A layout that installs it beside the backend rather than above it makes STATIC_DIR name a directory that does not exist. The resolver now also derives candidates from this module's own location, which reaches the backend static dir in any layout that keeps utils/ beside static/ -- and they ship together. PATH.CONFIG_DIR is kept with its repo spelling added beside it; that constant names a directory that has never existed in a checkout, filed as #14892 because it has nine other consumers. The fallback reported success. load_catalog returned True whether it had loaded the deployed YAML or silently substituted the built-ins, so no caller could tell "in use" from "never found". It now returns False on a fallback, records the catalog in use as a value (source, catalog_path, both in get_catalog_stats), and the failure message names every path it searched in order -- "not found" without saying where is why this sat unexamined in two separate log sweeps. The fallback is also visible on the health surface rather than only in a log: the error-resilience probe reports degraded with the source and path in data when the built-ins are in use. That probe already answers "is error handling healthy", so it did not need one of its own. Refs #13852 * fix(chat): /status must not report health it never measured (#14851) StatusCommand imported services.consolidated_health_service, which has never existed in this repo. The import is function-local, so it raised ModuleNotFoundError on every single call, and the surrounding except Exception answered "Status: ✅ Running / The chat system is operational" -- unconditionally, with no check behind it. The real branch, which could report degraded, was unreachable. A user asking /status during an outage was told everything was fine. /status now answers from api/system_health, the canonical aggregator behind /api/system/health, so there is one health source rather than a second one invented for this command. A measurement lists every component it checked and its status; a failure returns an answer that says UNKNOWN, names the reason, and carries success=False so a caller can tell the two apart programmatically rather than by reading prose. An empty probe registry is treated as a failure even though the aggregator reports it as "ok". Nothing was checked in that case, and letting that read as healthy is the exact defect being fixed -- so it is clamped at this consumer rather than by changing what /api/system/health returns for its own contract. Tests drive the command with the health source unreachable, with an empty registry, with a measured down component and with a measured healthy one. The first two assert the answer contains none of "operational", "running" or "healthy"; the last two pin which branch ran, so a fix that always says UNKNOWN fails too. Refs #13852 * fix(lifespan): stop the shutdown handler joining its own thread, and let cleanup finish (#13585) Two defects, and the second is why the first was not cosmetic. The self-join: the shared worker pool was drained with asyncio.to_thread(_executor.shutdown, wait=True). to_thread dispatches through the loop's DEFAULT executor, and the startup path makes that pool exactly that -- so shutdown(wait=True) ran on a pool worker and joined every pool thread including the one it was running on. RuntimeError: cannot join current thread, four times per restart. The drain now runs on a single-thread drainer that is not a member of the pool, so the join is legal. The blast radius: one except Exception wrapped the entire 400-line teardown, so that first failure jumped straight to the handler and skipped every step below it. Redis pools, the plugin manager, the MCP bridge workers, the database engine and the observer buffers have therefore been silently skipped on every restart for as long as the line has been firing, and the log understated it -- one ERROR with no traceback and no way to tell which steps ran. Every step is now guarded on its own through a small report object: a failure is recorded with exc_info and the next step still runs. Steps that announced success after their own await now log that line only when the step actually completed. The closing line reflects what happened -- "Cleanup completed successfully" was printed unconditionally, so a shutdown that skipped half its teardown and a clean one closed identically -- and cleanup_services returns the names of the steps that failed, so the outcome is a value and not only a log. Tests assert the reproduction first (the old to_thread call still raises "cannot join current thread") before asserting the drainer does not, so a passing test cannot come from the failure having never existed. A raising step is shown not to skip the steps after it, and a clean shutdown is shown to still report success. Refs #13852 * fix(guards): drop the stranded /status exemption and make a stranded one fail (#14851) #14851's import was fixed by REMOVING it, not by making the module exist. The module still does not resolve, so the exemption's "is this still broken?" check stayed green while the entry exempted a file that no longer contains the import -- an allowlist entry guarding nothing, with nothing saying so. The exemption test now also asserts the named file still imports the named module, so an exemption cannot outlive its bug in either direction. Also applies black to the files this branch touched. Refs #13852 * fix(monitoring): stop bash rewriting three inline python programs before they run (#14880) Found by running the fixed monitor with every check made to fail, which is what the acceptance criterion asked for. stderr carried something no static check had reported: "await: command not found", then a shell syntax error. #14877 added an explanatory comment to three inline python blocks that quoted two identifiers in backticks. Those blocks are DOUBLE-QUOTED shell arguments, so a backtick is command substitution -- bash ran the words between them and handed python whatever was left of the program. The checks could not have passed for any reason at all. Two of the three are in validate_access_control.sh, the post-deployment validation the access-control runbook tells operators to run: its ownership coverage and ownership performance tests. The third is the monitor's own ownership check. Under the fallback this branch removes, all three surfaced as a reassuring 0|0|0. Guard: the deployment-script suite already read these blocks as Python and as an AST. Nothing read them as the shell strings they actually are, which is why an error visible on the first real run was invisible to every check. It now flags a backtick or $(...) inside an inline block, with a positive control per form and a negative control for the plain $VAR interpolation three other scripts do on purpose. Verified by re-running the reproduction: no bash-level errors, no fabricated values in the output, exit status 2, and a summary saying the dashboard is incomplete. Refs #13852 * Auto-fix: code formatting Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml. * style(lint): satisfy isort on the files this branch touches (#14843, #13585) * fix(lifespan): assert the shutdown guard by driving it, not by greping for a log string (#13585) The mesh-brain wiring tests asserted on the SOURCE TEXT of lifespan.py: one greped for an await expression, the other for the literal "MeshBrainScheduler stop failed (non-fatal)". Replacing that log call with the shared failure recorder turned the second one red without anything about the property it names having changed. That is the failure mode the property itself is about. A test that breaks on a rename while the behaviour survives was not testing the behaviour -- and the same test would have stayed green if the try/except had been deleted and only the message kept. Both now drive cleanup_services: one asserts the scheduler's stop is awaited, the other makes it raise and asserts a LATER step still ran and the failure was recorded by name. Mutation-checked by removing the guard around that stop -- the independence test goes red, and the last-resort handler reports the teardown as incomplete rather than successful. Refs #13852 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
mrveiss
added a commit
that referenced
this pull request
Aug 30, 2026
…nd spot (#15127) Review found the baseline module's deletion took the whole test file with it -- including TestNoNewUnreferencedScript, the assertion that stops a newly-unreferenced script arriving silently, and the enumeration/reach checks that stop it passing over nothing. Restored repo_tests/unreferenced_shell_script_test.py with only the baseline-specific machinery removed (the KNOWN_UNREFERENCED import, TestBaselineRatchetsDownOnly, test_every_baselined_script_is_named_only_by_the_bookkeeping); every batch regression class from the four already-landed decisions in this issue stays, each with its KNOWN_UNREFERENCED assertion dropped since the set no longer exists. Closed the guard's own blind spot: pipeline-scripts/hardcoded_values_baseline.txt records a script's path because a hardcoded-value scan found something in it, not because anything calls it, but the guard's basename-substring match counted that as a reference. Added it to NOT_A_REFERENCE. That flipped 14 scripts from 'referenced' to genuinely orphaned, not just the one this review named -- decided all 14 on the same standard as the original five: Retired (targets infrastructure that never existed or was superseded): - deployment/start_npu_worker.sh, ensure-frontend-dependencies.sh -- DEV/SANDBOX headers pointing at docker/compose/docker-compose.hybrid.yml, which does not exist (filed #15322 separately: 13 OTHER tracked files, including the live run_agent.sh entrypoint, still reference that same missing file -- out of scope here, that one needs its own review). - run_agent_fast.sh -- targets backend.fast_main:app, deleted in #567. - setup/models/setup_model_sharing.sh, setup_windows_only_models.sh -- three more missing compose files between them, a wrong container name (autobot-ollama-shared vs autobot-ollama), a hardcoded personal Windows username fallback, and an .env/.yml output scheme nothing reads. - testing/run-playwright-tests.sh, test_terminal_input.sh -- target e2e specs that live under autobot-frontend/tests/e2e/, not the autobot-slm-frontend/ tree these cd into; run-playwright-tests.sh also shells out to the deprecated root main.py (#725/#781) with a --test-mode flag it never had. - utilities/complete-vm-sync-templates.sh -- autobot-user-backend and docker/npu_worker do not exist (real paths: autobot-backend, no docker npu-worker tree at all); duplicates the raw-rsync workflow docs/adr/005-single-frontend-mandate.md names sync-frontend.sh and sync-to-vm.sh as the actual tools for. Wired in (real defect, now fixed): - testing/manage_playwright.sh -- the compose file it starts is real (container_name: autobot-playwright) but was named as a bare relative path that only resolved from inside docker/compose/ itself; resolved from the script's own location instead. - utilities/sync-npu-worker-to-windows.sh -- SOURCE_PATH was missing the autobot-npu-worker/ path segment; install.ps1/check-health.ps1 it tells the operator to run next both confirmed present at the corrected path. Kept as documented manual tools (README.md, autobot-infrastructure/shared/scripts/): - check_ai_stack_health.sh, setup/setup_tier2_research.sh -- both hardened 6 days ago (#14877/#14867) against real, current targets. - environment/set-env-deepseek.sh -- every env var it sets is live and SSOT-recognized; network-config.sh it sources exists. - utilities/prevent-local-frontend.sh -- content-consistent with the real sync-frontend.sh workflow (both target autobot-slm-frontend); never wired to a caller, but not broken either. Baseline mechanics unaffected: repo_tests/unreferenced_shell_script_baseline.py stays deleted (AC4); nothing reintroduces it.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
#14041, #14172, #14518 and #14867 were filed separately but describe one defect
with one mechanism: a missing dependency is discarded instead of raised, so
the script runs in a degraded mode nobody can see. A shell script sources a
config library that is not there and continues on hardcoded literals. A Python
script imports a module that does not exist and dies before its first
statement. A shell script runs embedded Python that cannot import, throws the
traceback away, and prints a reassuring number.
Every one of them passes review, because the source text describes the
intended behaviour. Only the machine knows the difference.
So the fix has two halves, and both are required in each surface:
(a) make the missing dependency fail LOUD at the point of use — the value
of a check is entirely in the difference between "ran and was clean" and "could
not run", and discarding stderr erases exactly that difference;
(b) add a repo-wide guard so the class cannot be reintroduced silently —
shellcheck does not follow a dynamic source path, and pyflakes never checks
that an import target resolves, so nothing in the repo could see any of this.
Where a target could not be identified with confidence, the import is left
alone and tracked. Guessing would replace a loud
ModuleNotFoundErrorwith aquiet wrong answer, which is the defect this work exists to remove.
The #14172 scope decision
#14172 carried
needs-decision, asking whether the 56sourcelines shouldchange shape or whether a repo guard alone should cover it. This was an
autonomous run, so the recommendation, the rationale and the rejected
alternative were posted to the issue
and the work proceeded rather than blocking.
Decision taken: change the call-site shape. A guard only sees this checkout
at PR time; it cannot see a bad deploy, a
.gitignoreregression or a renamedownstream — the three recurrence scenarios the issue itself names — because in
every one of those the repo is green and the machine is missing the file. The
guard ships too, as the second half rather than the whole. The reason for the
silent degrade also expired when #14041 built the library, and the tree already
has the fail-loud convention:
lib/db-update-classify.shandscripts/lib/project_root.share both sourced bare, one of them two linesbelow an ssot-config source that was guarded with
|| true.Stated plainly: a node that legitimately ships only part of the tree now
gets a loud abort where it previously got a quiet wrong value. That is the
intended trade and the direction the issue asked for, but it is a behaviour
change on deployed hosts, and it is the part worth overruling on if the fleet
has such nodes. That needs host observation, which an autonomous run cannot
produce.
What Changed
(a) Fail loud at the point of use
56 shell scripts (#14041, #14172) —
source X 2>/dev/null || truebecomesan explicit failure block that writes to stderr and exits non-zero.
2>/dev/nullon the first attempt only — that missis expected, callers sit at two different depths — while the last attempt
keeps its stderr and is fatal. The old fallback re-read
.envand carried on.return 1 2>/dev/null || exit 1, so a script that is itselfsourced returns instead of killing the parent shell — the idiom
ssot-config.shalready uses at its own re-entry guard.test_ssot_config_lib.shasserted the old contract ("missing librarydegrades to literal"). It now asserts the inversion and executes the blocks
as shipped, extracted with
sedout of four real scripts, so reintroducing|| truetrips the test rather than passing a re-typed copy of the idiom.39 of 44 Python imports (#14518) — rewritten to the real module plus the
sys.pathinsert the operator entry points in this tree already use (#14129,#14507), with
parents[N]computed per file from its own depth.aioredisismigrated to
redis.asyncio(the alias this repo already uses), adding nodependency;
dockergains the sameImportErrorguard its five other callsites carry. Four further sites outside #14518's own enumeration
(
utils.redis_database_manager,utils.logging_manager) were found by thesweep and repointed at the canonical
autobot_sharedmodules.8 deployment scripts (#14867) — 7 of 9 embedded imports now name a real
module, with a
PYTHONPATHderived from each script's own location. All 9swallowed failures are gone: the traceback reaches stderr and the exit status
reflects it. Summary lines that printed unconditionally ("Health check
complete", "All checks passed", "Startup coordinator is ready for use") are now
gated on a failure counter. Three latent defects of the same shape surfaced and
were fixed: a
${PROJECT_ROOT}interpolated inside a quoted heredoc (so thepath was a literal string and "CLAUDE.md not found!" was guaranteed, returning
0); a
PYTHONPATHpointing at a package's contents rather than its parent; anda Python
"""docstring at the top of a bash script, which bash executed ascommands on every run.
(b) Repo-wide guards
Three new files under
repo_tests/— that location is deliberate, becauseautobot-infrastructure/is in no pytesttestpathsentry and in no CI pytestinvocation, so a test beside the scripts would be as dormant as the defects.
shell_lib_sources_resolve_test.pysource .../lib/*.shresolves, and the decisive attempt in each||chain is not silencedinfra_script_imports_resolve_test.pyshared/scripts/*.pyresolvedeployment_script_imports_resolve_test.pypython3 -cand heredocs resolve; no check answers with a fabricated valueVerification
No code from the codebase was executed. Verification is static analysis plus
the CI run on this branch. Each guard was copied to a scratch location,
parameterised on the tree root, and run against both the pristine
origin/Dev_new_gui(git archive) and this branch.Measured guarded/total — counted, not asserted
shared/scripts/*.pyimports*.shResult of running all three guards:
57 parametrized sites; the other two guards red on their own populations
Anti-fail-open measures
An empty or absent result reads as "clean", so every sweep asserts presence
of what it expects before asserting absence of failure:
declared distributions found, and first-party roots still present. The
block-count floor is load-bearing for the shell guard — an extractor that
stopped recognising
python3 -cwould otherwise pass having read nothing.under a directory named
.worktreesorvenvcannot skip every file andreport clean.
That second assertion is the positive control for each file: if a detector
regresses, the exemption test goes red instead of the whole file passing
vacuously.
unbalanced quote, or a backslash-escaped quote — neither occurs in a real
source statement), not by naming files, so
test_ssot_config_lib.shcan keepdriving the very shapes it tests. A dedicated test asserts those exclusions
exclude exactly what they claim.
bash -n(parse only) andcompile(..., "exec")onthe extracted Python payloads — neither executes anything. All 8 scripts are
#!/bin/bash; no dash script is in the set, and noset -o pipefailwasadded.
Enforcement caveat — measured, not assumed
Stated plainly so nobody reads more into "repo-wide guard" than it delivers.
The three guards land in
repo_tests/, which is collected by the Python suite(
coverage.yml) — and the Python suite is not a required status check onthis branch. The ten required contexts are
No commit trailers,No open blocks-merge issues reference this PR,Unit & Integration Tests(the frontend job, per
frontend-required-context.yml:84),api-wiring,code-quality,migration-matrix,smoke-test,startup-import-smoke,verify-generated-typesandverify-precommit-config.So these guards will run and go red visibly, but they will not block a merge
until #13162 is resolved. That is a property of the CI configuration, not of
the guards, and it is the same for the existing
first_party_imports_resolve_test.pythis work generalises.The related gap on the bash side is filed as #14878: the
ssot-config-lib-guardpre-commit hook is a regression suite, not a formatter,but
enforce-precommit.ymlroutes all hook findings to::warning::— onlya hook that could not execute at all fails the build. There is also no
pre-commithook installed in the local git checkout, so the suite did notrun at commit time either.
Issues this PR closes, and the two it does not
Closes #14041, #14172. Both are fully delivered: the library existed
already, a missing bootstrap now fails loudly, and the repo check exists.
#14518 and #14867 are advanced but NOT closed, because partial delivery
never closes an issue:
deleted or never landed:
SSHManager(0 hits repo-wide),add_api_routes(deleted; its successor contains no
workflow_router, so repointing wouldmake the script's assertion falsely red), and
monitoring_compat(dissolved by Integrate: monitoring_compat.py — unregistered router with deprecated endpoints #1283). No import spelling reaches any of them. Tracked as
bug(infra): 5 imports under shared/scripts name symbols that were deleted or never landed (#14518 residual) #14870.
remain:
BackupEngineandget_agent_orchestrator, both zero hitsrepo-wide. What remains now fails loudly rather than silently. Tracked as
bug(infra): 2 embedded imports in deployment scripts name an engine and an accessor that were never built (#14867 residual) #14875.
Filed rather than folded in silently: #14870, #14871 (langchain_redis
undeclared — its absence silently drops an arm of the comparison the scripts
exist to measure), #14872 (the docker SDK is imported by five production
backend modules and declared in no requirements file), #14873
(
optimize_agentsforked across two modules sharing one import name),#14875, #14876.
Merge-order note — PR #14868 landed mid-review, branch rebased
This PR originally predicted a collision with PR #14868 (#14866). It happened,
and is resolved.
Two file conflicts, both in the scripts #14867 wrongly described as already
fixed. #14866's
PYTHONPATHexport is kept in full; only itssource ... 2>/dev/null || trueis replaced by the #14172 fail-loud form.One add/add conflict: #14868 added its own
repo_tests/deployment_script_imports_resolve_test.py— the filename #14867predicted. Rather than ship a second guard, this branch's copy is merged into
the landed file (consolidate, never fork). The landed version is the survivor
because it carries a call-layer check this branch's did not: an awaited sync
Redis client is a failure that moves from import time to call time, which no
import guard can see. Three things merged in from here:
scripts export
${SCRIPT_DIR}on PYTHONPATH and import siblings; without itthe landed guard reported a false positive on
test_startup_coordinator.sh,whose
startup_coordinator.pysits right beside it.test_no_inline_check_fabricates_its_own_result— nine deployment/setup scripts embed Python importing packages that do not exist, with the failure discarded #14867's second acceptancecriterion, and the half fix(deploy): make the access-control validator and monitor import modules that exist (#14866) #14868 left undone. It removed the
2>/dev/nullbutkept
|| echo "{}", so a check that cannot run still reports a valueindistinguishable from a clean one. Four such sites remain in
access_control_monitor.sh(UNKNOWN,{},{},0|0|0); filed asbug(monitoring): the access-control monitor still answers a failed check with UNKNOWN/{} (#14866 second half) #14880 and carried as a self-guarding exemption.
_KNOWN_BROKENentries are retired, because this branchfixed those imports. The two that remain are repointed at bug(infra): 2 embedded imports in deployment scripts name an engine and an accessor that were never built (#14867 residual) #14875.
The new fabrication detector was wrong on its first draft, and this is worth
recording because it is the failure mode this whole PR is about. It matched
python3 -cto end-of-line, which finds nothing for a multi-line block —the
|| echosits after the closing quote, several lines down. It reportedzero fabricated results on a file that has four. Nothing about that looked
wrong: the test passed. Its own positive control ("this exemption must still be
broken") is what turned it red. That is precisely why every exemption in these
guards asserts it is still reported by the sweep, not merely still broken.
Model Used
Claude Opus 5 (1M context)
Closes #14041, #14172