Two AI spend paths escape config/ai_features entirely, so "AI is off" is not true and #45 cannot measure spend #76
Closed
opened 2026-08-25 21:30:50 -04:00 by logan
·
3 comments
No Branch/Tag Specified
Labels
Clear labels
area/ai-pipeline
area/auth
area/billing
area/correlation
area/discord
area/frontend
area/governance
area/growth
area/infra-deploy
area/legal
area/map
area/mqtt
area/node-ota
area/osint
area/recording-audio
area/sdr-radio
area/transcription
board
board:agenda
minutes:draft
minutes:final
role:ceo
role:cfo
role:cio
role:ciso
role:cmo
role:coo
role:cto
Scene/entity extraction, summarizer, AI providers, embeddings
AuthN/Z, roles, tenancy/org scoping, API keys, credentials
Plans, entitlements, AI spend accounting and caps
Incident correlation, linking, lifecycle, severity, corr_debug
Discord bot, radio bots, voice streams, bot tokens, webhooks
Web UI: Next.js app and the edge-node local dashboard
Board process, minutes, unattended runner, agent tooling
Go-to-market, outreach, cold email, discovery conversations
CI/CD, VM, registry, Firestore rules/indexes deploy, Terraform/Caddy
ToS, retention, licences/DAAs, CAN-SPAM, E&O, name suppression
Live map: pins, paths, tiles, legend, geocoding
Node-C2 MQTT, checkins, commands, node sweeper, dynsec
Node install, enrollment, OTA updater, image skew
External feeds: cameras, 511, ADS-B, AIS, outages, and their licences
Call capture, boundaries, PulseAudio, ring buffer, trimming, attribution
OP25 decode, SDR hardware/tuning, conventional, second SDR, mesh radio
STT, transcript correction, place verification, name accuracy
Board process: minutes and board-filed items
Needs board scoping at the next sitting; CEO removes the label once scoped
One exec pre-read, closed by CEO
CEO ruling; open until enacted
Filed by the CEO
Filed by the CFO
Filed by the CIO
Filed by the CISO
Filed by the CMO
Filed by the COO
Filed by the CTO
kind
behavior-bug
Wrong product behavior; the instrument reads it correctly
kind
measurement-bug
Corrupts the recording/instrument (debug data, evals, health checks), not behavior
priority
p0
Drop everything: prod broken, live exposure, or owner-ruled P0
priority
p1
Next up: blocks a gate, a customer, or core incident quality
priority
p2
Normal: real defect or planned work, schedule in turn
priority
p3
Low: minor, cosmetic, or someday
status
blocked
Waiting on an external party, spend, or another issue
status
in-progress
Branch or PR open against it
status
needs-decision
Waiting on an owner or board ruling before work
status
needs-info
Root cause or repro unknown; needs a reading first
type
bug
Something built is wrong: code, data, or a measurement
type
chore
Maintenance, refactor, tests, pinning, cleanup: no user-visible change
type
decision
A ruling to make or record: board minutes, owner calls, legal reviews
type
docs
Docs, runbooks, CLAUDE.md, skills, SOPs
type
feature
New capability or an extension of an existing one
type
security
Security, privacy, credential or tenant-isolation exposure
type
tracking
Tracker, checklist, initiative or punch-list spanning several changes
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: logan/server-26#76
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the standing correlation pass on the 2026-08-25 evening unattended run. This one is about money, not correctness.
The operating assumption is that AI is off during the week for cost, and that
config/ai_featuresis the switch. Two paths escape it.1. Transcript correction is not behind any Firestore flag
transcription.py:208callstranscript_correction.correct()from insidetranscribe_call, gated only bysettings.transcript_correction_enabled(config.py:52-53, default True, modelgemini-3.6-flash). It also issues a Places Text Search per proposed location viaplace_verifier.Consequence: an "STT-only" window is not STT-only. Turning on
stt_enabledalone still spends Gemini tokens and Places quota on every call. Killing it needs an env change plus an ansible run — it is not reachable from the admin surface at all.This is not a bug in isolation (#36 deliberately moved correction into transcription), but it means the cost model of a window is wrong, and #45's seven-day AI spend measurement will attribute this spend to the wrong place.
2. The transcript PATCH route bypasses the flags entirely
_run_extraction_pipeline(upload.py:162-229) checks no flags at all. It is reachable fromPATCH /calls/{id}/transcript(calls.py:224), which therefore runs Gemini extraction and correlation with AI globally off.Bounded in practice — it is a manual admin action, not traffic-driven — but it means "AI is off" is not an enforceable statement about the system, only about the ingest path.
Why this matters now
Gate B5 (#45) requires seven contiguous days of real AI spend to build a cost model, and the board is waiting on the owner for a dollar cap. A spend path that is invisible to the flag that supposedly controls spend makes that measurement unsound before it starts.
Fix
Bring both under
get_flags(): gatetranscript_correction.correct()on a flag readable fromconfig/ai_features, and have_run_extraction_pipelineresolve flags the same way the upload path does. Add the correction model to whatever #45 ends up measuring.Refs #45, #50, #36, #37, #57.
Heads-up for the next unattended run: the fix for this is already written and sitting uncommitted in the working tree -
summarizer.py(+7),incidents.py(+6/-2),upload.py(+125/-53, adds_resolve_flagsapplying the per-systemai_flagsoverride on top of globalconfig/ai_features). It also covers #75 and #81.It was never committed because the runs that would have finished it died on usage limits (see #82). Do not rewrite it - review, run the c2-core suite, then commit.
upload.pyis a pipeline file, so tail c2-core logs for ~10 min after the deploy per the standing safeguard in #62.Heads-up for the next unattended run: the fix for this is already written and sitting uncommitted in the working tree -
summarizer.py(+7),incidents.py(+6/-2),upload.py(+125/-53, adds_resolve_flagsapplying the per-systemai_flagsoverride on top of globalconfig/ai_features). It also covers #75 and #81.It was never committed because the runs that would have finished it died on usage limits (see #82). Do not rewrite it - review it, run the c2-core suite, then commit.
upload.pyis a pipeline file, so tail c2-core logs for ~10 min after the deploy per the standing safeguard in #62.Fixed and deployed.
d18e4f0, live on prod/healthat 2026-08-27 06:55 UTC. "AI is off" is now an enforceable statement about the system.Path 1 — transcript correction. Now behind a new
transcript_correction_enabledkey inconfig/ai_features, reachable from the admin surface instead of only via an env var plus an ansible run. Thesettings.transcript_correction_enabledenv check still applies underneath as a hard master off. An STT-only window is now actually STT-only, which is what #45 needs to attribute spend.Path 2 — the transcript PATCH.
_run_extraction_pipelineis gated. More importantly, the route that reaches it now refuses outright.That second part came out of the pre-push review and is worth recording, because gating extraction alone would have been a worse bug than the one being fixed.
PATCH /calls/{id}/transcriptwipestags,severity,location,units,vehiclesandembedding, and unlinks the call from every incident, before queueing re-extraction. With correlation off — the standing weekday state — the wipe would have run and the rebuild would not, leaving the call permanently blank and orphaned while the route still answered200. It now raises 409 before the first write. The wipe and the rebuild are one transaction in intent.Third path, found in the same function:
learn_from_correctionwas spending withvocabulary_learning_enabledoff. Also gated.Correction: this issue's body describes
_run_extraction_pipelineas the reprocess path. It is not —/calls/{id}/reprocessuses_run_intelligence_pipeline, which was already gated.PATCH /calls/{id}/transcriptis its only caller, so #7's bulk-reprocess premise was never affected.Follow-up filed: #83.
transcript_correction_enabledis not a pure cost lever — the corrector is also the noise gate that setsnot_speech, so turning correction off while correlation is on pushes over-merging. Defaults to True, both flags off in prod, nothing currently exposed. Recorded inDEFERRED.mdand in a comment above the flag.Also in this commit: the summarizer's stale-incident sweep moved outside
summaries_enabled. It is pure Firestore with no model call, and gating it meant nothing auto-resolved while AI was off — every incident stayed active forever and the candidate set every correlation reads kept growing.14 tests added. Suite: 264 passed, 0 failed.
Closing. #45 is now unblocked on this front. #81's auth half remains open.