Generate the facts that go stale, and record the fortnight in the changelog
CLAUDE.md is read at the start of every session and claimed contract 0.16.0 and 1159 tests while the code was at 0.18.0 and 1567, along with a plugin count and a chain head that were both wrong. Hand-written facts about a moving codebase are wrong within a fortnight, and a reader who cannot tell which lines are stale has to re-derive all of them. So they are derived. scripts/gen_project_map.py reads the versions, the plugin inventory, every Alembic chain head, the ADR index with status and the size of the codebase out of the code, and --check fails a build where the committed map no longer matches. CLAUDE.md now points at it, leads with the multi-site rule, and lists the gates to run instead of restating the conventions they enforce. The changelog's Unreleased section covered 8 of the 46 commits since 0.9.0 and had no Changed, Fixed or Security sections at all. It now carries the whole fortnight, including both contract bumps - which had never been recorded even though ADR-002 makes contract versions their own series.
This commit is contained in:
100
CHANGELOG.md
100
CHANGELOG.md
@@ -66,6 +66,106 @@ 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/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 in CLAUDE.md
|
||||
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
|
||||
|
||||
- **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