oficina / memoria Public

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

Plain git — no account needed to clone.

Download

Download this file

1// Command drive serves memoria with no sign-in and seeds it, so a real
2// browser can look at the real pages.
3//
4// Why this exists: a Go test renders a template into a buffer and proves
5// the markup is there. It cannot see that a stylesheet failed to load,
6// that an attribute the app writes is styled by nobody, or that an app
7// rule quietly overrode a framework component. Sheets learned this the
8// hard way — a menu bar that worked perfectly from the keyboard and not
9// at all with a mouse — and its tools/drive is the committed version of
10// the same recipe.
11//
12// It builds the app through memoria.AppForTest, which sets an explicit
13// unauthenticated flag. requireSession itself fails closed — an earlier
14// version fell through on nil auth and a code review showed the test that
15// was supposed to make that safe did not exist.
16package main
17
18import (
19 "context"
20 "flag"
21 "fmt"
22 "log/slog"
23 "net/http"
24 "os"
25 "path/filepath"
26 "strings"
27
28 "time"
29
30 "amadan.net/rastrillo/rastrillo/db"
31
32 memoria "amadan.net/oficina/memoria/internal/memoria"
33 "amadan.net/oficina/memoria/internal/memoria/notion"
34 "amadan.net/oficina/memoria/internal/memoria/provider"
35 "amadan.net/oficina/memoria/internal/memoria/seal"
36 "amadan.net/oficina/memoria/internal/memoria/sibling"
37)
38
39func main() {
40 addr := flag.String("addr", "127.0.0.1:8824", "address to serve on")
41 // The database goes somewhere disposable, and where that is has to
42 // be arguable: this used to be hard-coded to one machine's scratch
43 // directory, which does not exist on any other, and drive then
44 // panicked before serving a byte — a poor reason not to look at the
45 // page.
46 //
47 // Empty means os.TempDir(), which MkdirTemp reads for itself. That
48 // is the default rather than a named path because MkdirTemp does NOT
49 // create the parent: any default that names a directory is a default
50 // that panics on the first machine where it is missing, and the
51 // recipe in drop_test.go would have handed the next reader exactly
52 // that panic.
53 parent := flag.String("dir", "", "parent directory for the throwaway database (default: the system temp directory)")
54 flag.Parse()
55
56 logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}))
57 dir, err := os.MkdirTemp(*parent, "drive")
58 if err != nil {
59 panic(err)
60 }
61 d, err := db.Open(filepath.Join(dir, "drive.db"), logger)
62 if err != nil {
63 panic(err)
64 }
65 defer d.Close()
66
67 dek := make([]byte, 32)
68 for i := range dek {
69 dek[i] = byte(i * 3)
70 }
71 sealer, err := seal.New(dek)
72 if err != nil {
73 panic(err)
74 }
75
76 mux, app, err := memoria.AppForTest(d, "http://"+*addr, sealer, logger)
77 if err != nil {
78 panic(err)
79 }
80
81 // §8.9's model layer, with a stand-in for TensorX — drive has no key,
82 // and a 🤖 row is a visual state nobody sees unless something makes
83 // one. The stand-in is a COMPLIANT model, so what drive shows is the
84 // intended rendering; gate 39 is where non-compliant ones are tested.
85 app.UseFakeModel(driveModel{})
86
87 // Seed enough to make every surface non-empty: results, a heading
88 // trail, a highlight, a document list with more than one row.
89 seedViaHTTP(mux, *addr)
90
91 // And a connector with a finished import behind it, so the source
92 // pages are worth looking at. A template test proves the markup
93 // exists; it cannot show that a status page reads sensibly with real
94 // numbers in it.
95 seedConnector(app)
96 seedSiblings(app)
97
98 fmt.Printf("drive serving on http://%s (no sign-in)\n", *addr)
99 if err := http.ListenAndServe(*addr, mux); err != nil {
100 panic(err)
101 }
102}
103
104// seedViaHTTP posts documents through the real handler, so the seeded
105// state is exactly what a person would produce.
106func seedViaHTTP(h http.Handler, addr string) {
107 docs := []struct{ title, body string }{
108 {"Q3 board pack", "## Revenue\n\nJune revenue was 1,350,000 across all regions, up from 1,180,000 in the same month last year. The increase is almost entirely the enterprise tier.\n\n## Costs\n\nHosting costs fell after the migration, and support headcount was flat.\n\n## Risks\n\nOne customer is 22% of recurring revenue. That concentration is the single largest risk on this page and it has not improved since Q2."},
109 {"Sealing notes", "## The one rule\n\nA sheet's contents are sealed at rest and the bucket never holds a plaintext cell. The instance holds the key while it runs, so a compromised live host can read a sheet it has open — that is a deliberate softening and the README says so.\n\n## What it does not cover\n\nEmbeddings are content: a vector inverts back to text well enough that an unsealed index is a plaintext cell in a costume."},
110 // §8.8's fact box, seeded in THREE shapes rather than one.
111 //
112 // AGENTS.md: the section that is not seeded is the section
113 // nobody sees, and driving this page is what found the heading
114 // trail rendering as "› Revenue". A box only ever seeded in its
115 // happy case is a box whose two interesting states nobody looks
116 // at — and those are the states that decide whether the rule is
117 // right.
118 {"Tito office", "## Contact\n\nOffice: +353 (0)1 547 7854\n\nEircode: D02 X285\n\nVAT: IE3384527RH\n\n## Post\n\nWe are on the second floor; the intercom is marked Tito."},
119 // Two numbers, distinct sentence labels: the ENUMERATED case.
120 // The reader should see both and be able to tell which is which,
121 // because the document said "unused" and the label carries it.
122 // This paragraph is index/rank_test.go's, quoted rather than
123 // invented — it came off the live instance.
124 {"Reception notes", "## Office Address\n\nTurn the knob. The office phone number is: +353 (0)1 547 7854. We also have the following unused phone numbers: +353 (0)1 598 1234."},
125 // §8.9: an address, which no recogniser can validate, so only the
126 // model layer can box it — rendered 🤖 and named, beside the
127 // validated eircode inside it. Search "office address".
128 {"Head office", "## Visiting\n\nOffice address: Unit 4, 12 Main Street, Dublin 2, D02 X285\n\nRing the bell marked Tito."},
129 // A bulleted pair: the COLLISION case, where both labels fall to
130 // the same trail and the box must be ABSENT. An earlier draft of
131 // the plan called the paragraph above the collision case; it is
132 // the opposite, and a seed that demonstrates the wrong thing is
133 // worse than no seed at all.
134 {"Suppliers", "## Contact\n\n- +353 (0)1 555 0101\n- +353 (0)1 555 0202"},
135 // §8.8's NARROWED case (Paul, 2026-09-29), in the shape a Notion
136 // page arrives in: list items, the office number, and unused ones
137 // — two of them bare bullets that fall to the heading trail.
138 // Search "what is the office phone number?": one row, labelled
139 // without its bullet, and the "Matched “office”" line under it.
140 // Search "phone number" for the cap's silence beside it.
141 {"Office", "## Office Address\n\n- Turn the knob\n- The office phone number is: +353 (0)1 547 7854\n- We also have the following unused phone numbers: +353 (0)1 598 4321\n - +353 (0)1 598 4322\n - +353 (0)1 598 4323"},
142 {"Meeting, 12 August", "## Present\n\nEveryone.\n\n## Decisions\n\nWe will not build a suite index app. It answers a different question from the one anyone asked, and per-item access control is not expressible by the app that would have to publish it.\n\n## Actions\n\nPaul to decide whether the central host is a directory or an authenticator."},
143 }
144 for _, doc := range docs {
145 body, ctype := multipartBody(doc.title, doc.body)
146 req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://"+addr+"/documents", body)
147 req.Header.Set("Content-Type", ctype)
148 req.Header.Set("Origin", "http://"+addr)
149 rec := &recorder{header: http.Header{}}
150 h.ServeHTTP(rec, req)
151 if rec.code != http.StatusSeeOther {
152 fmt.Fprintf(os.Stderr, "seed %q: status %d\n", doc.title, rec.code)
153 }
154 }
155}
156
157// driveSource is a small stand-in for Notion, so the connector pages have
158// a real tree and real counts behind them.
159type driveSource struct{ tree map[string][]notion.Ref }
160
161func (d *driveSource) Roots(ctx context.Context) (roots, orphans []notion.Ref, err error) {
162 return d.tree[""], nil, nil
163}
164
165func (d *driveSource) Fetch(ctx context.Context, ref notion.Ref) (*notion.Content, error) {
166 return &notion.Content{
167 Markdown: "## " + ref.Title + "\n\nThis page was imported from Notion during a drive run. " +
168 "It exists so the connector screens have something real to render.",
169 Children: d.tree[ref.ID],
170 UnreadBlocks: 2,
171 }, nil
172}
173
174func seedConnector(app interface {
175 Store() *memoria.Store
176 UseFakeSource(memoria.SourceClient, func(context.Context, string) (string, error))
177 WaitForSync(int64, time.Duration) bool
178}) {
179 src := &driveSource{tree: map[string][]notion.Ref{
180 "": {{ID: "handbook", Title: "Handbook", EditedTime: "t1", Kind: notion.KindPage}},
181 "handbook": {
182 {ID: "onboarding", Title: "Onboarding", Path: []string{"Handbook"}, EditedTime: "t1", Kind: notion.KindPage},
183 {ID: "expenses", Title: "Expenses", Path: []string{"Handbook"}, EditedTime: "t1", Kind: notion.KindPage},
184 },
185 "onboarding": {
186 {ID: "week-one", Title: "Week one", Path: []string{"Handbook", "Onboarding"}, EditedTime: "t1", Kind: notion.KindPage},
187 },
188 }}
189 app.UseFakeSource(src, func(ctx context.Context, token string) (string, error) { return "drive-workspace", nil })
190 ctx := context.Background()
191 if err := app.Store().SaveConnector(ctx, "notion", "ntn_drive_token", "drive-workspace"); err != nil {
192 fmt.Fprintln(os.Stderr, "seed connector:", err)
193 return
194 }
195 c, err := app.Store().Connector(ctx, "notion")
196 if err != nil {
197 fmt.Fprintln(os.Stderr, "seed connector:", err)
198 return
199 }
200 run, err := app.Store().StartRun(ctx, c.ID)
201 if err != nil {
202 fmt.Fprintln(os.Stderr, "seed connector:", err)
203 return
204 }
205 memoria.NewSyncer(app.Store(), slog.New(slog.NewTextHandler(os.Stderr,
206 &slog.HandlerOptions{Level: slog.LevelWarn}))).RunWith(ctx, src, c.ID, run.ID)
207}
208
209// seedSiblings puts three suite apps on the Sources page in the three
210// states the breaker can be in, so the section can actually be looked at.
211//
212// **It exists because the section was built and shipped without anybody
213// seeing it.** drive had no roster, so `/connectors` rendered the
214// connector list and nothing else — the markup was tested, the page was
215// not, which is the exact gap AGENTS.md says drive is for.
216//
217// The three states are seeded rather than one, because they differ in
218// tone and in length and those are what a person has to be able to tell
219// apart at a glance: a firing breaker is a sentence with a number in it,
220// a cannot-determine is a longer sentence that must not read as
221// reassurance, and a quiet one is a short line that must not shout.
222func seedSiblings(app interface {
223 UseSiblings(memoria.Custody)
224 Store() *memoria.Store
225}) {
226 ctx := context.Background()
227 apps := []sibling.Sibling{
228 {Name: "Docs", BaseURL: "https://drive.docs.oficina.works", Token: "drive"},
229 {Name: "Sheets", BaseURL: "https://drive.sheets.oficina.works", Token: "drive"},
230 {Name: "Calendar", BaseURL: "https://drive.calendar.oficina.works", Token: "drive"},
231 }
232 custody, err := memoria.NewSiblings("drive", nil, apps)
233 if err != nil {
234 fmt.Fprintln(os.Stderr, "seed siblings:", err)
235 return
236 }
237 app.UseSiblings(custody)
238
239 store := app.Store()
240 now := time.Now().UTC()
241
242 // Docs: quiet, and polled a moment ago.
243 if err := store.StartCounting(ctx, "Docs", now.AddDate(0, 0, -30)); err != nil {
244 fmt.Fprintln(os.Stderr, "seed siblings:", err)
245 }
246
247 // Sheets: firing. Three people who could read last week, refused today.
248 if err := store.StartCounting(ctx, "Sheets", now.AddDate(0, 0, -30)); err != nil {
249 fmt.Fprintln(os.Stderr, "seed siblings:", err)
250 }
251 for _, who := range []string{"ana@example.com", "ben@example.com", "cleo@example.com"} {
252 store.SeedTallyForDrive(ctx, "Sheets", who, now.AddDate(0, 0, -3), 5, 0)
253 store.SeedTallyForDrive(ctx, "Sheets", who, now, 0, 4)
254 }
255
256 // Calendar: cannot determine — configured two days ago, so the
257 // baseline is short. This is the row that must not read as "fine".
258 if err := store.StartCounting(ctx, "Calendar", now.AddDate(0, 0, -2)); err != nil {
259 fmt.Fprintln(os.Stderr, "seed siblings:", err)
260 }
261}
262
263// driveModel stands in for TensorX so a 🤖 row can be looked at. It does
264// what a compliant model is asked to: every text after "address: " to the
265// end of its line, copied byte for byte, pointing at the passage it came
266// from. Nothing here dials anything.
267type driveModel struct{}
268
269func (driveModel) ExtractAddresses(_ context.Context, passages []string) ([]provider.Pointer, error) {
270 var out []provider.Pointer
271 for i, p := range passages {
272 for _, line := range strings.Split(p, "\n") {
273 if j := strings.Index(strings.ToLower(line), "address: "); j >= 0 {
274 out = append(out, provider.Pointer{Passage: i, Text: strings.TrimSpace(line[j+len("address: "):])})
275 }
276 }
277 }
278 return out, nil
279}
280