Mapped drives reported Unavailable on endpoints where the operator could see them fine. The data path was intact; the collection was running under the wrong token. Mapped drives are per logon token, so an elevated probe enumerates a different drive table than the interactive user. Collection: - Pin the user-context spool path so the probe and service agree on one location, and persist/overwrite it instead of deleting on read. - Refuse to collect while elevated (exit code 2) rather than emitting a confident wrong answer, and stamp elevated/username on the spool. - Reject spools stamped elevated or from an unexpected account on read, so an admin run cannot masquerade as valid. - Log netuse/smbmapping/userlistener counts so each run leaves evidence of what it actually saw. Install and launch context: - Move collection to a 'ShopDB user-context probe' scheduled task under S-1-5-32-545 at RunLevel Limited, the only mechanism that reliably runs in the operator's own session. Verify registration and record it in the install ledger. - Stop starting the tray as a child of the elevated installer; explorer.exe de-elevates without changing ACCOUNT, so an admin install produced an admin tray. The Run key only fires at logon, which left the tray dead on a machine already signed in, so add a transient 'ShopDB tray first launch' task that starts it once in the existing session and unregisters itself. - Hide the console window during --collect-usercontext. The agent is a console exe; the tray used to mask this with CreateNoWindow, but the scheduled task invokes it directly and flashed a window every cycle. Installer edits are in _install_script() in plugins/endpoints/api/siteagent.py, which generates the install.ps1 that actually ships. The standalone agent/installer/install-shopdbagent.ps1 was never shipped once the package moved to the UI builder and is deleted here to stop the divergence that caused an earlier fix to land in a file no endpoint ever ran. Versions: agent 1.10.1, tray 1.3.0. These share the pinned spool path and must be deployed together. Pre-existing test failures unrelated to this work (8, identical on a clean tree): docs publishable/versions, plugin contract symlink scan, import guard tamper check, security headers upload sandbox, and three endpoint timestamp tests.
5.6 KiB
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.
- Use a temp file for multi-line git commit messages instead of heredoc syntax:
git commit -F <file>. - Avoid using
powershell.exe -Filefor launching scripts, as it fails if the script file is missing or quarantined.
Installer Logic
- The endpoint agent installer shipped to endpoints is generated by
_install_script()inplugins/endpoints/api/siteagent.py, NOTagent/installer/install-shopdbagent.ps1. Any installer logic change must be made insiteagent.py, followed by rolling the containers. - The user's workflow: the agent hands over only the built EXEs (
shopdbagent.exe,shopdb-endpoint-tray.exe); the user uploads them via the "Upload New/Legacy Agents" field in the Build Site Agent UI and builds the package with the newest version selected at the top.
Naming (LOCKED - the hook rejects violations)
- DB tables: lowercase, concatenated, plural. No underscores, no dashes.
machines,networkdevices,businessunits- NOTmachine_types,BusinessUnits. - DB columns: lowercase, concatenated, singular. No underscores.
machineid,lastzabbixsync,isactive- NOTmachine_id,last_zabbix_sync. - Foreign keys: referenced table (singular) +
id:locationid,vendorid. - Booleans:
is/hasprefix: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}, localmachineid. - 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, nevermachineId. - 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 amanifest.json(single source of truth: name, version, dependencies, api_prefix) and aBasePluginsubclass inplugin.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 + adocs/PLUGIN-HOOKSupdate in the same PR. - Migrations, never
db.create_all(). The core Alembic chain is inmigrations/versions/; each plugin owns its own chain underplugins/<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 runflask db upgradethenflask plugin upgrade-allbefore 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
Assetplus a plugin subtype row linked byassetid(FK,ON DELETE CASCADE). Consumables with quantities are standalone tables, not assets. - Ledger pattern: a cached
quantityonhandmoves 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(valuesCollectorKey,BaseUrl), not committed inappsettings.json;appsettings.jsonholds 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.