The lean-build job proved the delete-a-plugin guarantee with two plugins and four hand-picked marker components. It now builds a real twelve-plugin set, and the assertions follow a rule instead of a hunch: one component from EVERY omitted plugin must be absent, one from EVERY chosen plugin must be present, and the backend tree is checked both ways. An omitted set that grows without its check growing is how a leak ships. CHANGELOG had not been touched across the whole range, so the largest security-behaviour change in the product - per-PC endpoint credentials, the shared-key refusal for enrolled PCs, server pinning, agent 1.20.1 and tray 1.7.1 - had no release record. An [Unreleased] section now carries it, written from the commits rather than from memory. CLAUDE.md and the agent quick-reference stated that ADR-016 bans storing credentials in the registry. It does not, and this fleet depends on HKLM for its collector key and server pins: the next person following that text removes them and silently breaks reporting and pinning on every un-enrolled PC. Corrected, along with two other statements that were false on this branch - the database is MySQL, not PostgreSQL, and column names are lowercase concatenated, not snake_case, which is the opposite of what the gate enforces. The wiki drafts are updated against the code as it stands, each old finding given a verdict with a file and line rather than left as an assertion from a week ago, and a new page records the ADR conformance result - including that the ADR set itself is not on this branch, so nothing here enforces it except the naming and style script. They are also scrubbed of the things that must not reach a published wiki: the site name, the internal git server, developer home paths and the assistant tooling. They now pass the repository's own publishability patterns, which is what the scrub is for.
160 lines
7.2 KiB
Markdown
160 lines
7.2 KiB
Markdown
# 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_<table>_<column>`. 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.
|