Guides image + draft gating, audit retention, ADR-015 gate, backup credential redaction
This commit is contained in:
98
CHANGELOG.md
98
CHANGELOG.md
@@ -12,6 +12,104 @@ ADR-007 and ADR-002.
|
||||
|
||||
### Added
|
||||
|
||||
- **Audit log retention.** The audit log is append-only and nothing ever removed
|
||||
a row, so it grew for as long as a site ran - here to the point where the
|
||||
audit page was tens of thousands of pages deep and therefore useless, because
|
||||
nobody pages through that to find anything. Site & Facility gains
|
||||
`audit_retentiondays` plus a card showing the log's size and exactly how many
|
||||
entries a purge would remove, and there is `flask audit stats` /
|
||||
`flask audit purge --apply` for the server. **Opt-in: the default is 0, which
|
||||
keeps everything.** An upgrade that silently started deleting a site's audit
|
||||
history would be indefensible. Deletion runs in committed batches, because one
|
||||
`DELETE` over a million rows holds table locks long enough to block the
|
||||
collectors posting inventory; a run that stops early reports how much is left
|
||||
rather than claiming it finished. Purging needs its own `audit.purge`
|
||||
permission - reading the log and destroying it are different acts.
|
||||
|
||||
- **Guides: images, and a reader who cannot see drafts.** Guide images are
|
||||
fetched through the authenticated API client instead of being pointed at by
|
||||
`<img src>`. An image tag sends no Authorization header and this app keeps its
|
||||
JWT in one, so every guide image was a broken icon for every user; the same
|
||||
change makes stored image links survive a subpath mount. A failed image now
|
||||
says so in words rather than showing a broken icon. Revision and image routes
|
||||
are gated on their parent guide - revision ids are sequential, so reading one
|
||||
guide used to be enough to walk the ids and read every unpublished draft on
|
||||
the site. Search, the guide list and the tag chips now match the PUBLISHED
|
||||
text, not the working copy, so a reader can no longer get a hit on wording
|
||||
that is not in the page when they open it.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **A guide's URL no longer moves when its title is edited.** The slug was
|
||||
regenerated on every save that carried a title, and the editor autosaves every
|
||||
three seconds - so fixing a typo in a heading silently moved the guide and
|
||||
broke exactly the pasted links the stored slug exists to protect. The slug is
|
||||
now set once, at create.
|
||||
|
||||
- **Publishing a guide after a failed save reported success.** `publish()` ran
|
||||
regardless of whether the pre-publish save reached the server, so the author
|
||||
saw "Published version N" while readers got the previous revision. A failed
|
||||
save now stops the publish and says why.
|
||||
|
||||
- **The ADR-015 site-literal check was enforcing nothing.** Its patterns are
|
||||
assembled from fragments so the published script does not contain the strings
|
||||
it greps for, but they used the apostrophe-escape form (`'a'\''b'`) instead of
|
||||
concatenation (`'a''b'`), embedding a literal apostrophe in every pattern.
|
||||
Nothing could ever match and the check passed everything from the day it was
|
||||
written. Fixed, and it immediately found 25 real violations - among them a QA
|
||||
ServiceNow tenant URL sitting in the shipped defaults in seven places.
|
||||
|
||||
- **ServiceNow ships off and blank.** The defaults pointed at one organisation's
|
||||
QA ServiceNow tenant with the integration enabled, so a site that had never
|
||||
configured ServiceNow still rendered ticket links - to a tenant its
|
||||
technicians cannot reach, and for an adopting site, into somebody else's
|
||||
instance. There is no cross-site default for "which ServiceNow is yours".
|
||||
|
||||
- **Credentials are no longer stored in backup revisions.** The NTLARS/DNC
|
||||
registry carries an FTP password in cleartext, and it was stored verbatim -
|
||||
rendered by the revision panel and handed back by the download route as a
|
||||
ready-to-import `.reg`. Credential values are now redacted at parse, so they
|
||||
never reach the database, and the rendered file NAMES each withheld value
|
||||
instead of carrying a placebo one: importing a fake password would produce a
|
||||
machine that looks restored and cannot connect. `scripts/redact_backup_secrets.py`
|
||||
scrubs revisions recorded before this (dry run by default). Rotate any
|
||||
password that was stored.
|
||||
|
||||
- **A search domain that fails is no longer indistinguishable from one that
|
||||
found nothing.** Each domain catches broadly and returns no rows so one broken
|
||||
table cannot take the whole search box down - but the technician was then told
|
||||
they might have misspelled a query that was right, and concluded the record
|
||||
did not exist. Failed domains are named in the response, shown as a banner,
|
||||
and suppress the did-you-mean.
|
||||
|
||||
- **An empty `sourcehostname` defeated backup dedup permanently.** The dedup
|
||||
query maps `''` to `IS NULL` while the insert stored the raw `''`, so those
|
||||
rows could never match their own chain: every post inserted, the table grew
|
||||
without bound and the history filled with identical entries. Normalized once
|
||||
at ingest, whitespace included.
|
||||
|
||||
- **`printedparts` migration 0003 was unguarded.** Unlike its twenty siblings it
|
||||
ran a bare `ALTER`, so `flask plugin upgrade-all` failed on any database built
|
||||
by `create_all` and took 0004 down with it. The migration test that should
|
||||
have caught this only covered single-migration chains; it now covers every
|
||||
post-cutover chain.
|
||||
|
||||
- **Guide links with several parameters opened the wrong page.** Text is escaped
|
||||
before formatting, so a captured URL arrives carrying entities and was then
|
||||
escaped a second time - `&amp;` in every multi-parameter ServiceNow and
|
||||
SharePoint link. Emphasis could also reach inside an `href`, and
|
||||
`//host` passed as an in-app link when the browser reads it as off-site.
|
||||
|
||||
- **Retire an asset from the interface.** Machines, PCs, printers and measuring
|
||||
tools had no way to take a record out of ShopDB without calling the API, so a
|
||||
scrapped tool stayed in every list and report. The endpoints already existed
|
||||
and were already soft deletes: the record leaves the lists while its
|
||||
calibration history, backups and relationships stay put, and it can be
|
||||
restored by setting `isactive` back. Called Retire rather than Delete for
|
||||
that reason, and the confirm says what is kept. USB is not included - it
|
||||
retires through a status column in the external CMMC database and needs its
|
||||
own decision.
|
||||
|
||||
- **`flask catalog dedupe`, `merge` and `rename`: catalog hygiene.** The legacy
|
||||
data carried space-stripped twins - `DellInc.` beside `Dell Inc.`,
|
||||
`Windows10` beside `Windows 10` - which rode into the shipped seed catalog
|
||||
|
||||
Reference in New Issue
Block a user