Gate endpoint UI refresh on fresh telemetry and land endpoint agent

A command reaching 'succeeded' only proves the agent ran the action, not that it posted a new telemetry snapshot. Reloading panels on command success alone re-rendered stale service/process rows, so a stopped process could still show running right after Refresh Status.

Add a lightweight telemetrymeta probe exposing lastreportedat and gate both the header Refresh Status action and inline row Start/Stop on that stamp advancing past a pre-action baseline. Polling is bounded so an offline or idle agent never hangs the button, and messaging distinguishes a confirmed refresh from a queued one.

Bring the .NET endpoint agent source (service, tray, installer) into the repo so Cameron can merge and test, with an agent/.gitignore keeping bin/obj/publish out. Includes the DncMain launch-path fix, tray single-file host env scrub, and best-effort file logger.
This commit is contained in:
benjamin.baker
2026-08-28 15:07:20 -04:00
parent c001657582
commit af133372b4
46 changed files with 4593 additions and 128 deletions

86
.github/agent-workflow.md vendored Normal file
View File

@@ -0,0 +1,86 @@
# Agent workflow rules
Companion to `copilot-instructions.md` (which owns naming/style/architecture).
These cut wasted turns. All are lessons from real sessions.
## Environment - know these, do not rediscover
- Shell is **PowerShell**: use `;` not `&&`, `2>$null` not `2>/dev/null`.
- API container `shopdb-flask-wilm-api-1` (Flask, port `5001`).
- DB container `shopdb-flask-wilm-db-1` (MySQL 8, root pw `root_password_123`).
- SPA is **baked into the image** (not bind-mounted). Plugin code is
**bind-mounted read-only**.
- `docs/` is stripped to a public wiki mirror - keep working docs out of it.
- `requirements.txt` is hash-checked - never hand-edit.
## Verify, don't react
- The terminal "Command failed" banner fires on any non-zero exit or stderr
output and is **usually a false negative** (`curl --fail` on a 400,
pytest/npm/git writing to stderr). Judge success from real output.
- Read container health via
`docker inspect --format '{{.State.Health.Status}}'` plus a probe **without**
`--fail` - never eyeball the exit code.
## Container runs the image, not your edits
Before running anything in the container that must reflect a change:
1. Frontend: `npm run build`, then
`docker cp frontend/dist/. shopdb-flask-wilm-api-1:/app/frontend/dist`.
2. Backend/scripts/templates: `docker cp` the files (or rebuild) first.
If a result contradicts a change you just made, suspect stale container code
before re-deriving the fix.
## Testing
- Confirm the working directory once, up front.
- Run the smallest test that proves the point; find the minimal polluter for
isolation bugs. Run the full suite only at the end.
- Clear stale local state before plugin/migration tests:
`if (Test-Path instance/plugins.json) { Remove-Item instance/plugins.json -Force }`.
- Naming-gate hits inside local venvs (`.ci-venv/`, `env/`) are not repo code -
do not "fix" vendored libraries.
- Final gates, exactly as CI: `python -m pytest tests/ -q`;
`cd frontend; npx vitest run; npm run build; cd ..`;
`bash scripts/check-naming-and-style.sh`.
## Migrations and imports
- Every migration: idempotent guard + real downgrade; must run clean on strict
MySQL 8. If you rename indexes in `upgrade()`, update `downgrade()` to match.
- Avoid duplicate indexes: `index=True` plus an explicit `db.Index` on the same
column creates two.
- Importers/seeders match by natural key and must be idempotent - prove it by
running twice (expect `updated`, not `created`). Watch all-or-nothing writes.
## Don't offer rollbacks you don't need
State current state plainly; surface a rollback path only for a real regression.
## Wilmington (wilm) local-run known failures - do not re-investigate
Running the full `python -m pytest tests/ -q` on a Windows dev box (not CI)
produces a stable set of environmental failures that are NOT code regressions.
Treat these as expected locally; judge your change against CI/Linux instead:
- `test_core/test_security_headers.py::test_a_served_upload_is_sandboxed` -
`PermissionError [WinError 32]` (served upload file still locked by another
process). Windows file-lock timing, not a headers bug.
- `test_plugin_contract.py::test_import_scan_covers_symlinked_plugins` -
`OSError [WinError 1314]` (symlink privilege not held). Needs admin/Developer
Mode to create symlinks; irrelevant to plugin logic.
- `test_plugin_importguard.py::test_verified_source_signed_ok_tampered_raises` -
CRLF vs LF (`b'# plugin\r\n' == b'# plugin\n'`). Git autocrlf checkout, not a
signing bug.
- `test_docs_publishable.py` and `test_docs_versions.py` - the local `docs/`
tree is not the stripped public wiki mirror, so doc-count / PROJECT-MAP
assertions fail. Expected off-CI.
- `test_core/test_public_read_surface.py::test_every_get_route_is_gated_or_named_public`
- pre-existing ungated-GET inventory drift; only act on it if YOUR change
added a new GET route.
If a run shows ONLY these, the suite is green for our purposes. Anything outside
this list is real - investigate it.