carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Canary TTL — the pipelines spec's second guard rail (§5 "Canaries
2// expire"). Every canary/* channel carries a default 7-day lease, measured
3// from the pointer's own PromotedAt: the pointer already carries that
4// field, so this needs no new storage, and re-promoting a canary refreshes
5// the lease, which is the right semantic — an actively-updated canary never
6// expires, however old the branch it started from.
7//
8// Expiry is visibility, not enforcement, matching §5's "prod-wired" guard
9// rail right above it: nothing here stops a route serving, nothing deletes
10// a pointer, and promotion to or from an expired canary stays allowed. The
11// hazard §5 names is not a canary in production — it's one that outlives
12// the branch it was testing and quietly becomes permanent infrastructure
13// nobody remembers deploying. Every caller that needs to know computes it
14// fresh from the pointer already in hand; nothing persists an "expired" bit
15// anywhere.
16package release
17
18import (
19 "fmt"
20 "strings"
21 "time"
22)
23
24// CanaryTTL is a canary/* channel's default lease length before it is
25// flagged expired. Spec §5: "default 7 days".
26const CanaryTTL = 7 * 24 * time.Hour
27
28// CanaryExpired reports whether p is a canary/* channel pointer whose lease
29// (PromotedAt + CanaryTTL) has passed as of now. Always false for a channel
30// outside canary/* — edge, beta and stable carry no TTL — and false for a
31// nil pointer or a zero PromotedAt (an unpromoted or not-yet-read pointer):
32// unknown age is never treated as proof of age. Exactly CanaryTTL old is
33// NOT yet expired; the TTL names the lease's length.
34func CanaryExpired(p *ChannelPointer, now time.Time) bool {
35 if p == nil || !strings.HasPrefix(p.Channel, RungCanary+"/") {
36 return false
37 }
38 if p.PromotedAt.IsZero() {
39 return false
40 }
41 return now.Sub(p.PromotedAt) > CanaryTTL
42}
43
44// CanaryAge is how long PAST CanaryTTL an expired canary pointer is — the
45// number both operator-facing surfaces (the adopt pass's nag and the
46// console's badge) render, NOT the pointer's total age. Total age
47// (now.Sub(p.PromotedAt)) is always exactly CanaryAge + CanaryTTL, and
48// reporting IT as "how long has this been expired" overstates the lapse by
49// one full lease length — a canary that missed its lease by an hour would
50// read as a seven-day-old problem (2026-08-03 review, IMPORTANT 2).
51//
52// Meaningful only when CanaryExpired(p, now) is already true; call that
53// first. A caller that skips the check gets a negative or otherwise
54// nonsensical duration back for a fresh or non-canary pointer, the same as
55// any bare Sub on the wrong side of zero.
56func CanaryAge(p *ChannelPointer, now time.Time) time.Duration {
57 return now.Sub(p.PromotedAt) - CanaryTTL
58}
59
60// FormatCanaryAge renders a CanaryAge duration for a human. It is the ONE
61// formatter both the adopt pass's nag (internal/release/adopt.go) and the
62// console's badge (internal/console/ladder.go) call, so the two
63// operator-facing surfaces of this feature can never drift into disagreeing
64// about what a duration means (2026-08-03 review, IMPORTANT 2) — the way
65// they briefly did when the console rounded-and-bucketed a duration the nag
66// printed raw and un-subtracted.
67//
68// "<1h" for anything under a full hour past the TTL — never "0h", which
69// reads as "not actually expired" to whoever sees it first (2026-08-03
70// review, MINOR 7). "Nh" rounded to the nearest hour from one hour up to a
71// day. "Nd" TRUNCATED (not rounded) at a day and beyond: nobody needs
72// minute-level precision on a guard rail whose whole point is that nobody
73// was watching the clock, and losing up to 23 hours off the day count is
74// the deliberate coarseness that buys — nothing here mistakes a 47-hour
75// lapse for two days.
76func FormatCanaryAge(d time.Duration) string {
77 if d < time.Hour {
78 return "<1h"
79 }
80 d = d.Round(time.Hour)
81 if d < 24*time.Hour {
82 return fmt.Sprintf("%dh", int(d.Hours()))
83 }
84 return fmt.Sprintf("%dd", int(d.Hours())/24)
85}
86