| 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. |
| 16 | package main |
| 17 | |
| 18 | import ( |
| 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 | |
| 39 | func 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. |
| 106 | func 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. |
| 159 | type driveSource struct{ tree map[string][]notion.Ref } |
| 160 | |
| 161 | func (d *driveSource) Roots(ctx context.Context) (roots, orphans []notion.Ref, err error) { |
| 162 | return d.tree[""], nil, nil |
| 163 | } |
| 164 | |
| 165 | func (d *driveSource) Fetch(ctx context.Context, ref notion.Ref) (*notion.Content, error) { |
| 166 | return ¬ion.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 | |
| 174 | func 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. |
| 222 | func 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. |
| 267 | type driveModel struct{} |
| 268 | |
| 269 | func (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 | |