refactor(audit): promote audit_gauges to levels audit-gauges (4C prep) - #191
Conversation
…ges`
4C prep: a paired-release systemd unit must run from the release venv, not
a source script absent from immutable releases (a finding from the clean-VM
cutover rehearsal). Move the audit closure into the package + expose a CLI.
- git mv scripts/{audit_gauges,fetch_usgs_sites,fetch_nwps_sites}.py →
src/kayak/gauge_audit/{audit,usgs_sites,nwps_sites}.py (history preserved);
delete scripts/_gauge_metadata_cache.py → kayak.config.GAUGE_METADATA_CACHE.
- Fetchers' argv main() → fetch_usgs_sites()/fetch_nwps_sites() callables;
refresh_caches() calls them directly (no sys.argv/sys.path hacks).
- New TYPED src/kayak/cli/audit_gauges.py (addArgs + handler → audit.run_audit),
registered in cli/main.py. gauge_audit imports are lazy, so `levels` startup
(and other subcommands) are unaffected; requests stays a lazy, prod-locked dep.
- systemd/kayak-audit-gauges.service ExecStart → /home/pat/.venv/bin/levels
audit-gauges (literal binary path like the sibling levels units; 4C re-points
it to /opt/kayak/current/venv).
- config.py: coerce blank/whitespace AUDIT_EMAIL → None. Routing through
KayakConfig (vs the old raw os.environ.get) means an empty AUDIT_EMAIL
(systemd ${AUDIT_EMAIL} unset) would fail EmailStr validation and break EVERY
levels command at import — the validator makes "" mean "no digest".
- mypy: gauge_audit.{audit,usgs_sites,nwps_sites} get the gradual-typing
override (same precedent as kayak.tracing.trace); cli/audit_gauges.py is typed.
- Also fixed scripts/seed_gauge_display.py (used the deleted cache module);
moved tests → tests/test_gauge_audit/; swept doc refs.
Gate green: ruff (full scope), format, mypy (106 files), `levels audit-gauges
--help`, 1725 fast tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review — PR #191 (
|
|
Adversarial review, including live-server context: Finding
What I verified
I did not run the network refresh path or send an email from the live server. |
…dence docs
Review follow-ups on the audit_gauges promotion:
- systemd/kayak-audit-gauges.service: set GAUGE_METADATA_CACHE explicitly
rather than relying on config.py's BASE_DIR-relative default (which only
happens to resolve to the right path because the editable install lives at
/home/pat/kayak). The literal value matches ReadWritePaths= so the write
target stays inside the sandbox grant even if the install location moves.
- config.py: the audit timer is twice-monthly (2nd + 17th), not weekly —
correct the audit_email docstring to match the unit's OnCalendar.
- cli/validate_config.py: the USGS_API_KEY reader moved from the deleted
scripts/fetch_usgs_sites.py into gauge_audit/usgs_sites.py; update the
stale comment to the current path.
- gauge_audit/{usgs,nwps}_sites.py: tag the `import requests` with
`# type: ignore[import-untyped]`, matching fetch_usgs_ogc.py, so mypy
passes with or without the types-requests stub installed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — both reviews addressed in Fixed in code:
D1 — deploy-time must-do (no code change; flagging so it isn't missed): this PR deletes Gate green locally (ruff, format, mypy, full non-slow suite 1725 passed). Not merging — yours to merge. |
Batch 4C foundation. docs/PLAN_4c_renderers.md lays out the remaining work to finish the paired-release cutover: render the systemd unit drop-ins / nginx vhosts / FPM open_basedir from host.yaml (replacing the hand-crafted runbook step 5), add the deployer's serving-path verification gate, and flip the live host to SERVING_CUTOVER=yes. It also records the verified consumer classification — note that #191's audit-gauges promotion makes it the 6th engine consumer to re-point (the runbook predates that and lists 5). This first increment adds only the scalar HostConfig fields the renderers need, all defaulting to the current WKCC shape (keep-current-then-flip, like the S3 slices) so nothing changes until the cutover host.yaml ships: - service_user / service_home — the account + home the units run as - release_root — the /opt/kayak paired-release root - fpm_pool_php — the PHP-FPM pool version in /etc/php/<v>/fpm Validators: service_user must be a bare POSIX username (it's interpolated into unit User= and shell ACL commands), the paths absolute, fpm_pool_php major.minor. docroot (existing) is reused — it flips to /var/cache/kayak/docroot at cutover. The vhost server_names type lands with the nginx renderer (increment 3) that consumes it, not here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… runbook Rehearsed the live cutover (running host → paired-release) end-to-end on a live-equivalent VM clone (full live DB — 4.46M obs, 77 migrations) with ZERO serving disruption, and captured it as a "Migrating a RUNNING host" section. The validated no-downtime ordering: - Phase 0: pre-stage the paired-release ALONGSIDE the running site (dirs, host.yaml, deploy.env, stage-only) — no mutation, public_html keeps serving. - Phase 1: apply the cutover config to the FILES only (render-serving → nginx root + FPM open_basedir via sed; render-units → drop-ins) and stop the timers, but DO NOT reload nginx/FPM — the running services hold the old in-memory config, so public_html keeps serving. Skip install-config.sh (pool/runtime-config already exist; a reload now would serve the empty new docroot). - Phase 2: activate — the gate passes on the FILES, `migrate` reports "No pending" against the live DB (no init-db — reuse it), the build writes the new docroot, symlink switch. Still serving public_html. - Phase 3: graceful `nginx reload` + `fpm restart` → flip to the new docroot. Confirmed: serving stayed 200 throughout (public_html → new docroot at the flip); the live DB migrated clean; and the cutover FIXED the live audit-gauges unit (the #191 promotion lands via render-units, which scripts/deploy.sh never did). "Still open" trimmed to: execute the real cutover + the deferred genericization. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Promotes
scripts/audit_gauges.pyto alevels audit-gaugesCLI subcommand by moving its closure into thekayakpackage. 4C prep: a paired-release systemd unit must run from the release venv (/opt/kayak/current/venv/bin/levels), not a source script that's absent from immutable releases — surfaced by the clean-VM cutover rehearsal.Changes
git mvthe 4-file closure intosrc/kayak/gauge_audit/(history preserved):audit.py,usgs_sites.py,nwps_sites.py;_gauge_metadata_cache.pydeleted (→kayak.config.GAUGE_METADATA_CACHE).main()→ callables;refresh_caches()calls them directly (nosys.argv/sys.pathhacks).src/kayak/cli/audit_gauges.py(addArgs+ handler →audit.run_audit), registered incli/main.py.gauge_auditimports are lazy →levelsstartup and other subcommands are unaffected;requests(used by the fetchers) stays a lazy, prod-locked dep, so the release venv has it.systemd/kayak-audit-gauges.service:ExecStart→levels audit-gauges(literal binary path like the 5 siblinglevelsunits; 4C re-points it to/opt/kayak/current/venv).config.py: coerce blank/whitespaceAUDIT_EMAIL→None. Routing throughKayakConfig(vs the old rawos.environ.get) means an emptyAUDIT_EMAIL(systemd${AUDIT_EMAIL}unset) would failEmailStrvalidation and break everylevelscommand at import — the validator makes""mean "no digest".gauge_audit.{audit,usgs_sites,nwps_sites}get the gradual-typing override (same precedent askayak.tracing.trace); the CLI module is fully typed.scripts/seed_gauge_display.py(used the deleted cache module); moved tests →tests/test_gauge_audit/; swept doc refs.Gate
ruff (full CI scope) · format · mypy (106 files) ·
levels audit-gauges --help· 1725 fast tests — all green.requestsverified declared + inrequirements-prod.lock.🤖 Generated with Claude Code