The footer walk skipped seven routes silently; now it cannot
TestTheFooterStatesTheModelSwitch, in the previous commit, claimed a page could not skip the egress claim because it walks every declared route. It could: the walk dropped every route that returned non-200 or had no footer, and under AppForTest — no sign-in wiring, no Home — seven declared GET routes 404. Among them /signed-out, /signin/local and /account/home, which ARE layout pages in production when Home is configured. The gate was blind to them and said nothing. The code was right: every one renders through renderHome or sets ModelOn directly, including the Home library's confirm page (its Bridge is built with Render: a.renderHome). The GATE was what overclaimed. Two changes. Every skip must now appear in noLayoutHere with its reason, and an unlisted skip fails — removing /signed-out's reason goes red, naming it. And the Home-only pages are rendered, both ways, through the Home router fixture, where they exist: a renderHome that forgets ModelOn goes red on /signed-out and /account/home. Found by probing my own gate while the review of the previous commit ran, because I had named it to the reviewer as the most likely hole and it was cheaper to check than to wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+69
−0
internal/memoria/home_router_test.go+38 −0internal/memoriatest/footer_model_test.go+31 −0
CI passed — run details
CI log
=== vet === go vet ./... === fmt === === test === go test ./... ? amadan.net/oficina/memoria/cmd/memoria [no test files] ok amadan.net/oficina/memoria/internal/memoria 1.204s ok amadan.net/oficina/memoria/internal/memoria/chunk (cached) ok amadan.net/oficina/memoria/internal/memoria/fact 0.522s ? amadan.net/oficina/memoria/internal/memoria/icons [no test files] ok amadan.net/oficina/memoria/internal/memoria/index (cached) ok amadan.net/oficina/memoria/internal/memoria/notion (cached) ok amadan.net/oficina/memoria/internal/memoria/provider 1.128s ok amadan.net/oficina/memoria/internal/memoria/query (cached) ok amadan.net/oficina/memoria/internal/memoria/seal (cached) ok amadan.net/oficina/memoria/internal/memoria/sibling (cached) ok amadan.net/oficina/memoria/internal/memoriatest 1.623s ? amadan.net/oficina/memoria/tools/bench [no test files] ? amadan.net/oficina/memoria/tools/drive [no test files] ? amadan.net/oficina/memoria/tools/seed [no test files] ? amadan.net/oficina/memoria/tools/vendorassets [no test files] === migration-check === go run github.com/carlosframework/rastrillo/cmd/rastrillo migration check rastrillo migration check: models and migrations agree
diff --git a/internal/memoria/home_router_test.go b/internal/memoria/home_router_test.go| index 3cb83f9..1a326ad 100644 |
| --- a/internal/memoria/home_router_test.go |
| +++ b/internal/memoria/home_router_test.go |
| @@ -300,3 +300,41 @@ func TestHomeSigninClearsOnlyDefinitivelyStaleSession(t *testing.T) { |
| t.Fatal("stale cookie prevented Home sign-in", target) |
| } |
| } |
| + |
| +// The footer's egress claim on the pages that exist only when Home is |
| +// configured. TestTheFooterStatesTheModelSwitch (memoriatest) walks every |
| +// declared route, but its harness does not configure Home, so these 404 |
| +// there and cannot be checked — this is where they are. They render |
| +// through renderHome, the Render callback the Home library is given. |
| +func TestHomePagesStateTheModelSwitch(t *testing.T) { |
| + for _, on := range []bool{true, false} { |
| + f := newHomeRouterFixture(t) |
| + if on { |
| + f.app.store.extractor = &recorder{} |
| + } |
| + f.local(t) |
| + checked := 0 |
| + for _, path := range []string{"/signed-out", "/account/home", "/signin/local"} { |
| + w := f.do("GET", path, "") |
| + b := w.Body.String() |
| + i := strings.Index(b, "<footer") |
| + if w.Code != 200 || i < 0 { |
| + continue |
| + } |
| + foot := strings.Join(strings.Fields(b[i:]), " ") |
| + if !strings.Contains(foot, "leaves this instance") { |
| + continue |
| + } |
| + checked++ |
| + if on && (strings.Contains(foot, "no document ever") || !strings.Contains(foot, "TensorX")) { |
| + t.Errorf("AI on, %s: footer does not disclose TensorX: %q", path, foot) |
| + } |
| + if !on && (!strings.Contains(foot, "no document ever") || strings.Contains(foot, "TensorX")) { |
| + t.Errorf("AI off, %s: footer does not make the true claim: %q", path, foot) |
| + } |
| + } |
| + if checked == 0 { |
| + t.Fatalf("AI on=%v: no Home page rendered a footer with a claim; this proves nothing", on) |
| + } |
| + } |
| +} |
diff --git a/internal/memoriatest/footer_model_test.go b/internal/memoriatest/footer_model_test.go| index b2adacb..f4f1242 100644 |
| --- a/internal/memoriatest/footer_model_test.go |
| +++ b/internal/memoriatest/footer_model_test.go |
| @@ -16,6 +16,25 @@ import ( |
| "amadan.net/oficina/memoria/internal/memoria/provider" |
| ) |
| +// noLayoutHere is every declared GET route that renders no page layout |
| +// under this harness, and why. A route missing from here that renders no |
| +// footer fails the walk. |
| +var noLayoutHere = map[string]string{ |
| + "GET /search": "the results fragment, swapped into a page by search.js — no layout", |
| + "GET /api/native/session": "JSON for the native client", |
| + "GET /native/start": "native sign-in redirect; not wired by AppForTest", |
| + "GET /native/authorize": "native sign-in redirect; not wired by AppForTest", |
| + "GET /auth/callback": "sign-in redirect; auth is not wired by AppForTest", |
| + "GET /auth/verify": "sign-in redirect; auth is not wired by AppForTest", |
| + "GET /auth/start": "Home sign-in redirect; Home is not configured here", |
| + "GET /auth/finish": "Home sign-in redirect; Home is not configured here", |
| + "GET /auth/local/complete": "Home sign-in redirect; Home is not configured here", |
| + "GET /signin/local": "Home-only page — covered by memoria.TestHomePagesStateTheModelSwitch", |
| + "GET /signed-out": "Home-only page — covered by memoria.TestHomePagesStateTheModelSwitch", |
| + "GET /account/home": "Home-only page — covered by memoria.TestHomePagesStateTheModelSwitch", |
| + "GET /account/home/confirm": "Home library's own confirm page, rendered through renderHome — covered there", |
| +} |
| + |
| // countingModel is a stand-in model that counts calls and points at any |
| // "address: " line, as a compliant model would. |
| type countingModel struct{ calls int } |
| @@ -80,6 +99,18 @@ func footers(t *testing.T, h http.Handler, docID int64) map[string]string { |
| body := rec.Body.String() |
| i := strings.Index(body, "<footer") |
| if rec.Code != http.StatusOK || i < 0 { |
| + // **A skip has to be explained**, or the walk proves less than |
| + // it says. Every route below renders no layout under this |
| + // harness, for the reason given; anything else that fails to |
| + // render a footer is a page this gate cannot see, and it fails |
| + // rather than passing quietly. (The first version skipped |
| + // silently, and seven routes went unchecked.) |
| + if why, ok := noLayoutHere[route]; !ok { |
| + t.Errorf("%s returned %d with no footer, and is not a known non-page route; "+ |
| + "the footer check cannot see it", route, rec.Code) |
| + } else if why == "" { |
| + t.Errorf("%s: skipped without a reason", route) |
| + } |
| continue |
| } |
| // Whitespace collapsed, as a browser renders it: the template wraps |