main was 16 days and 259 commits behind the line the product is actually built on, at contract 0.30.0 against 0.35.0, with no per-PC credential code at all. It was neither current nor curated - just stale. This brings it forward and, at the same time, makes it mean something: main is what a receiving site installs, and nothing else. WHAT IT CARRIES. Twelve plugins - backups, computers, endpoints, guides, knowledgebase, machines, network, notifications, printers, slides, tools, warranty - plus applications, which is manifest-less core and always ships. The only manifest dependency inside that set is endpoints on computers, and both are in it, so the closure is self-contained. WHAT IT DOES NOT. employees, geenforce, measuringtools, printedparts and usb stay on the site line. So do the Windows installer tree, the VM rehearsal helpers, the agent tooling config, one site's data loader, the internal audit and findings records, and the planner spreadsheet. 1,135 tracked files against 1,438. A REMOVAL THAT BREAKS THE APP IS NOT A REMOVAL, and two nearly shipped: deploy/site-profile-universal.json still named all seventeen plugins. It is the file docs/DEPLOY.md tells a site to provision from, so a fresh install would have failed at `flask plugin apply-profile` with PluginError - the first thing a receiving site does, broken by the branch meant to serve them. It now names the twelve that exist, and its comment says the two must agree. agent/ was never refreshed. Whoever populated this branch copied shopdb/, plugins/, frontend/ and tests/ from the site line and left the agent tree at its September state: 22 C# and PowerShell sources missing, including AgentCredentials, EnrollmentService, AgentHttpFactory and the fleet-alert client, plus one file the site line had deleted still present. The old tree is internally consistent, so it compiles, and nothing in CI builds it - every gate was green over a server that speaks enrollment and per-PC credentials to a bundled agent source that cannot. Resynced whole. DECISIONS THIS ENCODES, rather than defers: - The plugin-authoring lab builds printedparts, whose manifest depends on employees, so it could not stay. The lab remains on the site line and main ships no build-along tutorial. That is a real loss for an adopting developer and is recorded here rather than discovered later. - Recognition, Training and Recertification notifications are no longer seeded. Each splits one notification into a card per employee and renders a photo, which needs a plugin this branch does not bundle. Seeding them would offer an author a type that cannot resolve anybody. A site that installs employees gets them from that plugin's own seed. - Windows/IIS is documented as the legacy path throughout, and the README leads with Docker, because that is what a receiving site gets. The API description is regenerated for THIS branch - 347 paths and 483 operations, down from 415 and 573 - so the published contract describes what is here rather than what the site line serves. docs/CONFIG.md lost the two measuring-tool label keys the code no longer declares, which two lanes had decided opposite ways. Suite: 3124 passed, 6 skipped. Naming and style gates pass. The docs gates run rather than skip, and every index line count is derived from the file it names.
105 lines
5.1 KiB
Markdown
105 lines
5.1 KiB
Markdown
<!-- docmeta
|
|
purpose: explanation
|
|
audience: developer, operator
|
|
status: current
|
|
summary: Why the Backups-stopped card lists a chain, why it stays quiet about one, and why staleness is measured from lastseenat.
|
|
loadwith:
|
|
-->
|
|
|
|
# How the backups card decides good vs stale
|
|
|
|
The `backups` plugin's health verdict, as shown on the dashboard's **Backups
|
|
stopped** card. Read it when the card looks wrong, or before changing the rule.
|
|
Adding a new kind needs none of this - see [BACKUP-KINDS.md](BACKUP-KINDS.md).
|
|
|
|
## The trap this is built around
|
|
|
|
**A revision is only written when the config CHANGES.** Dedup compares against
|
|
the latest revision for the chain `(asset, kind, sourcehostname)`, so a machine
|
|
whose config has been stable for six months has a six-month-old newest revision
|
|
and is perfectly healthy. The question worth answering is not "when was the last
|
|
backup taken" - it is "is this still being checked".
|
|
|
|
So the timestamp the card uses is `backuprevisions.lastseenat`: the last time
|
|
the collector CONFIRMED this config, whether or not anything changed. It moves
|
|
on every successful collection; the revision does not.
|
|
|
|
It is a card of exceptions rather than a column of raw dates because a raw date
|
|
read as neglect. At the default `backups_intervalhours` of **24** the collector
|
|
only attempts once a day, so a day-old confirmation IS the healthy steady state -
|
|
and the date made the reader do arithmetic against a setting they would have to
|
|
go and look up.
|
|
|
|
## What it does now
|
|
|
|
`GET /api/backups/dashboard/stale` returns one row per chain that has gone
|
|
quiet, worst first:
|
|
|
|
| Field | Meaning |
|
|
|-------|---------|
|
|
| `assetid`, `assetnumber` | The asset the backup belongs to. |
|
|
| `backupkind` | Which kind stopped. Chains fail independently, so the kind is part of the identity, not a summary. |
|
|
| `sourcehostname` | The PC that was doing the collecting. |
|
|
| `lastseenat` | When this chain was last CONFIRMED still current (ISO). |
|
|
| `quietdays` | How long it has been quiet, which is what the card sorts on. |
|
|
|
|
`lastseenat` is reported rather than the capture time because they answer
|
|
different questions and one cannot stand for both: the first proves the check is
|
|
running, the second only says what is stored. Collapsing them was what made a
|
|
healthy machine look neglected.
|
|
|
|
## The chain is the unit, not the host
|
|
|
|
A marker bay runs both an NTLARS backup and a part-marker backup. Reporting one
|
|
verdict per host would let a healthy kind mask a dead one: the part-marker
|
|
capture could stop for a month and the host would look fine on the strength of
|
|
its NTLARS row. So the card is keyed on the CHAIN - asset, kind and source PC
|
|
together - because that is the unit that fails independently.
|
|
|
|
## Silence is not a verdict
|
|
|
|
A chain whose newest revision has never been confirmed - `lastseenat` NULL,
|
|
which means a backfilled row or one written before the column existed - is not
|
|
stale, it is UNMEASURABLE, and it is skipped rather than listed. Substituting
|
|
`collectedat` for it looked right and was wrong: on a real fleet it reported
|
|
every machine whose config had simply been stable for months as a stopped
|
|
backup, so the whole board lit up and said the site was broken when it was fine.
|
|
A chain becomes measurable the first time its PC posts, and the card says
|
|
nothing about it until then.
|
|
|
|
The card is equally silent about an asset that has NEVER been backed up. Whether
|
|
one SHOULD be is a question the asset record cannot answer - most machines carry
|
|
no NTLARS at all - and guessing would list a hundred healthy assets, which is
|
|
the noise that makes a board worth ignoring.
|
|
|
|
Known consequence: a device retired from a PC leaves its last revision behind,
|
|
so that chain stays listed until the revision is pruned. That is the honest
|
|
failure direction - it complains about a backup that genuinely is not running
|
|
any more, rather than going quiet about one.
|
|
|
|
The threshold is the backups plugin's own `backups_staledays` setting (default
|
|
**3**), read through `plugins.backups.services.staleness.staledays()` rather
|
|
than re-derived, so there is ONE definition of stale. `0` disables the card.
|
|
|
|
## Why time-based rather than per-kind
|
|
|
|
Every kind answers the same question the same way: something confirmed this
|
|
recently, or it did not. Making the rule per-kind would mean each new kind has
|
|
to define health before it can appear, for no gain. If a kind ever needs its own
|
|
window - a weekly backup that should not be judged on a 3-day rule - add a
|
|
threshold override on `BackupKind` and have the rule prefer it; the
|
|
skip-the-unmeasurable contract stays as it is.
|
|
|
|
## Where the code lives
|
|
|
|
- `plugins/backups/services/staleness.py` - `staledays()` (the shared
|
|
threshold), `latestperchain()` (newest revision per chain), `stalechains()`
|
|
(the verdict)
|
|
- `plugins/backups/api/routes.py` - `dashboard_stale`, a thin route over that
|
|
rule so it stays testable without an auth layer in the way
|
|
- `plugins/backups/plugin.py` - the `backups-stale` dashboard widget
|
|
declaration, including the `backups_staledays` setting
|
|
|
|
Adding a kind is [BACKUP-KINDS.md](BACKUP-KINDS.md); backing up the ShopDB
|
|
database itself is [BACKUP-RESTORE.md](BACKUP-RESTORE.md), a different subject.
|