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.
98 lines
4.9 KiB
Markdown
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.
|