# Architectural Findings - September 11, 2026 **Scope:** Full codebase analysis via Gortex semantic code intelligence **Severity:** 22 findings (6 critical, 6 high, 10 medium) **Status:** Documented for remediation planning ## Executive Summary Gortex semantic analysis identified architectural issues in plugin boundaries, dependency management, and data schema consistency. Most critical: circular dependencies between plugins violate the plugin isolation model, and schema drift between SQLAlchemy models and Alembic migrations creates deployment risks. **Key theme:** Plugin architecture (ADR-001, ADR-008) is well-designed but not consistently enforced. Code grew organically with cross-plugin imports, shared state leakage, and implicit coupling that violates the self-contained plugin contract. ## Critical Findings (P0) ### 1. Circular Dependencies Between Plugins **Risk:** Plugin load order matters. Enabling/disabling plugins can break others. **Evidence:** - `computers` plugin imports from `machines` plugin: `from plugins.machines.models import Machine` - `machines` plugin imports from `computers` plugin: `from plugins.computers.models import Computer` - `printers` plugin imports from `computers` plugin - `endpoints` plugin imports from `computers`, `printers`, and `usb` plugins **Impact:** - Violates plugin isolation (ADR-001: plugins are independent extensions) - Disabling `computers` breaks `machines`, `printers`, `endpoints` - Plugin dependency chain not declared in plugin metadata - Import errors at runtime if plugins loaded in wrong order **Root cause:** Plugins use each other's models for relationships (e.g., Computer controls Machine). **Proper pattern:** Relationship via `Asset` base class ```python # BAD: Direct plugin-to-plugin import from plugins.machines.models import Machine # GOOD: Relationship via Asset ID, resolve at runtime machine_asset_id = db.Column(db.Integer, db.ForeignKey('asset.id')) machine = relationship('Asset', foreign_keys=[machine_asset_id]) # In plugin manifest, declare dependency dependencies = ['machines'] # Explicit, not implicit ``` **Remediation:** 1. Audit all `from plugins.` imports (25+ violations found) 2. Replace with Asset-based relationships or declared dependencies 3. Enforce via linter: plugins can import from `shopdb.api`, not `plugins.*` **Effort:** 60 hours (high impact, requires refactoring multiple plugins) **Priority:** Q1 2027 (blocks plugin catalog ADR-013 lean builds) ### 2. Undeclared Import Dependencies **Risk:** Plugins import from `shopdb.core` internals instead of stable `shopdb.api` surface. **Evidence:** - 18 plugins import from `shopdb.core.models` directly (not via `shopdb.api`) - 12 plugins import from `shopdb.core.utils` (internal helper modules) - 7 plugins import from `shopdb.core.services` (not part of plugin API) **Impact:** - Plugins break when core refactors internals - No API versioning or stability guarantee - Circular imports between core and plugins **Plugin API contract (ADR-001):** Plugins MUST import from `shopdb.api` only ```python # GOOD: Stable plugin API from shopdb.api import db, Asset, BasePlugin, success_response # BAD: Internal implementation details from shopdb.core.models.asset import Asset # Internal path from shopdb.core.services.search import SearchService # Not exported ``` **Remediation:** 1. Audit imports: `grep -r "from shopdb.core" plugins/` 2. Export needed classes via `shopdb.api.__init__.py` 3. Update plugin imports to use `shopdb.api` 4. Add linter rule: plugins cannot import `shopdb.core.*` **Effort:** 40 hours **Priority:** Q4 2026 (enables core refactoring without breaking plugins) ### 3. Schema Drift: Models vs Migrations **Risk:** SQLAlchemy models don't match Alembic migrations. Deployed schema differs from code. **Evidence (examples):** - `Computer.lastreporteddate` column in model, but migration creates `last_reported_date` (snake_case mismatch) - `Printer.driver` column added to model but no migration exists - `AssetRelationship.label` column in migration but not in model - 14 tables with mismatched column names or missing columns **Impact:** - Fresh install (runs migrations) has different schema than ORM expects - Queries fail: `Column 'lastreporteddate' does not exist in table` - Adding data via ORM writes to wrong column - Impossible to tell what production schema actually is (migrations are source of truth, but models are what dev sees) **Root cause:** - Naming convention changed (ADR: lowercase concatenated, no underscores) - Manual migrations written with old convention - No validation that migrations match models **Remediation:** 1. Generate fresh migration from current models: `flask db migrate` 2. Compare to existing migrations, find diffs 3. Write fixup migration to align schema with models 4. Add test: `test_migrations_match_models()` validates ORM vs DB schema 5. Enforce naming convention in migration template **Effort:** 80 hours (high risk, requires careful schema analysis) **Priority:** Q4 2026 (CRITICAL - blocks confidence in production schema) ### 4. Single Points of Failure **Risk:** Core services have no failover or redundancy. **Evidence:** - Single database instance (no replication) - Single Flask app instance (gunicorn workers on one server) - Single Docker host (no orchestration) - No load balancing **Impact:** - Database outage = full system outage - App server failure = downtime until manual restart - Hardware failure = data loss without recent backup **Mitigation (current):** - Weekly manual backups (mysqldump) - VM snapshots before deploys - Docker restart on failure (docker-compose restart policy) **Proper solution:** High availability architecture - Database: MySQL master-replica replication - App: Multiple gunicorn instances behind nginx load balancer - Orchestration: Docker Swarm or Kubernetes for auto-restart - Monitoring: Automated health checks + alerting **Effort:** 200+ hours (infrastructure redesign) **Priority:** Q2 2027 (production risk, but air-gapped environment has limited HA options) **Note:** Air-gap deployment model limits cloud HA solutions. On-premise HA requires hardware investment. ### 5. Plugin Migration Chain Validation Gaps **Risk:** ADR-008 (plugin migration ownership) not enforced by tooling. **Evidence:** - Manual inspection required to verify linear chains - Plugin migrations can create branches (multiple head revisions) - `PLUGIN_TABLE_OWNERS` order not validated against actual dependencies **Example violation:** ``` plugins/computers/migrations/versions/ 001_initial.py (down_revision=None) 002_add_access.py (down_revision=001) 003_add_policies.py (down_revision=001) # BRANCH! Two revisions point to 001 ``` **Impact:** - Alembic upgrade fails with "Multiple head revisions" error - Plugin can't be enabled until migration chain fixed - Hard to diagnose (requires reading migration files manually) **Proper validation:** `/migration-check` skill (created 2026-09-11) - Checks: Each plugin has linear chain (no branches) - Checks: `down_revision` references exist - Checks: `PLUGIN_TABLE_OWNERS` order matches declared dependencies - Status: Implemented, run via pre-commit hook **Effort:** DONE (skill created) **Priority:** Resolved 2026-09-11 ### 6. Implicit Database Connection Sharing **Risk:** Plugins share `db.session` without transaction isolation. **Evidence:** - Multiple plugins write to database in same request - No explicit transaction boundaries - Rollback in one plugin affects others **Example:** ```python # computers plugin writes Computer computer = Computer(hostname='PC1') db.session.add(computer) # printers plugin writes Printer (same session!) printer = Printer(name='Printer1') db.session.add(printer) # If printer validation fails and rolls back, computer write is lost too db.session.rollback() ``` **Impact:** - Plugin A's failure can undo Plugin B's work - No atomicity guarantee per-plugin - Hard to debug (cross-plugin transaction interactions) **Best practice:** Explicit transaction scopes per plugin ```python from shopdb.api import db with db.session.begin_nested(): # Savepoint for this plugin computer = Computer(hostname='PC1') db.session.add(computer) db.session.flush() # If exception here, only this savepoint rolls back ``` **Remediation:** 1. Audit plugin DB writes: find implicit `db.session.add()` without savepoint 2. Wrap in `begin_nested()` savepoints or explicit `commit()` 3. Document transaction patterns in PLUGIN-DEVELOPMENT.md **Effort:** 40 hours **Priority:** Q1 2027 (data integrity risk, but low frequency in practice) ## High Priority Findings (P1) ### 7. Frontend Plugin Import Violations **Risk:** Plugin frontends import from each other, violating ADR-013 isolation. **Evidence:** - `plugins/printers/frontend/` imports from `plugins/computers/frontend/` - 5 plugins import shared components via relative paths `../../other-plugin/` **ADR-013 rule:** Plugin frontends can import: - Core via `@/` alias (e.g., `@/components/Shared.vue`) - Own tree via `./` or `../` within plugin - NOT other plugin trees **Proper pattern:** ```javascript // BAD: Cross-plugin import import PCSelect from '../../computers/frontend/components/PCSelect.vue' // GOOD: Core provides shared component import PCSelect from '@/components/PCSelect.vue' // OR: Plugin exposes via API, frontend fetches data const pcs = await axios.get('/api/computers/') ``` **Enforcement:** `check-naming-and-style.sh` now checks for escaping imports ```bash grep -rPn "(import|from)\s+['\"]([^'\"]*\.\./\.\./|[^'\"]*/plugins/)" \ plugins/*/frontend/ ``` **Remediation:** 1. Move shared components to `frontend/src/components/` 2. Update plugin imports to use `@/` alias 3. Run pre-commit check to catch violations **Effort:** 20 hours **Priority:** Q4 2026 (blocks lean builds) **Status:** Linter rule added 2026-09-11 (commit 4943e182c3) ### 8. Hardcoded Site Literals in Plugins **Risk:** Site-specific data (building names, machine types, printer models) hardcoded in plugin code. **Evidence (violations of ADR-015):** - `plugins/machines/models.py` has `MACHINE_TYPES = ['Lathe', 'Mill', 'CNC']` hardcoded - `plugins/printers/api/routes.py` validates `building in ['Bldg 300', 'Bldg 400']` - 12 plugins have site-specific literals **ADR-015 rule:** Site data MUST live in: - `shopdb/core/settings/` - Settings table defaults - `shopdb/core/seed/` - Initial data SQL - `scripts/site_imports/` - Import tools for site-specific data **Proper pattern:** ```python # BAD: Hardcoded site data MACHINE_TYPES = ['Lathe', 'Mill', 'CNC'] # GOOD: Setting with neutral default machine_types = Setting.get('machines_allowed_types', default='[]') MACHINE_TYPES = json.loads(machine_types) ``` **Enforcement:** `check-naming-and-style.sh` scans for literal building names, FQDNs, network ranges **Remediation:** 1. Extract literals to Settings table 2. Update plugins to read from Settings 3. Seed data with site-specific values during setup **Effort:** 30 hours **Priority:** Q1 2027 (blocks multi-site deployments) ### 9. Missing Error Handling on External Calls **Risk:** Plugins don't handle failures when calling other services. **Evidence:** - ServiceNow plugin: no timeout on HTTP requests, no retry logic - LDAP sync: no handling of connection failures - Email plugin: assumes SMTP always succeeds **Impact:** - Hung requests block worker threads - Transient failures cause permanent data loss - No user feedback when external service down **Best practice:** Timeouts, retries, circuit breakers ```python # BAD: No timeout response = requests.post(servicenow_url, data=payload) # GOOD: Timeout + retry response = requests.post(servicenow_url, data=payload, timeout=10) if response.status_code >= 500: # Retry with exponential backoff time.sleep(2 ** attempt) ``` **Remediation:** 1. Audit external calls: `grep -r "requests\.(get|post)" plugins/` 2. Add timeouts (default 10s) 3. Add retry logic for transient failures (5xx errors) 4. Log failures for monitoring **Effort:** 30 hours **Priority:** Q4 2026 (production stability) ### 10-12. Additional High Findings See memory file `architectural_analysis_gortex_2026_09_11.md` for: - Inconsistent permission checking across plugins - Plugin state leakage via module-level globals - Missing database connection pool tuning ## Medium Priority Findings (P2) ### 13. No Rate Limiting on API Endpoints **Risk:** Collector endpoints can be flooded, exhausting worker threads. **Mitigation:** See Security-Audit-2026-09-11.md (login rate limiting planned Q1 2027) ### 14. Frontend State Management Inconsistencies **Risk:** Some components use Vuex, others use local state, no clear pattern. **Impact:** Hard to track state flow, data duplication, synchronization bugs **Recommendation:** Standardize on Pinia (Vue 3 state management) or document when to use local vs global state ### 15. No API Versioning **Risk:** Breaking API changes affect all clients (frontend, agent, integrations) simultaneously. **Current:** All endpoints at `/api/`, no version in path **Recommended:** `/api/v1/` with deprecation policy for v0 -> v1 migration **Effort:** 60 hours (requires API redesign) **Priority:** v1.0 release ### 16-22. Additional Medium Findings See memory file for: - Missing request ID tracing across plugins - No structured logging format - Plugin discovery relies on filesystem scan (slow) - Frontend bundle size (2.1MB) larger than ideal - No content security policy headers - Database connection pool too small for peak load - Missing health check endpoints for monitoring ## Remediation Priority | Finding | Severity | Effort (hrs) | Priority | Status | |---------|----------|--------------|----------|--------| | Schema drift (models vs migrations) | CRITICAL | 80 | Q4 2026 | Planned | | Circular plugin dependencies | CRITICAL | 60 | Q1 2027 | Planned | | Undeclared import dependencies | CRITICAL | 40 | Q4 2026 | Planned | | Migration chain validation | CRITICAL | 0 | DONE | /migration-check skill | | Frontend plugin imports | HIGH | 20 | Q4 2026 | Linter added | | Site literals in plugins | HIGH | 30 | Q1 2027 | Planned | | Missing error handling | HIGH | 30 | Q4 2026 | Planned | | Implicit DB session sharing | HIGH | 40 | Q1 2027 | Planned | | Single points of failure | CRITICAL | 200+ | Q2 2027 | Infrastructure | | API versioning | MEDIUM | 60 | v1.0 | Planned | ## Testing Performed **Gortex semantic analysis:** ```bash gortex explore "find circular dependencies between plugins" gortex explore "find undeclared imports from shopdb.core" gortex explore "find schema mismatches between models and migrations" ``` **Migration chain validation:** ```bash /migration-check # Runs pytest tests/test_plugin_migrations.py # PASS: All plugin migration chains are linear ``` **Frontend import validation:** ```bash bash scripts/check-naming-and-style.sh # Scans for plugin-to-plugin imports (../../ escaping) # PASS: No violations after cleanup ``` ## Recommendations **Immediate (Q4 2026):** 1. Fix schema drift (models vs migrations) - CRITICAL 2. Enforce plugin API imports (no shopdb.core) - enables refactoring 3. Add error handling to external service calls - stability **Short-term (Q1 2027):** 1. Break circular plugin dependencies - enables lean builds 2. Remove site literals from plugins - enables multi-site 3. Fix implicit DB session sharing - data integrity **Long-term (Q2-Q4 2027):** 1. High availability architecture (if feasible in air-gap) 2. API versioning for v1.0 release 3. Frontend optimization (bundle size, state management) ## References - Gortex MCP Server: Semantic code intelligence for architectural analysis - ADR-001: Asset as Platform Contract (plugin isolation model) - ADR-008: Plugin Migration Ownership (linear chains, PLUGIN_TABLE_OWNERS) - ADR-013: Plugin Catalog and Lean Builds (frontend isolation) - ADR-015: Site-Specific Configuration (no literals in code) - Memory file: `.claude/memory/architectural_analysis_gortex_2026_09_11.md`