fix(vision-manager): retain analysis-enabled gate alongside memory-yield
Some checks failed
deploy / test-frontend (pull_request) Waiting to run
deploy / build-and-push (pull_request) Blocked by required conditions
deploy / deploy (pull_request) Blocked by required conditions
deploy / test-backend (pull_request) Has been cancelled

The analysis-disabled gate (stop/inhibit vision when Admin→Settings analysis
is OFF, so stale queued jobs don't pin ~4.4GiB) was applied directly on the
host and never committed, so it was absent from main and from this branch —
merging as-is would have regressed it. Re-introduce it here so the merged
manager has BOTH gates: vision is held down if analysis is disabled OR host
memory is tight. Also grant the read-only role SELECT on app_settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 14:42:36 +02:00
parent 0a9e727530
commit 27f215066d
2 changed files with 36 additions and 1 deletions

View File

@@ -64,6 +64,20 @@ crawl_running() {
AND state = 'running';" 2>/dev/null || echo "ERR" AND state = 'running';" 2>/dev/null || echo "ERR"
} }
# Is the analysis subsystem enabled in Admin → Settings? The runtime source of
# truth is the `app_settings` row (key='analysis'), NOT the .env seed. When
# analysis is OFF the worker drains nothing, so leftover pending jobs would
# otherwise keep vision pinned forever — we must gate on this too, not just the
# queue depth. Returns the literal 'true'/'false'; 'ERR' (or anything else) on
# query failure is treated as enabled by the caller (fail-open: never yank
# vision out from under a live analysis on a transient DB hiccup).
analysis_enabled() {
"${PSQL[@]}" -c \
"SELECT CASE WHEN COALESCE((value->>'enabled')::boolean, false)
THEN 'true' ELSE 'false' END
FROM app_settings WHERE key = 'analysis';" 2>/dev/null || echo "ERR"
}
# ---- Memory pressure ------------------------------------------------------- # ---- Memory pressure -------------------------------------------------------
# Host-wide used% from MemAvailable (NOT MemFree, which sits near "full" thanks # Host-wide used% from MemAvailable (NOT MemFree, which sits near "full" thanks
# to page cache and would false-trigger constantly). /proc/meminfo in this # to page cache and would false-trigger constantly). /proc/meminfo in this
@@ -151,6 +165,7 @@ log "started: container=$VISION_CONTAINER health=$VISION_HEALTH_URL poll=${POLL_
idle_for=0 # seconds the queue has been empty while vision is running idle_for=0 # seconds the queue has been empty while vision is running
up_for=0 # seconds vision has been running (for MAX_UPTIME backstop) up_for=0 # seconds vision has been running (for MAX_UPTIME backstop)
crawl_deferred=0 # 1 while a start is held off by the crawl mutex (log on flip only) crawl_deferred=0 # 1 while a start is held off by the crawl mutex (log on flip only)
analysis_off=0 # 1 while analysis is disabled in settings (log on flip only)
mem_yield_until=0 # epoch deadline of the active memory-yield cooldown (0 = none) mem_yield_until=0 # epoch deadline of the active memory-yield cooldown (0 = none)
mem_inhibited=0 # 1 while starts are inhibited by memory pressure (log on flip only) mem_inhibited=0 # 1 while starts are inhibited by memory pressure (log on flip only)
@@ -169,6 +184,22 @@ while true; do
;; ;;
esac esac
# Gate on the runtime enable flag. If analysis is OFF, force the backlog to 0
# so the normal idle path runs the STOP_DEBOUNCE timer and shuts vision down.
# We deliberately reuse the idle debounce (rather than stopping instantly) so
# a quick toggle off→on inside the debounce window keeps vision warm — no
# cold reload for a momentary flip. Fail-open: only a literal 'false' counts
# as disabled; 'ERR'/empty leaves the real queue depth in effect.
if [ "$(analysis_enabled)" = "false" ]; then
if [ "$analysis_off" != "1" ]; then
log "analysis disabled in settings — ignoring ${pending} queued job(s); idle timer will stop vision"
analysis_off=1
fi
pending=0
else
analysis_off=0
fi
# Memory-yield gates (before the backlog logic). The active stop handles a # Memory-yield gates (before the backlog logic). The active stop handles a
# running vision the idle path can't; the inhibit forces pending=0 so the # running vision the idle path can't; the inhibit forces pending=0 so the
# existing idle-debounce path keeps it down (same shape as the crawl mutex). # existing idle-debounce path keeps it down (same shape as the crawl mutex).

View File

@@ -26,7 +26,11 @@ SELECT 'CREATE ROLE vision_manager LOGIN'
-- (Re)set the password every run so rotating it is just a re-apply. -- (Re)set the password every run so rotating it is just a re-apply.
ALTER ROLE vision_manager LOGIN PASSWORD :'pw'; ALTER ROLE vision_manager LOGIN PASSWORD :'pw';
-- CONNECT to the current database, and read-only on the one table we poll. -- CONNECT to the current database, and read-only on the two tables we poll:
-- crawler_jobs (backlog depth + crawl-in-flight) and app_settings (the runtime
-- analysis-enabled flag — without this the manager can't tell that analysis was
-- turned off and would keep vision pinned on stale queued jobs).
GRANT CONNECT ON DATABASE :"DBNAME" TO vision_manager; GRANT CONNECT ON DATABASE :"DBNAME" TO vision_manager;
GRANT USAGE ON SCHEMA public TO vision_manager; GRANT USAGE ON SCHEMA public TO vision_manager;
GRANT SELECT ON crawler_jobs TO vision_manager; GRANT SELECT ON crawler_jobs TO vision_manager;
GRANT SELECT ON app_settings TO vision_manager;