# ShopDB Flask - Claude Code Context
ShopDB is a manufacturing floor asset management system built with Flask, Vue 3, and MySQL, deployed across air-gapped GE Aerospace sites.
## Architecture
- **Backend**: Flask 3.x with SQLAlchemy 2.x, plugin-based architecture
- **Frontend**: Vue 3 with Vite, component-based UI with plugin panels
- **Database**: MySQL 8 with Alembic migrations per plugin (`docker-compose.yml` runs `mysql:8.4`; WJ prod is IIS in front of MySQL 8)
- **Deployment**: Docker Compose with gunicorn, air-gapped sites via bundle workflow
- **Plugins**: 20+ plugins (computers, endpoints, printers, machines, etc.)
## Critical Constraints
### ADR-015: Site-Specific Configuration
Site literals (building names, printer models, machine types) MUST live in:
- `shopdb/core/settings/` - Site settings (buildings, roles, etc.)
- `shopdb/core/seed/` - Initial data SQL
- `scripts/site_imports/` - Import tools for Maximo/WINC data
NEVER hardcode site-specific data in plugin code or migrations.
### ADR-008: Plugin Migration Ownership
Each plugin owns its migration chain in `plugins/{name}/migrations/versions/`.
- Migrations run in dependency order defined in PLUGIN_TABLE_OWNERS
- Each plugin's chain must be linear (no branches)
- Migrations run automatically on deploy
### Database Naming Convention
- **Tables**: lowercase concatenated, plural, no underscores (e.g., `machines`, `mappeddrives`)
- **Columns**: lowercase concatenated, singular, no underscores (e.g., `machineid`,
`lastzabbixsync`, `isactive`). NOT snake_case - see CONTRIBUTING.md
- **Indexes**: `idx_
_`. Underscores are allowed HERE ONLY, because an
index is infrastructure and not data
- Enforced by `scripts/check-naming-and-style.sh` pre-commit hook, on model attributes
AND on the column names a migration creates
### Branch and Deployment Model
- `wj` -> prod (shop floor at WJ site)
- `dev` -> ops (monitoring/pilot sites)
- `main` -> nothing (GitHub public mirror only)
- Three air gaps: dev PC <-> work PC <-> GitHub
- Bundles carry code across gaps (pull-shopdb-bundle.cmd, fetch-wilm-branch.cmd)
### Code Style
- Plain ASCII only - no unicode, emojis, or em-dashes
- No LLM attribution comments (no "Co-Authored-By: Claude")
- Minimal comments - code should be self-documenting
- No banned shorthand (e.g., "config" not "cfg", "initialize" not "init")
## Plugin Contract
Plugins extend ShopDB via:
- `plugin.py` - BasePlugin subclass with manifest, routes, hooks
- `models/` - SQLAlchemy models
- `api/` - Flask blueprints and routes
- `frontend/views/` - Vue components auto-discovered by router
- `migrations/versions/` - Alembic migrations
Import from `shopdb.api` for stable surfaces. Every name has to be in that
module's `__all__`; this line used to read
`db, Asset, Employee, BasePlugin` and two of the four do not resolve, so the
first block a new agent copied raised ImportError:
```python
from shopdb.api import db, Asset, User, Setting, require_permission
# BasePlugin is NOT on that surface. plugin.py takes it from its own module,
# which is what every plugins/*/plugin.py in this tree does:
from shopdb.plugins.base import BasePlugin, PluginMeta
```
There is no `Employee` model. The directory is an external read-only database
reached through `employee_connection`; the plugin's own row type is
`DirectoryEmployee`. See `.claude/QUICK-REFERENCE.md` for the full export list
and for the other names that are commonly assumed and are not there.
## Documentation
Full documentation in the GitHub wiki at `../shopdb-flask.wiki/`:
**Critical ADRs**:
- ADR-001: Asset as platform contract
- ADR-004: Deployment topology (air-gapped sites)
- ADR-006: Collector contract (endpoint agent)
- ADR-007: Product versioning and releases
- ADR-013: Plugin catalog and lean builds
- ADR-016: Credential delivery to the fleet (cmdkey credentials for bays; ACCEPTED, NOT built - it is not about kiosks or device tokens)
**References**:
- `API-REFERENCE.md` - Core API surface
- `PLUGIN-DEVELOPMENT.md` - Plugin authoring guide
- `DEPLOYMENT.md` - Deploy runbooks per site
- `TESTING.md` - Test patterns and CI
## Development Workflow
1. Feature branches off `dev` or `wj`
2. **Run tests**: `pytest -v` (REQUIRED before claiming completion)
3. **Check naming/style**: `scripts/check-naming-and-style.sh` (REQUIRED before commit)
4. Bundle for other PC or push to GitHub
5. PR to `wj` (for prod) or `dev` (for ops)
## Boundaries & Constraints
**DO NOT**:
- Edit generated files (frontend/dist/, docs/openapi.json)
- Change dependencies (requirements.txt, package.json) without approval
- Commit code with unicode/emojis (plain ASCII only)
- Hardcode site-specific data (buildings, models, etc.) - use settings/seed
- Skip tests or pre-commit hooks
- Break plugin migration chains (keep linear, coordinate via PLUGIN_TABLE_OWNERS)
**ALWAYS**:
- Run `pytest -v` before claiming work complete
- Run `scripts/check-naming-and-style.sh` before committing
- Use `shopdb.api` imports, not internal paths
- Keep database table names lowercase concatenated
- Reference ADRs by number when making architectural decisions
- Verify bundle/deploy scripts work before pushing to branches
## Testing Patterns
- Feature tests in `tests/test_plugins/test_{plugin}_{feature}.py`
- Use fixtures from `conftest.py` (app, client, db_session)
- Test migrations with `test_plugin_migrations.py`
- Frontend specs alongside components: `*.spec.js`
## Common Pitfalls
- **Site data in code**: Use settings/seed, not hardcoded values
- **Migration conflicts**: Keep plugin chains linear, coordinate with PLUGIN_TABLE_OWNERS
- **Unicode**: ASCII only - check logs, comments, commit messages
- **Import surface**: Use `shopdb.api`, not internal paths
- **Kiosk access**: a kiosk route admits a caller on either of two grounds - the
per-kiosk device token, or a source address inside that kiosk's CIDR allowlist
(`usb_kiosk_allowed_cidrs`, `printedparts_kiosk_allowed_cidrs`) - and both are
gated on the kiosk's enable flag. Use `ip_in_cidrs` from `shopdb.api`; do not
hand-roll a CIDR parser. `request_ip_allowlisted` is only
`ip_in_cidrs(client_ip(request), cidrs)` - the USB kiosk does NOT use it,
because it first refuses an address that belongs to the path rather than to a
caller (loopback, or a configured trusted proxy). On the shipped Windows stack
the X-Forwarded-For rewrite rule is disabled, so every caller arrives as
127.0.0.1 and that refusal is what keeps the door shut. The real shape and the
full trace are in `.claude/QUICK-REFERENCE.md`. There is no
`requires_device_token` decorator
- **Fleet credentials are in the registry, deliberately**: the endpoint agent and the
GE-Enforce reporters run as SYSTEM with no user to prompt, and read their collector
key, base URL and server pins from `HKLM\SOFTWARE\GE\ShopDB`. An enrolled PC's
own per-PC credential is DPAPI-protected in a SYSTEM-only directory instead.
Nothing forbids the registry hive - ADR-016 names registry provisioning as a
recommended first step
## Work PC Role
This machine (work PC):
- Has GitHub credentials (ge-aero/shopdb-flask)
- Carries bundles between dev PC and GitHub
- Deploys to prod and ops servers
- Mirrors wilm branch from GitHub to gitea
For active development, use the dev PC with gitea.