oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1package memoria
2
3import (
4 "fmt"
5 "net/http"
6 "time"
7)
8
9// sourceRow is one sibling as the owner's sources page shows it.
10//
11// **Sentences, not codes.** The page is read by a person deciding whether
12// something is wrong in the suite, and a verdict they have to look up is a
13// verdict they will skip. Every string here is built in Go so there is one
14// wording per state and the template cannot invent a second.
15type sourceRow struct {
16 App string
17 // Current is what the corpus is current as of, or the sentence that
18 // says it is not known. §8.1: a failed or stale S1 states what the
19 // corpus is current as of rather than implying freshness.
20 Current string
21 // CurrentAlarming and BreakerAlarming are **separate on purpose**, and
22 // the first version had one flag for both.
23 //
24 // Looking at the rendered page is what caught it: a sibling that had
25 // simply not been polled yet set the flag, and the breaker sentence —
26 // "No refusal anomaly." — was then rendered inside a warning callout.
27 // A warning box with reassurance in it is the notice a reader learns
28 // to skip, which is the failure §8.1 spends a paragraph avoiding,
29 // arriving through the markup rather than the predicate.
30 //
31 // Two lines, two states, toned independently. A template test would
32 // have proved both strings were present and told me nothing.
33 CurrentAlarming bool
34 // Breaker is the refusal breaker's sentence.
35 Breaker string
36 BreakerAlarming bool
37}
38
39// siblingRows builds the sibling section of the owner's sources page.
40//
41// **Owner-only, and that placement is a rule rather than a convenience.**
42// §8.1: the refusal notice goes here and never on a reader's results page,
43// because a message telling one reader how much they were not allowed to
44// see is a disclosure with a number in it — and §4.1 is explicit that
45// NOT_VISIBLE is the one value that may be silent. The reader's page says
46// what could not be shown and from which app; only this one says how many
47// people a sibling stopped answering for.
48//
49// It lives on the page already titled "Sources" — the connector page —
50// rather than on a second one, because that is the page an owner opens to
51// ask where memoria's content comes from, and a suite app is one of the
52// answers.
53func (a *app) siblingRows(r *http.Request) []sourceRow {
54 roster, ok := a.store.custody.(*Siblings)
55 if !ok || roster == nil || len(roster.byName) == 0 {
56 return nil
57 }
58 var out []sourceRow
59 now := time.Now().UTC()
60 for _, app := range roster.ordered() {
61 row := sourceRow{App: app.Name}
62
63 at, err := a.store.polledAt(r.Context(), app.Name)
64 switch {
65 case err != nil:
66 // **Not "never polled".** A read that failed says so; a count
67 // that fails is not a count of zero, and the same rule governs
68 // a timestamp that cannot be read.
69 row.Current = "Could not read when this source was last checked."
70 row.CurrentAlarming = true
71 case at.IsZero():
72 // **Not alarming, and that is the second thing looking at the
73 // page taught.** memoria hibernates, so "not polled yet" is
74 // the ordinary state of every wake's first seconds — the
75 // warm-up is running while this renders. A warning on every
76 // row of every freshly-woken instance is a notice owners
77 // learn to skip, which is §8.1's argument about a breaker
78 // firing for ordinary people, arriving in the freshness line
79 // instead.
80 //
81 // A warm-up that keeps failing is not invisible for it: the
82 // breaker below says cannot-determine, and every search from
83 // that sibling carries its own withholding notice.
84 row.Current = "Not checked yet since this instance woke."
85 default:
86 row.Current = "Current as of " + at.UTC().Format("2 January 2006, 15:04 UTC") + "."
87 }
88
89 b, err := a.store.RefusalBreaker(r.Context(), app.Name, now)
90 if err != nil {
91 row.Breaker = "Could not read this source's refusal history."
92 row.BreakerAlarming = true
93 } else {
94 row.Breaker, row.BreakerAlarming = breakerSentence(b)
95 }
96 out = append(out, row)
97 }
98 return out
99}
100
101// breakerSentence turns a verdict into the sentence the owner reads.
102//
103// The cannot-determine case **names which of the window and the baseline
104// is missing**, because §10 gate 8 requires it and asserts the string: an
105// owner who is told only "cannot determine" cannot tell a fresh instance
106// from a broken one.
107// It takes no incoming flag. The first version threaded one through, so
108// a quiet breaker inherited the alarm from an unrelated line and rendered
109// "No refusal anomaly." in a warning box.
110func breakerSentence(b Breaker) (string, bool) {
111 switch b.Verdict {
112 case BreakerFiring:
113 return fmt.Sprintf(
114 "Refusing people it used to allow: %d people who could read from "+
115 "%s last week were refused everything today. Nothing has been "+
116 "deleted.", b.Askers, b.App), true
117 case BreakerQuiet:
118 return "No refusal anomaly.", false
119 default:
120 return "Cannot determine whether this source is refusing people: " +
121 joinWords(b.Lacking) + " missing. This is not the same as no anomaly.", true
122 }
123}
124
125// joinWords renders a short list as a phrase, so the sentence reads.
126func joinWords(in []string) string {
127 switch len(in) {
128 case 0:
129 return "the counters are"
130 case 1:
131 return in[0] + " is"
132 default:
133 out := ""
134 for i, w := range in {
135 switch {
136 case i == 0:
137 out = w
138 case i == len(in)-1:
139 out += " and " + w
140 default:
141 out += ", " + w
142 }
143 }
144 return out + " are"
145 }
146}
147