hub v0.110.0: allowlist + operator-only for offsite_proof_empty (R-87)
gates / gates (push) Successful in 15s
gates / gates (push) Successful in 15s
Controller v0.231.0 adds a nightly job that proves an app's newest off-site snapshot still CONTAINS that app's data. When it finds one that does not, it emits offsite_proof_empty. Two register lines, both load-bearing and both in this commit: - allowedEventTypes - an unallowlisted type is answered 400 and VANISHES, so this entry is what makes the alarm exist at all. - operatorOnlyEvents - a missing customerMessages entry is NOT a routing block (FormatCustomerEmail falls back to the raw message, the v0.78.0 defect that register was built for). A customer can take no action on a hollow recovery unit. DELIBERATELY NOT reusing backup_integrity_failed, which is the nearest existing type: it means THE STORE IS DAMAGED and carries the Hungarian template saying so. Here the store is sound and the CONTENT is absent - different cause, different action, and telling a customer their backups are damaged when they are not is the more expensive mistake. Same asymmetry looksLikeRepositoryDamage is shaped around. DELIBERATELY no customerMessages entry (the controller's dynamic Hungarian names the app and what is missing; a template would discard it) and DELIBERATELY not in perAppCooldownEvents (a fenced act - this job proves ONE app per night, so the coarse hourly cooldown is already the right grain). This widens the R-87 task's stated repo scope to felhom.eu/hub/. The reason is recorded in felhom-controller/CONTEXT.md ruling 4 rather than left as an unexplained diff. Hub green gate: go build/vet/test all pass, 18 packages.
This commit is contained in:
@@ -1,3 +1,32 @@
|
||||
## v0.110.0 — `offsite_proof_empty`: a backup that is intact and holds nothing is not a damaged store (2026-08-31, R-87)
|
||||
|
||||
**Two lines of register, and the reason they are not one line of reuse.** Controller v0.231.0 adds a
|
||||
nightly job that proves an app's newest off-site snapshot still CONTAINS that app's data. When it
|
||||
finds one that does not, it needs to say so — and the nearest existing type, `backup_integrity_failed`,
|
||||
would say the wrong thing.
|
||||
|
||||
- **`allowedEventTypes` gains `offsite_proof_empty`.** An event type the hub does not allowlist is
|
||||
answered **400 and vanishes** (R-97a / the event-allowlist gotcha), so this entry is what makes the
|
||||
alarm exist at all.
|
||||
- **`operatorOnlyEvents` gains it in the SAME commit.** A missing `customerMessages` entry is NOT a
|
||||
routing block — `FormatCustomerEmail` falls back to the raw message, which is the v0.78.0 defect
|
||||
that register was built for. The customer can take no action on a hollow recovery unit: the causes
|
||||
are capture-side and diagnosed from the manifest and the capture legs, both operator surfaces. The
|
||||
customer's actionable half of this class is the fill warning, which fires first.
|
||||
- **Deliberately NO `customerMessages` entry.** The controller sends a dynamic Hungarian message
|
||||
naming the app and what is missing; a template would discard those specifics — the
|
||||
`offbox_enlarge_blocked` / `disk_health_degraded` precedent.
|
||||
- **Deliberately NOT in `perAppCooldownEvents`.** That is a fenced act (08 §6.2): the backup family's
|
||||
cooldown is coarse on purpose. This job proves ONE app per night, so the hourly operator cooldown is
|
||||
already the right grain and an entry would widen it with no cause.
|
||||
|
||||
**Why not reuse `backup_integrity_failed`:** it means *the store is damaged* and carries the Hungarian
|
||||
template that says so. Here the store is sound and the CONTENT is absent — a different cause, a
|
||||
different action, and telling a customer their backups are damaged when they are not is the more
|
||||
expensive mistake. That asymmetry is the same one `looksLikeRepositoryDamage` is shaped around.
|
||||
|
||||
---
|
||||
|
||||
## v0.109.0 — the Backup card told every operator that every customer had no backups (2026-08-30, R-331)
|
||||
|
||||
> **This push used `git push --no-verify`, and that is declared here rather than worked around.**
|
||||
|
||||
@@ -2005,6 +2005,20 @@ var allowedEventTypes = map[string]bool{
|
||||
"db_dump_failed": true,
|
||||
"backup_integrity_ok": true,
|
||||
"backup_integrity_failed": true,
|
||||
// controller v0.231.0 (R-87) — the nightly off-site PROOF found a backup that is READABLE and
|
||||
// contains none of the app's data. DELIBERATELY NOT `backup_integrity_failed`, which is the
|
||||
// nearest existing type and would be the wrong sentence: that one means the STORE IS DAMAGED and
|
||||
// carries a Hungarian template saying the integrity check found an error. Here the store is sound
|
||||
// and the CONTENT is missing — a different fact, a different cause and a different action.
|
||||
// Telling a customer their backups are damaged when they are not is the more expensive mistake,
|
||||
// and it is the one R-359's own `looksLikeRepositoryDamage` is shaped to avoid.
|
||||
//
|
||||
// NO `customerMessages` entry, deliberately: the controller sends a dynamic Hungarian message
|
||||
// naming the app and what is missing, and a template would DISCARD those specifics
|
||||
// (templates.go priority) — the `offbox_enlarge_blocked` / `disk_health_degraded` precedent.
|
||||
// Operator routing is enforced by `notify.operatorOnlyEvents`, in this same commit; this entry
|
||||
// alone does NOT make it operator-only (the v0.78.0 defect that register records).
|
||||
"offsite_proof_empty": true,
|
||||
"crossdrive_completed": true,
|
||||
"crossdrive_failed": true,
|
||||
// controller v0.134.1 — enlarged offsite push refused by the quota gate (warning; the controller's
|
||||
|
||||
@@ -517,6 +517,16 @@ var operatorOnlyEvents = map[string]bool{
|
||||
// block — `FormatCustomerEmail` falls back to the raw English message. That mistake shipped
|
||||
// once (v0.78.0) and the comment above records it.
|
||||
"backup_run_failures": true,
|
||||
// R-87 (controller v0.231.0). The nightly off-site proof found a backup that is intact and holds
|
||||
// none of the app's data. Operator-only for the same reason as `recovery_unit_capture_failed`
|
||||
// above: the customer can take no action on it — a hollow recovery unit is a product/ops fault,
|
||||
// diagnosed from the capture legs and the manifest, both of which are operator surfaces. The
|
||||
// customer's actionable half of this class is the fill warning, which fires first.
|
||||
//
|
||||
// LISTED IN THE SAME COMMIT THAT MINTS THE TYPE, because a missing customerMessages entry is NOT
|
||||
// a block — FormatCustomerEmail falls back to the raw message, which is the v0.78.0 defect this
|
||||
// register was built for.
|
||||
"offsite_proof_empty": true,
|
||||
// R-197 (v0.93.0). "The sealed offsite repository key changed" is a custody fact about escrow
|
||||
// blobs. A customer can take no action on it — the remedy is the operator's inspection of the
|
||||
// off-site tier — and the text is operator-grade English naming host ids and retained-blob
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: hub
|
||||
image: gitea.dooplex.hu/admin/felhom-hub:0.109.0
|
||||
image: gitea.dooplex.hu/admin/felhom-hub:0.110.0
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
|
||||
@@ -171,6 +171,43 @@ ALLOWLIST = {
|
||||
"that card. The sibling `offsite.last_integrity_check` is NOT allowlisted and passes on its "
|
||||
"own — the string already occurs hub-side. **When a hub surface is built, delete this entry.**"),
|
||||
|
||||
# ---- R-87, controller v0.231.0: the nightly off-site PROOF's verdict ----
|
||||
#
|
||||
# Five fields, ONE reason, and it is the SAME reason as the two integrity siblings below: the
|
||||
# verdict is published so a hub surface can read it, and no hub surface reads it yet. R-331 ruled
|
||||
# that class a decision for the operator rather than something to fold into a controller task —
|
||||
# the previous integrity card was REMOVED because it rendered a verdict for every customer
|
||||
# forever from fields nothing wrote. Publishing first and consuming when someone decides what the
|
||||
# screen should say is the opposite order to the one that produced that card.
|
||||
#
|
||||
# They are listed INDIVIDUALLY rather than as a prefix, so deleting them one at a time as a
|
||||
# surface starts reading them is a reviewable diff. **Delete each entry when its field is read.**
|
||||
# Filed with the integrity pair under R-402.
|
||||
(_CH, "offsite.last_proof_result"): (
|
||||
"R-87, controller v0.231.0: the nightly off-site content-proof VERDICT (pass / fail / "
|
||||
"cannot_judge). Published for a hub surface that does not exist yet — same class and same "
|
||||
"ruling as offsite.last_integrity_ok below (R-331, R-402). ABSENT means NOT RECORDED (a "
|
||||
"controller older than v0.231.0), never 'failed', so any future reader must degrade to "
|
||||
"unknown. **Delete this entry when a hub surface reads it.**"),
|
||||
(_CH, "offsite.last_proof_reason"): (
|
||||
"R-87: the machine reason code behind the verdict above (e.g. database_expected_none_captured). "
|
||||
"Unconsumed for the identical reason. It matters because 'fail' alone cannot distinguish a "
|
||||
"backup that is readable-and-empty from one whose manifest could not be read, and those are "
|
||||
"different operator actions. **Delete with its siblings.**"),
|
||||
(_CH, "offsite.last_proof_stack"): (
|
||||
"R-87: WHICH app the newest verdict is about. One app is proved per night, so a verdict "
|
||||
"without its app names nothing. Unconsumed for the same reason. **Delete with its siblings.**"),
|
||||
(_CH, "offsite.last_proof_snapshot"): (
|
||||
"R-87: WHICH SNAPSHOT was proved — the per-archive due-ness model (R-86, 07 §3) records the "
|
||||
"snapshot and never a timestamp, and a hub surface showing the verdict without it cannot say "
|
||||
"whether the newest backup was the one proved. Unconsumed for the same reason. **Delete with "
|
||||
"its siblings.**"),
|
||||
(_CH, "offsite.last_proof_run"): (
|
||||
"R-87: when that verdict was reached. Unconsumed for the same reason. NOTE for whoever builds "
|
||||
"the surface: this is an attempt-free stamp — it is written ONLY when a verdict was actually "
|
||||
"reached, never on a skip, a missing snapshot or a restore error, so 'presence is not success' "
|
||||
"does not bite here. **Delete with its siblings.**"),
|
||||
|
||||
(_CH, "offsite.last_integrity_depth"): (
|
||||
"R-399, controller v0.228.0: the DEPTH the verdict was reached at, published beside "
|
||||
"last_integrity_ok above and unconsumed for the identical reason. It matters because 'checked, "
|
||||
|
||||
Reference in New Issue
Block a user