Security hardening, multi-site portability, and a documentation and API-surface sweep
This commit is contained in:
126
CHANGELOG.md
126
CHANGELOG.md
@@ -66,6 +66,132 @@ ADR-007 and ADR-002.
|
||||
components nobody wrote. Cards are ordered by severity, hide themselves when
|
||||
there is nothing to report, gate on a permission, and each fetches
|
||||
independently so one broken endpoint cannot blank the board.
|
||||
- Tech Tools: a section for the small conversions and code generation that
|
||||
otherwise happen in a browser tab on somebody's phone. Starts with barcode and
|
||||
QR codes laid out for the label stock actually in the printers.
|
||||
- Kiosks report what they are. The dispatcher already read
|
||||
`C:\Enrollment\display-type.txt` to pick its target; now it tells ShopDB, so
|
||||
the Asset column on an enforcement report shows a reported value instead of
|
||||
one inferred from a mapping table nobody filled in.
|
||||
- Enforcement reports carry what the PC is, where it is, and whether it is
|
||||
backed up: the host links to its own page, the Asset column names the machine
|
||||
or instrument the PC controls, a map pin shows the location on hover, and the
|
||||
backup column judges the chain rather than printing its last date.
|
||||
- `flask relationships audit`, read-only, listing links that cannot both be
|
||||
true, plus an inverse guard that refuses to create one.
|
||||
- An employees endpoint that says WHY a name did not resolve, rather than
|
||||
returning nothing and leaving the caller to guess.
|
||||
- The display client module updates itself through the manifest, so a client
|
||||
change no longer means hands on every kiosk.
|
||||
- `docs/START-HERE.md` routes a reader by what they came to do, and is now the
|
||||
wiki's landing page - which previously recommended the one install document
|
||||
the docs themselves forbid for a new site. `docs/FLEET-ARCHITECTURE.md` is the
|
||||
page nothing else could assume: how the server, GE-Enforce, the asset
|
||||
reporter, the backup collectors and EventSaver relate, where credentials live
|
||||
at both ends, and which of the five to open for a given symptom.
|
||||
- `docs/ADOPTING-AT-ANOTHER-SITE.md`: how another site repoints the asset
|
||||
reporter and the EventSaver screensaver at its own ShopDB, with worked
|
||||
examples for Intune (including Machine Configuration/DSC), a GE-Enforce
|
||||
manifest entry, and manual installation. Both tools were already built to be
|
||||
repointed - the URL, the key and the targeting are inputs - but nothing said
|
||||
so, and the two things that catch people out are written down: EventSaver's
|
||||
compiled-in fallback path is not neutral, and a config enforced by hash
|
||||
reverts a hand edit by design.
|
||||
- `docs/PROJECT-MAP.md`, generated by `scripts/gen_project_map.py`: versions,
|
||||
the plugin inventory, every Alembic chain head, the ADR index and the size of
|
||||
the codebase, derived from the code. The hand-written equivalents had drifted
|
||||
by two contract releases and 400 tests.
|
||||
|
||||
### Changed
|
||||
|
||||
- Plugin contract **0.19.0**: `get_dashboard_widgets` returns data and shape,
|
||||
not a component name. The old shape named a Vue component per widget, which
|
||||
cannot survive a lean build (ADR-013) and had five plugins pointing at
|
||||
components nobody wrote. The change itself shipped earlier without a bump and
|
||||
with `BasePlugin` and PLUGIN-HOOKS.md still teaching the dead shape; this
|
||||
records it and corrects both.
|
||||
- Plugin contract **0.17.0**: `DashboardDefault` is part of the `shopdb.api`
|
||||
surface, so a plugin can resolve a display without reaching into core.
|
||||
- Plugin contract **0.18.0**: one name per display role, and it is the kiosk's
|
||||
own (`Dashboard`, `Lobby`, `3DPrintRoom`). Server and dispatcher had three
|
||||
vocabularies for the same three kiosks, and a display could report a role core
|
||||
could not store. `normalize_display_role` resolves case and the retired
|
||||
spelling; an unknown value is kept verbatim so a typo stays visible.
|
||||
Contract versions are their own series (ADR-002) and belong in this file;
|
||||
0.17.0 and 0.18.0 shipped without an entry, which is what this note corrects.
|
||||
- Publishing a GE-Enforce manifest is refused when PCs reporting for that scope
|
||||
run an older client library than the manifest needs, naming the hosts. A minor
|
||||
version bump that NARROWS behaviour is not backward compatible: an old library
|
||||
reads a gated entry as ungated and installs every version it cannot detect.
|
||||
- Shadow mode runs inside the enforce cycle instead of on its own schedule. The
|
||||
share is mounted only for the length of a cycle, so a task on its own clock
|
||||
woke to a missing drive and reported 0 installed / 0 skipped / 0 failed, which
|
||||
is indistinguishable from a healthy no-op.
|
||||
- A GE-Enforce report is judged stale on both clocks, the server's and the
|
||||
client's asserted check-in, so a PC cannot mask real silence with a future
|
||||
timestamp. A PC that has gone quiet stops reading as healthy.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Documentation a reader could not follow. 98 curl examples single-quoted
|
||||
`$TOKEN`, so the shell never expanded it and the server answered 422; a JSON
|
||||
body carried a `//` comment, which made the server report a missing hostname
|
||||
about a body that has one; the IIS install omitted `flask plugin upgrade-all`,
|
||||
which is the 1054 error a deploy hits days later; a pilot loop exited 1 on
|
||||
every iteration; and the external-plugin guide pinned a contract version six
|
||||
releases stale, so a plugin built by following it is refused at startup. Nine
|
||||
documents now point at the generated project map rather than typing a version,
|
||||
and a test enforces that. The API inventory gained the 22 routes that were
|
||||
serving traffic undocumented, with a parity test against the live URL map so
|
||||
the next one cannot slip in silently.
|
||||
|
||||
- **Every plugin migration chain re-ran its head on each MySQL deploy.** Core's
|
||||
Alembic env got a `connection.commit()`; the per-plugin template did not, so
|
||||
the last migration of every run lost its version stamp while its DDL survived.
|
||||
Invisible for as long as every head happened to be idempotent - and two were
|
||||
not. The backups head cleared a column for every row, and the geenforce head
|
||||
added a column unconditionally and failed on the second attempt. Both are now
|
||||
bounded and guarded, and the template commits.
|
||||
- A part-marker PC reported 200 on the cycle that created its marker and 500 on
|
||||
every cycle after. Both of the links it makes were looked up by label, so a row
|
||||
made by hand or by the legacy import was invisible and the insert violated the
|
||||
uniqueness of (source, target, type); and the marker's asset number is derived
|
||||
from the PC, so it could already be taken.
|
||||
- A CMM reported the same way: the instrument IS the reported bay, so adoption
|
||||
was relabelling the row the machine sync owns and finding it by that label.
|
||||
- A reported identifier is matched exactly rather than with `ilike`, so `_` and
|
||||
`%` in a file on a shopfloor PC are no longer wildcards. `MT-600_` had been
|
||||
adopting MT-6001, and a bare `%` adopted whatever active asset came first.
|
||||
- Measuring tools are adopted before they are minted. Minting derived the asset
|
||||
number from the HOSTNAME, so a permanent instrument took the identity of
|
||||
whichever PC drove it that week, and the collector could not see a tool it had
|
||||
not created - which left legacy instruments shadowed by minted twins.
|
||||
- An unanchored rsync exclude in the export was matching at any depth, so a
|
||||
whole plugin was silently dropped from the published payload. Root excludes
|
||||
are anchored and the export now aborts if a plugin manifest fails to survive.
|
||||
- Backup revisions are ordered in Python, not with `ORDER BY ... NULLS LAST`,
|
||||
which SQLite accepts and MySQL rejects.
|
||||
|
||||
### Security
|
||||
|
||||
- The Windows installer breaks ACL inheritance on the roots it creates. A
|
||||
directory made directly under `C:\` inherits an inherit-only Modify grant for
|
||||
Authenticated Users, so `C:\shopdb-flask`, `C:\Python314` and `C:\MySQL84`
|
||||
were writable by any logged-on user. Two files inside them make that a local
|
||||
privilege escalation: `shopdb-admin.ps1`, which self-elevates, and
|
||||
`instance/config.py`, which the app executes unconditionally. The MySQL data
|
||||
directory is locked down as well, since it holds the password hashes. A Python
|
||||
installation this installer did not create is reported rather than seized.
|
||||
- Site-specific values no longer ship in product code (ADR-015). The backups
|
||||
share root defaults to blank instead of one site's file server, the shadow
|
||||
client no longer overwrites a site's own ShopDB URL in the registry on every
|
||||
cycle, the kiosk dispatcher takes its URL from the `site_base_url` setting and
|
||||
refuses rather than guessing, and one site's hostname, FQDN and internal
|
||||
subnets are out of the documentation, the generated API specs and the UI
|
||||
placeholders. The ADR-015 scanner now covers PowerShell, the installer, the
|
||||
seeds and generated JSON, case-insensitively, and FAILS the build; deliberate
|
||||
organisation-wide defaults are declared with an `ADR-015-OK` marker. Both
|
||||
publication gates carry the site patterns.
|
||||
|
||||
## [0.9.0] - 2026-08-11
|
||||
|
||||
|
||||
Reference in New Issue
Block a user