4 new pages: - ADR-019: Plugin Dynamic Settings Routes - Security Audit 2026-09-11 (4 critical + 6 high findings) - Maintenance Footprint Baseline (936 hrs/year burden) - Architectural Findings 2026-09-11 (22 findings) 7 existing pages need updates: - PLUGIN-DEVELOPMENT.md, API-REFERENCE.md, DEPLOYMENT.md - CONTRIBUTING.md, TESTING.md, SECURITY.md - New: Automation-Skills.md Ready to copy to GitHub wiki. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
444 lines
16 KiB
Markdown
444 lines
16 KiB
Markdown
# 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.<other>` 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`
|