From 61a1c0b4fbbdac78406c778dd252bac9e42d6470 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 3 Sep 2026 15:21:58 -0400 Subject: [PATCH] Answer API errors in the envelope, say when supplies are unread, badge each chain Three more findings from the 2026-09-03 run. Werkzeug answered 405, 415 and a malformed JSON body itself, in HTML. A client written against the documented {status,data,meta} contract got a page of markup with no error code, which is indistinguishable from the server having fallen over. The 405 had a second edge: on a route the caller cannot otherwise see, a method-not-allowed CONFIRMS the path exists, so a gated kiosk route meant to read as absent announced itself by refusing the wrong verb. All three now answer in the envelope, and only under /api/ - everything else is the SPA and keeps the framework's behaviour, deep links included. The Toner Report rendered "0 Printers Checked / 0 Low / 0 Critical" and "No printers match the selected filter" while the API it had just called said supply monitoring was unreachable. Zeros read as a clean bill of health for a fleet nobody has looked at. The page now carries the reason the API gave and suppresses the counts, because a number nobody measured is not a result. Same defect shape as the supplies card fixed on 2026-08-29; `unavailable` was already in the payload and the page ignored it. Backup revisions badged "current" per (asset, kind). NTLARS settings describe a MACHINE but are read off the controlling PC, so a machine with two reporting PCs has two independent chains of the same kind - and only the higher revision id was badged, so the second PC's newest revision showed as history and lost its last-verified stamp. The machine looked like one source re-reporting rather than two. sourcehostname is the chain; a revision without one forms its own, which keeps the old behaviour for everything that has only ever had one source. Nine tests. The envelope ones verified by planting: remove the three handlers and three fail. Scope: product --- plugins/backups/api/routes.py | 20 ++++-- .../printers/frontend/views/TonerReport.vue | 28 +++++++- shopdb/__init__.py | 42 ++++++++++++ tests/test_core/test_error_envelope.py | 66 +++++++++++++++++++ tests/test_plugins/test_backups.py | 43 ++++++++++++ 5 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 tests/test_core/test_error_envelope.py diff --git a/plugins/backups/api/routes.py b/plugins/backups/api/routes.py index 26953834..a45e6e87 100644 --- a/plugins/backups/api/routes.py +++ b/plugins/backups/api/routes.py @@ -116,13 +116,25 @@ def asset_revisions(assetid): revisions = query.order_by(BackupRevision.backuprevisionid.desc()).all() - # "Latest" is per kind, not per asset, so an unfiltered listing still marks - # the current revision of each kind correctly. + # "Latest" is per kind AND per source chain. Per kind alone was wrong on + # the case this exists for: NTLARS settings describe a machine but are read + # off the controlling PC, so a machine with two reporting PCs has two + # independent chains of the same kind. Marking only the highest revision id + # meant the second PC's current revision was badged as history and lost its + # "last verified" stamp - the machine looked like it had one source that + # kept re-reporting, rather than two. + # + # sourcehostname is the chain: it records which PC produced the revision. + # A revision with none (an older row, or a share-drop with no host) forms + # its own chain under the empty key, which keeps the per-kind behaviour for + # everything that has only ever had one source. seen = set() out = [] for revision in revisions: - islatest = revision.backupkind not in seen - seen.add(revision.backupkind) + chain = (revision.backupkind, + (revision.sourcehostname or '').strip().lower()) + islatest = chain not in seen + seen.add(chain) out.append(_summary(revision, islatest=islatest)) return success_response(out) diff --git a/plugins/printers/frontend/views/TonerReport.vue b/plugins/printers/frontend/views/TonerReport.vue index 22126aad..906b4466 100644 --- a/plugins/printers/frontend/views/TonerReport.vue +++ b/plugins/printers/frontend/views/TonerReport.vue @@ -17,8 +17,18 @@