Skip to content

fix(infrastructure): make a missing script dependency fail loudly, and guard the class (#14041, #14867, #14518, #14172) - #14877

Merged
mrveiss merged 5 commits into
Dev_new_guifrom
issue-14041-script-imports
Aug 23, 2026
Merged

mrveiss merged 5 commits into
Dev_new_guifrom
issue-14041-script-imports

Conversation

@mrveiss

@mrveiss mrveiss commented Aug 23, 2026 •

Copy link
Copy Markdown
Owner

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 ModuleNotFoundError with a
quiet wrong answer, which is the defect this work exists to remove.

The #14172 scope decision

#14172 carried needs-decision, asking whether the 56 source lines should
change 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 .gitignore regression or a rename
downstream — 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.sh and
scripts/lib/project_root.sh are both sourced bare, one of them two lines
below 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 || true becomes
an explicit failure block that writes to stderr and exits non-zero.

  • 51 single-path sites converted directly.
  • 5 two-path sites keep 2>/dev/null on the first attempt only — that miss
    is expected, callers sit at two different depths — while the last attempt
    keeps its stderr and is fatal. The old fallback re-read .env and carried on.
  • 2 already-bare sites get the same block, so one shape ships everywhere.
  • The die uses return 1 2>/dev/null || exit 1, so a script that is itself
    sourced returns instead of killing the parent shell — the idiom
    ssot-config.sh already uses at its own re-entry guard.
  • 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 four real scripts, so reintroducing
    || true trips 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.path insert the operator entry points in this tree already use (#14129,
#14507), with parents[N] computed per file from its own depth. aioredis is
migrated to redis.asyncio (the alias this repo already uses), adding no
dependency; docker gains the same ImportError guard its five other call
sites carry. Four further sites outside #14518's own enumeration
(utils.redis_database_manager, utils.logging_manager) were found by the
sweep and repointed at the canonical autobot_shared modules.

8 deployment scripts (#14867) — 7 of 9 embedded imports now name a real
module, with a PYTHONPATH derived from each script's own location. All 9
swallowed 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 the
path was a literal string and "CLAUDE.md not found!" was guaranteed, returning
0); a PYTHONPATH pointing at a package's contents rather than its parent; and
a Python """ docstring at the top of a bash script, which bash executed as
commands on every run.

(b) Repo-wide guards

Three new files under repo_tests/ — that location is deliberate, because
autobot-infrastructure/ is in no pytest testpaths entry and in no CI pytest
invocation, so a test beside the scripts would be as dormant as the defects.

guard watches surface no existing check reads
shell_lib_sources_resolve_test.py every source .../lib/*.sh resolves, and the decisive attempt in each || chain is not silenced shellcheck does not follow a dynamic source path
infra_script_imports_resolve_test.py imports under shared/scripts/*.py resolve pyflakes never checks that an import target exists
deployment_script_imports_resolve_test.py imports inside python3 -c and heredocs resolve; no check answers with a fabricated value the Python is a shell string — no linter parses it at all

Verification

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

guard violations on the pristine base after this PR coverage
shell lib sources 57 of 57 sites flagged, across 56 of 56 scripts 0 56/56 scripts — the exact population #14041 enumerated
shared/scripts/*.py imports 42 sites present, 34 flagged 0 unflagged 8 are the tracked residue below
inline Python in *.sh 27 sites present, 15 flagged 0 unflagged 12 are the tracked residue below

Result of running all three guards:

  • this branch — 0 failures (13 tests, including 76 parametrized cases)
  • pristine base — shell guard fails both aggregate assertions plus 57 of
    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:

  • discovery floors on files walked, sites matched, inline blocks extracted,
    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 -c would otherwise pass having read nothing.
  • skip lists match relative paths, never absolute, so a checkout living
    under a directory named .worktrees or venv cannot skip every file and
    report clean.
  • every exemption is asserted still broken AND still 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.
  • documentation and nested shell strings are excluded structurally (an
    unbalanced quote, or a backslash-escaped quote — neither occurs in a real
    source statement), not 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.
  • shell verification used bash -n (parse only) and compile(..., "exec") on
    the extracted Python payloads — neither executes anything. All 8 scripts are
    #!/bin/bash; no dash script is in the set, and no set -o pipefail was
    added.

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 on
this 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-types and verify-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.py this work generalises.

The related gap on the bash side is filed as #14878: the
ssot-config-lib-guard pre-commit hook is a regression suite, not a formatter,
but enforce-precommit.yml routes all hook findings to ::warning:: — only
a hook that could not execute at all fails the build. There is also no
pre-commit hook installed in the local git checkout
, so the suite did not
run 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:

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_agents forked 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 PYTHONPATH export is kept in full; only its
source ... 2>/dev/null || true is 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 #14867
predicted. 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:

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 -c to end-of-line, which finds nothing for a multi-line block —
the || echo sits after the closing quote, several lines down. It reported
zero 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

@github-actions

Copy link
Copy Markdown
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.
…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
mrveiss force-pushed the issue-14041-script-imports branch from 13d2e9e to 817d7d5 Compare August 23, 2026 18:57
@mrveiss
mrveiss merged commit 35f48ba into Dev_new_gui Aug 23, 2026
59 of 62 checks passed
@mrveiss
mrveiss deleted the issue-14041-script-imports branch August 23, 2026 19:40
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.
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.
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.

1 participant