Files
shopdb-flask/.github/copilot-instructions.md
benjamin.baker 86985efcf2 Fix user-context collection, split agent log destinations, widen upload cap
Mapped drives stopped reporting because the user-context path failed silently
in three separate places, and none of them left a trace to read back.

- Probe: launch via -EncodedCommand instead of staging a temp .ps1 and running
  powershell.exe -File. A missing or quarantined script file fails BEFORE any
  statement runs, so the script's own try/catch never sees it and the only
  symptom is exit -196608.
- Spool: pin to the ShopFloor profile. Resolving it through WTSQueryUserToken
  is privileged, endpoint protection blocks it, and on failure the tray
  (writer) and service (reader) silently disagree about the path.
- Spool: consume after a successful parse, so a stopped tray cannot keep
  re-reporting stale mapped drives as current. Unparseable files are left in
  place for the next cycle.
- Logging: user-session components cannot write to Program Files or to the
  SYSTEM-owned ProgramData log directory, and the file sink degrades to a
  silent no-op when it cannot create its directory. Tray and probe now log to
  C:\Users\ShopFloor\ShopDB\Logs, installs transcript to ProgramData, and the
  sink records a SinkProblem instead of swallowing UnauthorizedAccessException.
- Installer: provision the operator spool/log directories with ShopFloor:Modify
  before the tray first runs, and stop the transcript via trap so a throw
  cannot orphan exactly the failed install worth reading.
- Upload cap 64 -> 128 MB: the self-contained tray publishes at ~69 MB and was
  rejected on SIZE, but the size check returns before the PE is parsed, so the
  operator only ever saw a version-detection failure.

AGENT-CONTRACT.md gains a normative section defining the three security
contexts, their log destinations, the fixed spool path and consume-on-read.

Agent 1.8.0 -> 1.8.1, tray 1.1.0 -> 1.1.1 so the deployed build is
identifiable from a log line.
2026-09-05 14:20:07 -04:00

98 lines
4.9 KiB
Markdown

# Copilot instructions for shopdb-flask
Follow these when suggesting code. They are enforced by a naming/style hook and
by CI (`.github/workflows/ci.yml`) - suggestions that break them fail the build.
`CONTRIBUTING.md` is the full authority; this is the short version.
## General Guidelines
- Finish the in-flight task before starting new workstreams.
- When asked to "take notes" on a future idea, capture it in the notes file only - do not implement it, and do not expand on it at length in replies.
## Naming (LOCKED - the hook rejects violations)
- **DB tables**: lowercase, concatenated, plural. No underscores, no dashes.
`machines`, `networkdevices`, `businessunits` - NOT `machine_types`, `BusinessUnits`.
- **DB columns**: lowercase, concatenated, singular. No underscores.
`machineid`, `lastzabbixsync`, `isactive` - NOT `machine_id`, `last_zabbix_sync`.
- **Foreign keys**: referenced table (singular) + `id`: `locationid`, `vendorid`.
- **Booleans**: `is`/`has` prefix: `isactive`, `isshopfloor`.
- **Index names**: `idx_<table>_<column>` (underscores allowed here only).
### Python
- A variable, attribute, function, or dict key that holds a DB value MUST match
the column name exactly - do NOT convert to snake_case.
Column `machineid` -> `Machine.machineid`, `{"machineid": 1}`, local `machineid`.
- Pure code that does NOT mirror a DB field uses normal snake_case (PEP 8):
`loop_count`, `current_user`, `validate_input()`.
- Classes: PascalCase, spelled out (`NetworkDevice`, `AssetType`).
### JavaScript / Vue
- A JS variable holding an API field value matches the API key exactly - do NOT
camelCase it. API `{"machineid": 1}` -> `response.machineid`, never `machineId`.
- Components: PascalCase (`AssetDetail.vue`). CSS classes: lowercase-with-dashes.
### API
- Endpoints: lowercase plural nouns, no underscores/dashes: `/api/networkdevices`.
- Query params + response keys match column names: `?locationid=5`,
`{"machineid": 1, "lastzabbixsync": "..."}`.
### Allowed acronyms only
Universal: id url api http https json jwt sql os ip dns csv pdf cors ttl uuid
html css orm. Domain: cmm cnc pc usb vnc winrm ssh ssl tls tcp udp smtp ldap
vlan sso dnc focas clm mtconnect. Anything else: spell it out.
### Banned shorthand
Never use `cfg ctx mgr req res env util helper` or `db` as a standalone variable
name. Spell out: `config context manager request response environment utilities`.
`_bp` is fine only as a suffix with a meaningful prefix (`printers_bp`).
## Style (ASCII only)
- NO emojis anywhere - code, comments, strings, UI.
- NO em-dashes, en-dashes, Unicode arrows, or smart quotes. Plain ASCII only.
- Comments default to NONE. Add one only when the WHY is non-obvious; keep inline
`#`/`//` comments terse. Docstrings stay normal English.
- Dark theme is the default; keep UI functional and professional.
## Architecture (do not violate)
- **Plugins are the product.** Plugin code lives in `plugins/<name>/{models,api,services,schemas}/`
with a `manifest.json` (single source of truth: name, version, dependencies,
api_prefix) and a `BasePlugin` subclass in `plugin.py`.
- **Plugins never import core internals.** Use the contract surface `shopdb.api`
(e.g. `from shopdb.api import db, Asset, success_response`). Adding to that
surface is a contract-version bump + a `docs/PLUGIN-HOOKS` update in the same PR.
- **Migrations, never `db.create_all()`.** The core Alembic chain is in
`migrations/versions/`; each plugin owns its own chain under
`plugins/<name>/migrations/`. New schema = a new migration with an idempotent
guard and a real downgrade. Migrations must run clean on strict MySQL 8.
Any model column/table change ships WITH its migration in the SAME change, and
bringing up a new image MUST run `flask db upgrade` then
`flask plugin upgrade-all` before serving traffic. A model that references a
column its migration has not applied yet 500s every query on that table
(`1054 Unknown column`), so code and schema must never be deployed out of step.
- **Asset model is the platform contract.** Physical things are an `Asset` plus a
plugin subtype row linked by `assetid` (FK, `ON DELETE CASCADE`). Consumables
with quantities are standalone tables, not assets.
- **Ledger pattern**: a cached `quantityonhand` moves in the SAME commit as the
signed transaction row it reflects.
- **Configuration for endpoint agent**: The endpoint agent's live collector key and base URL are provisioned in the Windows registry `HKLM\SOFTWARE\GE\ShopDB` (values `CollectorKey`, `BaseUrl`), not committed in `appsettings.json`; `appsettings.json` holds only non-secret runtime settings.
## Before you finish a change
Run the three gates (CI runs the same):
```
python -m pytest tests/ -q
cd frontend && npx vitest run && npm run build && cd ..
bash scripts/check-naming-and-style.sh
```
Commits: short present-tense subject, body says WHY. No AI/tool attribution in
commit messages, code comments, or docs.