Licence dropped 60→50 on cairn-enc: one 403 self-revokes forever, silently — retry window, reason surfacing, metrics (pre-launch) #437
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Incident (2026-09, cairn-enc)
Nikola noticed the dogfood dropped from the 60-seat licence to the free tier; the licence page said "licence revoked — running on the free tier". He recovered it by re-pasting the key (2026-09-03 23:40 — the successful check-in right after his account-page login at 23:37).
Root cause (confirmed)
revoked=0, key valid to 2027-07-29 (the sub is yearly; the monthly one belongs to bao).unknownduring the licence server's account-era redeploys / JSON→SQLite migration (Aug 24–26; frequent restarts in the journal).Revoked=true→ cap drops to 50 immediately →checkinRequiredLockedreturns false → check-ins stop permanently. No self-heal is possible; only a manual Refresh/SetKey clears it. One transient server-side 403 = a silently bricked licence.Why it took days to notice, and could not be reconstructed
lastSeenis one overwritten value.Build plan (approved by Nikola; UI parts mockup-first)
Client (this repo):
revoked/unknownrefusal keeps the licence inactive (honest) but check-ins continue daily for a revoked-watch window (~14 days, mirroring the #308 expired-watch), so a transient server-side 403 self-heals at the next 200. A genuinely revoked licence gets two weeks of daily 403s, then goes quiet for good.CairnLicenseDropped(key installed ∧ not licensed) so the next drop pages within minutes.Server: cairn-license-server companion issue — log every check-in with id/outcome/reason, persist last-refusal per licence.
Airgapped deployments are unaffected by the whole class: no check-in URL, no 403, no self-revocation.
Shipped in v0.6.133 (PR #439, merged; dogfoods live).
Decision record:
revokedWatchWindow); the first 200 restores the licence with no admin action. Past the watch, check-ins stop for good — same bounded-phone-home shape as the expired watch (#308). Legacy state with the bareRevokedflag re-enters the watch on load.dropped/restored, reasonrevoked/grace/expired) — the forensic trail this incident lacked. The bell derives admin-only entries from it (one per state change, click lands on Licensing)./metrics:cairn_license_installed/cairn_license_licensed/cairn_license_seat_cap— absent when unwired, soabsent()keeps meaning. homelab-config now alertsCairnLicenseDropped(installed==1 and licensed==0, 5m, critical): the silent month this incident got is no longer possible.inactiveReason/inactiveSince/retryUntil/licSeats.TDD:
TestTransientRefusalSelfHealsreplayed the incident red on the runner (server hits stayed 1);TestForbiddenRevokedStillStops— which asserted the incident-causing behaviour — becameTestForbiddenRevokedEntersWatch. Full suite green.Live verify: cairn-enc + cairn-openbao on v0.6.133;
files.c0rdyceps.ch/metricsshows installed 1 / licensed 1 / seat_cap 60 (the 60-seat key counting again).Follow-ups tracked separately: cairn-license-server#40 (log every check-in, persist refusal history, 503-not-403 when storage is unavailable — the server-side half of never letting a redeploy answer "unknown"), #438 (Remove licence button, mockup first).