Automated Architecture Validation Report
Date: 2026-04-20
Result: 6 violations found — 2 critical (P0-P1), 4 non-critical
Critical Findings (P0-P1)
Invariant #1 — Three-Layer Backend: Router → Service → DB
Routers must not contain SQL. Found direct SQL in router layer:
src/backend/routers/processes.py:777-866 — Raw sqlite3.connect, cursor.execute, CREATE TABLE IF NOT EXISTS process_schedules, INSERT OR REPLACE, DELETE all in router function _sync_process_schedules_to_db (and _remove_process_schedules)
src/backend/routers/voice.py:176-183 — cursor.execute("UPDATE agent_ownership SET voice_system_prompt = ...") directly in router endpoint
src/backend/routers/voice.py:304-310 — cursor.execute("SELECT voice_system_prompt ...") directly in router endpoint
src/backend/routers/triggers.py:252-260 — cursor.execute("SELECT * FROM process_schedules ...") directly in router function
Fix: Extract SQL into db/process_schedules.py and db/agent_settings/voice.py mixin; create corresponding service methods.
Invariant #3 — Schema in db/schema.py, Migrations in db/migrations.py
Found CREATE TABLE outside schema.py and migrations.py:
src/backend/routers/processes.py:780 — CREATE TABLE IF NOT EXISTS process_schedules created ad-hoc in router function
src/backend/services/process_engine/services/templates.py:117 — CREATE TABLE IF NOT EXISTS process_templates created ad-hoc in service constructor
src/backend/services/process_engine/services/alerts.py:163,178 — CREATE TABLE IF NOT EXISTS cost_thresholds and CREATE TABLE IF NOT EXISTS cost_alerts created ad-hoc in service constructor
Fix: Add these tables to db/schema.py and create migrations in db/migrations.py. Remove ad-hoc CREATE TABLE calls.
Non-Critical Findings (P2-P3)
Invariant #6 & #7 — Frontend Store = Domain / Single API Client
Several views bypass the Pinia store and call the API directly (raw fetch()) rather than going through api.js:
ApiKeys.vue (lines 378–498): multiple raw fetch('/api/mcp/keys', ...) calls
AgentDetail.vue (lines 382, 422): raw fetch('/api/agents/.../autonomy', ...) calls
HostTelemetry.vue:35: raw fetch(API_BASE + '/api/telemetry/host', ...)
router/index.js:191: raw fetch('/api/setup/status')
- Process views (
ProcessWizard.vue, ProcessList.vue, ProcessEditor.vue) make direct api.get/post calls bypassing stores
Fix: Move API calls into Pinia stores; use api.js client throughout.
Invariant #10 — Process Engine DDD Isolation
Router layer imports directly from services.process_engine.domain (should go through application services):
routers/approvals.py:17, routers/triggers.py:21, routers/processes.py:17, routers/process_templates.py:185, routers/executions.py:19
Fix: Expose domain types through a service-layer facade or response models in models.py.
Invariant #15 — Pydantic Models Centralized in models.py
Model classes defined inline in router files:
fan_out.py, users.py, audit_log.py, image_generation.py, git.py, approvals.py, voice.py, triggers.py, paid.py, telegram.py, alerts.py
Fix: Move Pydantic models to models.py.
Recommended Actions
- (P1) Extract
process_schedules management from routers/processes.py into db/ + service layers; move schema into schema.py/migrations.py
- (P1) Move
voice_system_prompt SQL in routers/voice.py into db/agent_settings/ mixin
- (P1) Add
process_templates, cost_thresholds, cost_alerts tables to schema.py and migrations.py; remove ad-hoc CREATE TABLE in service constructors
- (P2) Consolidate frontend API calls through Pinia stores +
api.js
- (P2) Add service-layer facades for process engine domain types consumed by routers
- (P3) Migrate inline Pydantic models to
models.py
Generated by scheduled /validate-architecture run
Automated Architecture Validation Report
Date: 2026-04-20
Result: 6 violations found — 2 critical (P0-P1), 4 non-critical
Critical Findings (P0-P1)
Invariant #1 — Three-Layer Backend: Router → Service → DB
Routers must not contain SQL. Found direct SQL in router layer:
src/backend/routers/processes.py:777-866— Rawsqlite3.connect,cursor.execute,CREATE TABLE IF NOT EXISTS process_schedules,INSERT OR REPLACE,DELETEall in router function_sync_process_schedules_to_db(and_remove_process_schedules)src/backend/routers/voice.py:176-183—cursor.execute("UPDATE agent_ownership SET voice_system_prompt = ...")directly in router endpointsrc/backend/routers/voice.py:304-310—cursor.execute("SELECT voice_system_prompt ...")directly in router endpointsrc/backend/routers/triggers.py:252-260—cursor.execute("SELECT * FROM process_schedules ...")directly in router functionFix: Extract SQL into
db/process_schedules.pyanddb/agent_settings/voice.pymixin; create corresponding service methods.Invariant #3 — Schema in
db/schema.py, Migrations indb/migrations.pyFound
CREATE TABLEoutsideschema.pyandmigrations.py:src/backend/routers/processes.py:780—CREATE TABLE IF NOT EXISTS process_schedulescreated ad-hoc in router functionsrc/backend/services/process_engine/services/templates.py:117—CREATE TABLE IF NOT EXISTS process_templatescreated ad-hoc in service constructorsrc/backend/services/process_engine/services/alerts.py:163,178—CREATE TABLE IF NOT EXISTS cost_thresholdsandCREATE TABLE IF NOT EXISTS cost_alertscreated ad-hoc in service constructorFix: Add these tables to
db/schema.pyand create migrations indb/migrations.py. Remove ad-hocCREATE TABLEcalls.Non-Critical Findings (P2-P3)
Invariant #6 & #7 — Frontend Store = Domain / Single API Client
Several views bypass the Pinia store and call the API directly (raw
fetch()) rather than going throughapi.js:ApiKeys.vue(lines 378–498): multiple rawfetch('/api/mcp/keys', ...)callsAgentDetail.vue(lines 382, 422): rawfetch('/api/agents/.../autonomy', ...)callsHostTelemetry.vue:35: rawfetch(API_BASE + '/api/telemetry/host', ...)router/index.js:191: rawfetch('/api/setup/status')ProcessWizard.vue,ProcessList.vue,ProcessEditor.vue) make directapi.get/postcalls bypassing storesFix: Move API calls into Pinia stores; use
api.jsclient throughout.Invariant #10 — Process Engine DDD Isolation
Router layer imports directly from
services.process_engine.domain(should go through application services):routers/approvals.py:17,routers/triggers.py:21,routers/processes.py:17,routers/process_templates.py:185,routers/executions.py:19Fix: Expose domain types through a service-layer facade or response models in
models.py.Invariant #15 — Pydantic Models Centralized in
models.pyModel classes defined inline in router files:
fan_out.py,users.py,audit_log.py,image_generation.py,git.py,approvals.py,voice.py,triggers.py,paid.py,telegram.py,alerts.pyFix: Move Pydantic models to
models.py.Recommended Actions
process_schedulesmanagement fromrouters/processes.pyintodb/+ service layers; move schema intoschema.py/migrations.pyvoice_system_promptSQL inrouters/voice.pyintodb/agent_settings/mixinprocess_templates,cost_thresholds,cost_alertstables toschema.pyandmigrations.py; remove ad-hocCREATE TABLEin service constructorsapi.jsmodels.pyGenerated by scheduled /validate-architecture run