hub v0.67.0 — auto-minted self-bind link, post-RESET staleness, unprovisioned-offsite warning

Four small items, each a case where the hub already knew something and said
nothing. Green: build, vet, tests all pass.

(a) Self-bind link is minted automatically at customer creation AND at RESET
    completion (R-36 sub-item). The console banner tells the customer to open
    "az e-mailben kapott link"; until now that email existed only once the
    operator remembered the button, so the banner could point at something that
    did not exist — during the 2026-07-18 rehearsal the box waited ~11.7 min on
    exactly that. handleSelfBindLinkSend's body was extracted into a shared
    mintAndSendSelfBindLink core so the button and the auto-mint callers cannot
    drift apart on the honesty rules: F1 (no address -> mint nothing) and F2
    (send failed -> delete the token, never leave it live). The wrapper NEVER
    fails the operation it rides on — a create that provisioned Cloudflare,
    offsite and PBS must not 500 over a courtesy email.

    Gap found and closed while wiring it: PurgeCustomerResetDBState does NOT
    clear selfbind_tokens, so a link minted BEFORE a reset would have stayed
    live across it. A successful mint already replaces it (delete-then-insert,
    single-active); the skip paths would not have, so they now clear stale
    tokens too. Invariant: after auto-mint runs the only live link is one it
    just issued, or none.

(b) Post-RESET staleness banner (R-37). When a RESET COMPLETED after the newest
    report, every health figure on the page describes a lifecycle that no longer
    exists, and the page kept showing pre-RESET warnings as current. Narrow on
    purpose: an in-flight reset does not trigger it, and it clears itself when a
    report arrives. Ties resolve to STALE — SQLite timestamps are second-
    resolution and a same-second report almost certainly predates the reset;
    erring the other way would hide the banner exactly when it matters.

(c) Unprovisioned-offsite warning (R-36 interim). enabled==true with type=="" is
    a real, stable, silent state: provisioning is Save-triggered and the
    re-enroll auto-re-issue deliberately skips an unprovisioned target, so
    nothing self-heals it. Reuses the exact predicate the offsite re-issue
    handler already refuses on.

(d) pbsdr_reissued rendered an EMPTY flash box — the key had no template branch,
    so re-issuing PBS credentials showed a success box with no words (observed
    live 2026-07-18). Now describes what was staged plus the R-39 caveat:
    confirm `pvesm status` shows the entry active, because a converged agent can
    report `applied` while the storage still 401s.

New .flash-warn (amber, --warn tokens) for the deviation tier between success
and error — exception-color principle: only on deviation, never on a healthy
page.

Tests assert each banner is ABSENT in the nominal cases as well as present in
the deviating one — a banner that always renders is worse than none. Both
red-proofed: deleting the pbsdr_reissued branch reproduces the original empty
box; neutering the staleness predicate fails the banner assertion. New
read-only store accessor CountSelfBindTokens makes the single-active invariant
assertable.

NOT in this train: the R-39 hub-side generation-bump fix the pre-travel task
made conditional. Its condition was REFUTED (SetHostDesired bumps
unconditionally; applyPBSDR is idempotent as documented) — the real mechanism is
the agent's descriptor-hash convergence and needs its own spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
2026-07-18 21:45:11 +02:00
parent 28811c207b
commit b6d537d86c
9 changed files with 572 additions and 33 deletions
+62
View File
@@ -1,5 +1,67 @@
# Felhom Hub — Changelog
## v0.67.0 — the hub stops keeping things to itself: auto-minted self-bind link, post-RESET staleness, unprovisioned-offsite warning (2026-07-18)
Four small items, each one a case where the hub already knew something and said nothing. Green:
`go build ./... && go vet ./... && go test ./...` all pass.
- **(a) The self-bind link is minted automatically — at customer creation AND at RESET completion**
(R-36 sub-item). The box's console banner tells the customer to open „az e-mailben kapott link";
until now that email existed only once the operator remembered to press *Send self-bind link*, so
the banner could be instructing someone to look for something that did not exist. During the
2026-07-18 rehearsal the box sat in pairing mode for ~11.7 minutes waiting on exactly that.
`handleSelfBindLinkSend`'s body was extracted into a shared `mintAndSendSelfBindLink` core so the
button and the two auto-mint call sites **cannot drift apart on the honesty rules**: F1 (no
registered address → mint nothing, because a link nobody can receive is worse than none) and F2
(send failed → delete the token, never leave it silently live). The auto-mint wrapper **never
fails the operation it rides on** — a customer create that provisioned Cloudflare, offsite and PBS
must not 500 because a courtesy email bounced; every outcome is logged instead, and the operator
can still re-send from the Setup tab.
**Gap found and closed while wiring this:** `PurgeCustomerResetDBState` does **not** clear
`selfbind_tokens`, so a capability link minted *before* a RESET would have stayed live across it.
A successful mint already replaces it (minting is delete-then-insert, single-active), but the skip
paths would not have — so the wrapper now clears stale tokens on those paths too. The invariant is
now: after auto-mint runs, the only live link is one it just issued, or none.
- **(b) Post-RESET staleness banner** (R-37). When a RESET **completed** after the newest report,
every health figure on the customer page describes a lifecycle that no longer exists — and the
page went on showing pre-RESET warnings as if they were current. It now says so, quoting the
customer-facing phrasing („RESET óta nincs adat") and the reset's timestamp. Deliberately narrow:
an in-flight reset does **not** trigger it (only a completed one), and it clears itself the moment
a report arrives. Ties resolve to *stale* — SQLite timestamps are second-resolution, and a
same-second report almost certainly arrived just before the reset destroyed what it describes;
erring the other way would hide the banner exactly when it matters most.
- **(c) Unprovisioned-offsite warning** (R-36 interim). `offsite.enabled == true` with no
descriptor (`type == ""`) is a real, stable, silent state: provisioning is *Save*-triggered
(`applyOffsite`), and the re-enroll auto-re-issue deliberately skips an unprovisioned target, so
nothing self-heals it. The page now names the state and the fix (press Save once, then verify),
reusing the exact predicate the offsite re-issue handler already refuses on.
- **(d) `pbsdr_reissued` rendered an EMPTY flash box.** The flash key had no branch in the template,
so re-issuing PBS credentials showed the operator a success box containing nothing — observed live
on 2026-07-18. It now describes what was staged **and** carries the R-39 caveat: confirm
`pvesm status` shows the entry *active*, because a converged agent can report `applied` while the
storage still authenticates 401.
- **Styling:** new `.flash-warn` (amber, `--warn`/`--warn-dim` tokens) for the deviation tier between
success and error — per the exception-color principle it appears ONLY on deviation, never on a
healthy page.
- **Tests** (`customer_state_banners_test.go`, `selfbind_automint_test.go`) assert each banner is
**absent** in the nominal cases as well as present in the deviating one — a banner that renders
unconditionally is worse than none, because operators stop reading it. Auto-mint covers F1, F2,
the no-mailer case, and the pre-RESET-token invariant. **Both red-proofed:** deleting the
`pbsdr_reissued` branch reproduces the original empty-box bug, and neutering the staleness
predicate fails the banner assertion. New store accessor `CountSelfBindTokens` (read-only, keyed
by a customer id the operator already knows) makes the single-active invariant assertable.
**Not in this train:** the R-39 hub-side generation-bump fix the pre-travel task made conditional.
Its condition was **refuted**`SetHostDesired` bumps unconditionally and `applyPBSDR` is
idempotent as documented; the real mechanism is the agent's descriptor-hash convergence, which needs
its own spec. Nothing was improvised here.
## v0.66.0 — Customer self-bind (R-27 slice 1): tokenized capability link + public two-factor `/bind/` page (2026-07-17)
Lets a customer bind their OWN freshly-installed appliance without the operator. Until now every