oficina / memoria Public

๐Ÿค– fix: two bugs on the sources page that only looking at it could find

The section was built, the tests were green, and the page was wrong in
two ways โ€” both invisible to a template test, because neither changes a
string anything was checking for.

**A warning-toned callout containing the words "No refusal anomaly".**
One `Alarming` flag carried two unrelated lines, so a sibling that had
merely not been polled yet dragged the breaker's verdict into the alarm
tone with it. A warning box with reassurance in it is the notice a reader
learns to skip โ€” ยง8.1's whole argument, arriving through the markup
rather than the predicate. Two lines, two states, toned independently,
and `breakerSentence` no longer takes an incoming flag to inherit.

**A warning on every row of every freshly-woken instance.** memoria
hibernates, so "not polled yet" is the ordinary state of a wake's first
seconds while the warm-up is still running. Firing for the routine case
is the same failure again. It is plain text now; a warm-up that keeps
failing is not thereby invisible, because the breaker beneath it says
cannot-determine and every search from that sibling carries its own
notice.

**`tools/drive` now seeds three siblings**, in the three states the
breaker has. It had none, so `/connectors` rendered the connector list
and nothing else โ€” the section was shipped without anybody seeing it,
which is exactly the gap drive exists to close. Three rather than one,
because what a person must tell apart at a glance is the difference
between them: a number, a longer sentence that must not read as
reassurance, and a short line that must not shout.

`SeedTallyForDrive` writes counters into the past and is named so it
cannot be reached by accident, under the same `cmd/` gate as
`AppForTest`: the breaker's entire claim is that its counters record what
happened when it happened.

AGENTS.md gains both lessons, and the rule that follows from them โ€” if
you add a section to a page, add it to drive, because the section that is
not seeded is the section nobody sees.

`make ci` green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3VKtXnq6fSmM2F11Bjcsy
Paul Campbell pushed by paul@keymail.dev b790e67a8212b58f924a470c9083cc19ec1e47c0 parent 450dbde
7 files changed, +203 −21
  • AGENTS.md +15 −0
  • internal/memoria/breaker.go +25 −0
  • internal/memoria/breaker_test.go +55 −2
  • internal/memoria/handlers_sources.go +36 −13
  • internal/memoria/templates/connectors.html +12 −6
  • internal/memoriatest/gates_test.go +4 −0
  • tools/drive/main.go +56 −0
diff --git a/AGENTS.md b/AGENTS.md
index b5f1e6f..cb7b0f0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -152,11 +152,26 @@ so; it never renders as a confident zero.
now checks the vendored files this app copies.
- The attribute grammar (`rst-btn="primary compact"`), never the class
spelling. Utilities stay classes.
+- **The design system has a vocabulary, and a word outside it renders as
+ nothing.** `rst-callout="danger"` styles nothing: tokens.css carries the
+ tone in `data-tone`, so a value on the attribute itself matches the base
+ selector and no tone. Every flash in this app rendered untoned for
+ months and **three tests pinned it there**, written from the same guess
+ as the markup. Grep the stylesheet for the attribute before writing it.
+ Gate: `TestEveryTemplateAttributeIsInTheDesignSystem`, which checks
+ names *and* whether a value is one anything selects on.
- **Look at the page.** `tools/drive` serves the app with sign-in off so a
browser can see it. It found three bugs in its first run that the whole
Go suite passed over โ€” a heading trail rendering as "โ€บ Revenue", a
snippet repeating its own heading, and a submit button floating over the
textarea. A template test proves markup exists, not that it renders.
+ It then found two more in the sources page the day that page was built:
+ a warning-toned callout containing the words "No refusal anomaly",
+ because one flag carried two unrelated states; and a warning on every
+ row of every freshly-woken instance, because "not polled yet" is the
+ ordinary state of a wake. Neither changes a string a test looks for.
+ **If you add a section to a page, add it to drive too** โ€” the section
+ that is not seeded is the section nobody sees.
**Auth**
- `requireSession` **fails closed**. The test seam is `AppForTest`, named
diff --git a/internal/memoria/breaker.go b/internal/memoria/breaker.go
index 946ee5c..9ba151b 100644
--- a/internal/memoria/breaker.go
+++ b/internal/memoria/breaker.go
@@ -461,3 +461,28 @@ func (s *Store) recordForBreaker(ctx context.Context, asker suite.Subject, cands
}
}
}
+
+// SeedTallyForDrive writes a counter directly, for tools/drive only.
+//
+// **A seam, named so it cannot be reached by accident**, and held by the
+// same `cmd/` gate as AppForTest: it writes into the past, which is
+// exactly what a production caller must never do โ€” the breaker's whole
+// claim is that the counters record what actually happened when it
+// happened.
+//
+// drive needs it because the alternative is seeding a week of real S2
+// traffic against three fake siblings to look at one page.
+func (s *Store) SeedTallyForDrive(ctx context.Context, app, who string, when time.Time, visible, notVisible int) {
+ answers := make([]suite.Authorization, 0, visible+notVisible)
+ for i := 0; i < visible; i++ {
+ answers = append(answers, suite.VISIBLE)
+ }
+ for i := 0; i < notVisible; i++ {
+ answers = append(answers, suite.NOT_VISIBLE)
+ }
+ if len(answers) == 0 {
+ return
+ }
+ _ = s.recordAuthorizationsAt(ctx, app,
+ suite.Subject{Kind: suite.SubjectAddress, Value: who}, answers, when)
+}
diff --git a/internal/memoria/breaker_test.go b/internal/memoria/breaker_test.go
index de03387..cfb44b5 100644
--- a/internal/memoria/breaker_test.go
+++ b/internal/memoria/breaker_test.go
@@ -506,7 +506,7 @@ func TestTheBreakerNoticeReachesTheOwnerAndNoReader(t *testing.T) {
t.Fatalf("the fixture did not fire: %v โ€” nothing below is about "+
"placement if there is no notice to place", b.Verdict)
}
- sentence, alarming := breakerSentence(b, false)
+ sentence, alarming := breakerSentence(b)
// It names the anomaly rather than gesturing at it: an owner has to be
// able to act on this without opening a log.
@@ -559,7 +559,7 @@ func TestCannotDetermineSaysWhichHalfIsMissing(t *testing.T) {
} {
got, alarming := breakerSentence(Breaker{
App: "Sheets", Verdict: BreakerCannotDetermine, Lacking: c.lacking,
- }, false)
+ })
for _, want := range c.want {
if !strings.Contains(got, want) {
t.Errorf("lacking %v produced %q, want it to contain %q",
@@ -580,3 +580,56 @@ func TestCannotDetermineSaysWhichHalfIsMissing(t *testing.T) {
}
}
}
+
+// A row with nothing wrong carries no alarm, and the two lines are toned
+// independently.
+//
+// **This exists because looking at the rendered page found two bugs the
+// whole Go suite passed over**, on markup every template test agreed was
+// correct:
+//
+// 1. one `Alarming` flag carried both lines, so a sibling that had merely
+// not been polled yet rendered "No refusal anomaly." inside a warning
+// callout โ€” a warning box with reassurance in it;
+// 2. "not polled yet" was itself alarming, and memoria hibernates, so
+// every row of every freshly-woken instance warned about the ordinary
+// case. ยง8.1's argument about a breaker that fires for ordinary people,
+// arriving in the freshness line instead.
+//
+// Both are about what a person sees and neither changes a string a test
+// was checking for. What this pins is the property underneath: the alarm
+// is reserved for something actually being wrong.
+//
+// MUTATION-TESTED 2026-09-07, each verified red then reverted:
+//
+// breakerSentence takes an incoming flag again -> quiet-row case red
+// "not polled yet" sets CurrentAlarming -> quiet-row case red
+// a firing breaker returns false -> firing case red
+// a cannot-determine returns false -> cannot-determine red
+func TestOnlyAWrongThingIsAlarming(t *testing.T) {
+ for _, c := range []struct {
+ name string
+ b Breaker
+ alarm bool
+ }{
+ {"quiet", Breaker{App: "Docs", Verdict: BreakerQuiet}, false},
+ {"firing", Breaker{App: "Sheets", Verdict: BreakerFiring, Askers: 3}, true},
+ {"cannot determine", Breaker{App: "Cal", Verdict: BreakerCannotDetermine,
+ Lacking: []string{"the baseline"}}, true},
+ } {
+ _, alarm := breakerSentence(c.b)
+ if alarm != c.alarm {
+ t.Errorf("%s: alarming = %v, want %v", c.name, alarm, c.alarm)
+ }
+ }
+
+ // And the two lines cannot infect each other: a quiet breaker stays
+ // quiet whatever the freshness line is doing. Asserted by the
+ // signature โ€” breakerSentence takes no incoming flag โ€” and here by
+ // the value, because a signature is not a test.
+ if _, alarm := breakerSentence(Breaker{App: "Docs", Verdict: BreakerQuiet}); alarm {
+ t.Error("a quiet breaker reported an alarm; the first version " +
+ "inherited one from the freshness line and rendered " +
+ "\"No refusal anomaly.\" in a warning box")
+ }
+}
diff --git a/internal/memoria/handlers_sources.go b/internal/memoria/handlers_sources.go
index 4fbb41b..d1205a8 100644
--- a/internal/memoria/handlers_sources.go
+++ b/internal/memoria/handlers_sources.go
@@ -18,13 +18,22 @@ type sourceRow struct {
// says it is not known. ยง8.1: a failed or stale S1 states what the
// corpus is current as of rather than implying freshness.
Current string
+ // CurrentAlarming and BreakerAlarming are **separate on purpose**, and
+ // the first version had one flag for both.
+ //
+ // Looking at the rendered page is what caught it: a sibling that had
+ // simply not been polled yet set the flag, and the breaker sentence โ€”
+ // "No refusal anomaly." โ€” was then rendered inside a warning callout.
+ // A warning box with reassurance in it is the notice a reader learns
+ // to skip, which is the failure ยง8.1 spends a paragraph avoiding,
+ // arriving through the markup rather than the predicate.
+ //
+ // Two lines, two states, toned independently. A template test would
+ // have proved both strings were present and told me nothing.
+ CurrentAlarming bool
// Breaker is the refusal breaker's sentence.
- Breaker string
- // Alarming marks the row for the template so a firing breaker and a
- // cannot-determine read differently from a quiet one. Two states, not
- // three: "I cannot tell" and "something is wrong" both want the
- // owner's attention, and only "nothing is wrong" does not.
- Alarming bool
+ Breaker string
+ BreakerAlarming bool
}
// siblingRows builds the sibling section of the owner's sources page.
@@ -58,10 +67,21 @@ func (a *app) siblingRows(r *http.Request) []sourceRow {
// that fails is not a count of zero, and the same rule governs
// a timestamp that cannot be read.
row.Current = "Could not read when this source was last checked."
- row.Alarming = true
+ row.CurrentAlarming = true
case at.IsZero():
- row.Current = "Not yet checked since this instance started."
- row.Alarming = true
+ // **Not alarming, and that is the second thing looking at the
+ // page taught.** memoria hibernates, so "not polled yet" is
+ // the ordinary state of every wake's first seconds โ€” the
+ // warm-up is running while this renders. A warning on every
+ // row of every freshly-woken instance is a notice owners
+ // learn to skip, which is ยง8.1's argument about a breaker
+ // firing for ordinary people, arriving in the freshness line
+ // instead.
+ //
+ // A warm-up that keeps failing is not invisible for it: the
+ // breaker below says cannot-determine, and every search from
+ // that sibling carries its own withholding notice.
+ row.Current = "Not checked yet since this instance woke."
default:
row.Current = "Current as of " + at.Format("2 January 2006, 15:04 UTC") + "."
}
@@ -69,9 +89,9 @@ func (a *app) siblingRows(r *http.Request) []sourceRow {
b, err := a.store.RefusalBreaker(r.Context(), app.Name, now)
if err != nil {
row.Breaker = "Could not read this source's refusal history."
- row.Alarming = true
+ row.BreakerAlarming = true
} else {
- row.Breaker, row.Alarming = breakerSentence(b, row.Alarming)
+ row.Breaker, row.BreakerAlarming = breakerSentence(b)
}
out = append(out, row)
}
@@ -84,7 +104,10 @@ func (a *app) siblingRows(r *http.Request) []sourceRow {
// is missing**, because ยง10 gate 8 requires it and asserts the string: an
// owner who is told only "cannot determine" cannot tell a fresh instance
// from a broken one.
-func breakerSentence(b Breaker, alarming bool) (string, bool) {
+// It takes no incoming flag. The first version threaded one through, so
+// a quiet breaker inherited the alarm from an unrelated line and rendered
+// "No refusal anomaly." in a warning box.
+func breakerSentence(b Breaker) (string, bool) {
switch b.Verdict {
case BreakerFiring:
return fmt.Sprintf(
@@ -92,7 +115,7 @@ func breakerSentence(b Breaker, alarming bool) (string, bool) {
"%s last week were refused everything today. Nothing has been "+
"deleted.", b.Askers, b.App), true
case BreakerQuiet:
- return "No refusal anomaly.", alarming
+ return "No refusal anomaly.", false
default:
return "Cannot determine whether this source is refusing people: " +
joinWords(b.Lacking) + " missing. This is not the same as no anomaly.", true
diff --git a/internal/memoria/templates/connectors.html b/internal/memoria/templates/connectors.html
index 9082028..7cfdf38 100644
--- a/internal/memoria/templates/connectors.html
+++ b/internal/memoria/templates/connectors.html
@@ -26,12 +26,18 @@
{{range .Siblings}}
<li rst-list-item>
<span rst-list-title>{{.App}}</span>
- <p rst-cell-mut>{{.Current}}</p>
- {{/* A toned callout only when something wants attention. A row that
- is fine says so in plain text; if every row carried a callout the
- tone would stop meaning anything, which is the same argument ยง8.1
- makes about a breaker that fires for ordinary people. */}}
- {{if .Alarming}}
+ {{/* Two lines, two states, toned separately. One flag for both put
+ "No refusal anomaly." inside a warning callout for any sibling
+ that simply had not been polled yet โ€” found by looking at the
+ page, which is the only thing that would have. */}}
+ {{if .CurrentAlarming}}
+ <div rst-callout data-tone="warning">
+ <p rst-callout-body>{{.Current}}</p>
+ </div>
+ {{else}}
+ <p rst-cell-mut>{{.Current}}</p>
+ {{end}}
+ {{if .BreakerAlarming}}
<div rst-callout data-tone="warning">
<p rst-callout-body>{{.Breaker}}</p>
</div>
diff --git a/internal/memoriatest/gates_test.go b/internal/memoriatest/gates_test.go
index f3a1499..2801e3e 100644
--- a/internal/memoriatest/gates_test.go
+++ b/internal/memoriatest/gates_test.go
@@ -443,6 +443,10 @@ func TestProductionDoesNotUseTheTestConstructor(t *testing.T) {
// gate that greps for AppForTest alone. Found by a code review.
for _, seam := range []string{
"AppForTest", "AsPerson", "UseFakeSource", "WaitForSync",
+ // SeedTallyForDrive writes breaker counters into the past.
+ // The breaker's whole claim is that they record what
+ // happened when it happened.
+ "SeedTallyForDrive",
// sibling.NewForTest builds a plain http.Client with no team
// binding. In production that is ยง6.4's cross-team refusal
// removed โ€” the guard that runs before the transport dials โ€”
diff --git a/tools/drive/main.go b/tools/drive/main.go
index 6697401..0bb1d58 100644
--- a/tools/drive/main.go
+++ b/tools/drive/main.go
@@ -31,6 +31,7 @@ import (
memoria "amadan.net/oficina/memoria/internal/memoria"
"amadan.net/oficina/memoria/internal/memoria/notion"
"amadan.net/oficina/memoria/internal/memoria/seal"
+ "amadan.net/oficina/memoria/internal/memoria/sibling"
)
func main() {
@@ -84,6 +85,7 @@ func main() {
// exists; it cannot show that a status page reads sensibly with real
// numbers in it.
seedConnector(app)
+ seedSiblings(app)
fmt.Printf("drive serving on http://%s (no sign-in)\n", *addr)
if err := http.ListenAndServe(*addr, mux); err != nil {
@@ -163,3 +165,57 @@ func seedConnector(app interface {
memoria.NewSyncer(app.Store(), slog.New(slog.NewTextHandler(os.Stderr,
&slog.HandlerOptions{Level: slog.LevelWarn}))).RunWith(ctx, src, c.ID, run.ID)
}
+
+// seedSiblings puts three suite apps on the Sources page in the three
+// states the breaker can be in, so the section can actually be looked at.
+//
+// **It exists because the section was built and shipped without anybody
+// seeing it.** drive had no roster, so `/connectors` rendered the
+// connector list and nothing else โ€” the markup was tested, the page was
+// not, which is the exact gap AGENTS.md says drive is for.
+//
+// The three states are seeded rather than one, because they differ in
+// tone and in length and those are what a person has to be able to tell
+// apart at a glance: a firing breaker is a sentence with a number in it,
+// a cannot-determine is a longer sentence that must not read as
+// reassurance, and a quiet one is a short line that must not shout.
+func seedSiblings(app interface {
+ UseSiblings(memoria.Custody)
+ Store() *memoria.Store
+}) {
+ ctx := context.Background()
+ apps := []sibling.Sibling{
+ {Name: "Docs", BaseURL: "https://drive.docs.oficina.works", Token: "drive"},
+ {Name: "Sheets", BaseURL: "https://drive.sheets.oficina.works", Token: "drive"},
+ {Name: "Calendar", BaseURL: "https://drive.calendar.oficina.works", Token: "drive"},
+ }
+ custody, err := memoria.NewSiblings("drive", nil, apps)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "seed siblings:", err)
+ return
+ }
+ app.UseSiblings(custody)
+
+ store := app.Store()
+ now := time.Now().UTC()
+
+ // Docs: quiet, and polled a moment ago.
+ if err := store.StartCounting(ctx, "Docs", now.AddDate(0, 0, -30)); err != nil {
+ fmt.Fprintln(os.Stderr, "seed siblings:", err)
+ }
+
+ // Sheets: firing. Three people who could read last week, refused today.
+ if err := store.StartCounting(ctx, "Sheets", now.AddDate(0, 0, -30)); err != nil {
+ fmt.Fprintln(os.Stderr, "seed siblings:", err)
+ }
+ for _, who := range []string{"ana@example.com", "ben@example.com", "cleo@example.com"} {
+ store.SeedTallyForDrive(ctx, "Sheets", who, now.AddDate(0, 0, -3), 5, 0)
+ store.SeedTallyForDrive(ctx, "Sheets", who, now, 0, 4)
+ }
+
+ // Calendar: cannot determine โ€” configured two days ago, so the
+ // baseline is short. This is the row that must not read as "fine".
+ if err := store.StartCounting(ctx, "Calendar", now.AddDate(0, 0, -2)); err != nil {
+ fmt.Fprintln(os.Stderr, "seed siblings:", err)
+ }
+}