| index 50afbb2..12d7b8d 100644 |
| --- a/docs/superpowers/specs/2026-09-03-memoria-design.md |
| +++ b/docs/superpowers/specs/2026-09-03-memoria-design.md |
| @@ -3,8 +3,8 @@ |
| The Oficina suite's memory. One instance per team, at |
| `<team>.memoria.oficina.works`. |
| |
| -**Status:** draft 3, 2026-09-03; amended 2026-09-04 (§8.3 provider, §8.4 |
| -shape, decisions 13–14). |
| +**Status:** draft 3, 2026-09-03; amended 2026-09-04 and 2026-09-05 (§8.3 |
| +provider, §8.4 shape, decisions 13–14, live-key verification, gate 17). |
| |
| Review history, per AGENTS.md §Review. None of these models wrote the |
| draft they reviewed; all drafts were written by Opus. |
| @@ -16,6 +16,7 @@ draft they reviewed; all drafts were written by Opus. |
| | 3 | Fable | REQUEST CHANGES — 22 findings. (This row read "none yet" while §13 recorded the review as done; §13 was right.) | |
| | 3, §8.3 provider amendment | Fable | REQUEST CHANGES — 4 MAJOR, 4 MINOR, 2 NIT. All applied; §1.5 records what it changed | |
| | 3, §8.4 and decisions 13–14 | Fable | REQUEST CHANGES — 5 MAJOR, 4 MINOR, 1 NIT. All applied. It found §8.4's central argument resting on a misreading of §6.5 and §4.1; §1.6 records what it changed | |
| +| 3, gate 17 + live-key verification | Fable | REQUEST CHANGES — 3 MAJOR, 4 MINOR, 3 NIT. All applied. **It mutation-tested gate 17 itself** and found five idioms the gate blessed, including one that served a declared path outside the session gate; §1.7 records what it changed | |
| |
| §1.2 records what the draft-1 reviews changed; §1.3 what the draft-2 |
| review changed. **Draft 3 has not been reviewed.** |
| @@ -307,7 +308,9 @@ forward breaks nothing. |
| on decision 5 — the search floor is the product — not on decision 3. A |
| conversational surface that keeps the invariant is deferred to §12, to be |
| argued on its merits. The row is attributed to the spec author with Paul |
| -unconfirmed, because unlike decisions 13's two halves it is not his ruling. |
| +unconfirmed, because unlike decision 13's two halves it was not his ruling. |
| +(He confirmed it on 2026-09-05; the attribution stands as written, since |
| +who *made* a ruling and who *approved* it are different facts.) |
| |
| The lesson, distinct from §1.4's unfailable gates and §1.5's rule broken in |
| the paragraph that states it: **a preference will always find a rule that |
| @@ -316,6 +319,47 @@ decision 5's reasons. Reaching past those for decision 3 made a choice look |
| forced, and cost two misread sections to do it. When a constraint arrives |
| conveniently, check it against the text rather than the memory of the text. |
| |
| +### 1.7 What the gate review changed, and the rule it produced |
| + |
| +The branch's third review covered gate 17 and the live-key verification. |
| +Fable returned REQUEST CHANGES with three MAJOR findings, and one of them |
| +is the most useful thing any review has done here. |
| + |
| +**It mutation-tested the gate itself.** Gate 17 shipped with a comment |
| +naming three mutations, each verified red. A reviewer took the test into a |
| +scratch copy and tried five more — all ordinary chi and `ServeMux` idioms, |
| +none exotic — and **all five passed green.** `r.Route("/chat", …)` was |
| +blessed by the declared `"/"` inside it. `r.With(mw).Post` was invisible |
| +because the receiver is a call, not an identifier. `r.Delete` passed on a |
| +path declared only for GET. `r.Handle("/")` bound every method at once. And |
| +`mux.HandleFunc("/documents", …)` served a **declared** path while |
| +bypassing `requireSession` — worse than an undeclared route, because the |
| +allowlist actively approved it. |
| + |
| +**The rule that follows, and it belongs in AGENTS.md:** mutation-testing |
| +your own gate tests the mutations you thought of, and those are the ones |
| +you already defended against while writing it. Three green mutations chosen |
| +by the author is not evidence the gate works; it is evidence the author is |
| +consistent. **The mutations must be chosen by someone who did not write the |
| +gate.** This is §Review's argument, one level down: the reviewer of a rule |
| +should not be its author, and neither should the adversary of a test. |
| + |
| +The rewrite keys on method *and* path — which §10's own list had declared |
| +all along and the test had not implemented — and separates the chi and |
| +`ServeMux` namespaces. Eight mutations now, all verified. The rewrite also |
| +nearly shipped its own bug: following an arbitrary selector chain to find |
| +the receiver made `r.URL.Query().Get("err")` resolve to `r`, reporting two |
| +query parameters as undeclared routes. Caught by running it. |
| + |
| +**The other two MAJORs are the familiar shape.** §8.3 still asserted |
| +TensorX "runs the weights it serves" on the strength of a 403 that had no |
| +control probe — the reading recorded was the middle of three, and the worst |
| +one is that the model name is in their routing table and merely gated. And |
| +two of Paul's four rulings were announced as recorded in QUESTIONS while |
| +being in neither §1 nor §13, with the interpretation of the load-bearing |
| +one living only in a commit message. That is §1.6's failure by omission: |
| +the strong reading asserted where reviewers do not look. |
| + |
| --- |
| |
| ## 2. What memoria is |
| @@ -1015,12 +1059,19 @@ storing, so the table below is unchanged — 90k chunks × 4096 f32 is |
| get by mis-reading the kept width as the f32@512 intermediate (184 MB is |
| what truncation yields; int8 quantisation is what is stored). |
| |
| -**On the wire it is ~4 GB, and there is no opt-out.** Tested 2026-09-05: |
| -`encoding_format: "base64"` is **accepted and silently ignored** — the |
| -response comes back as JSON floats, byte-identical to the request that |
| +**On the wire it is several GB, and base64 is not the way out.** Tested |
| +2026-09-05: `encoding_format: "base64"` is **accepted and silently |
| +ignored** — the response returns JSON floats, matching the request that |
| omits the parameter. Not an error, just dropped. So the ~2 GB base64 figure |
| -an earlier draft hedged with is unavailable, and the honest number is the |
| -JSON one. Note the behaviour as well as the number: **a provider that |
| +an earlier draft hedged with is unavailable. |
| +**The replacement figure is derived, not measured, and the range is wide.** |
| +At ~11 bytes a float it is ~4 GB across 90k chunks; servers of this family |
| +often emit 17-significant-digit reprs at ~20 bytes, which is ~7 GB. A probe |
| +response was in hand and nobody counted its bytes — measure bytes-per- |
| +vector once and multiply, rather than guessing at a serialiser's precision. |
| +**And "no opt-out" overstates it:** `Content-Encoding: gzip` collapses |
| +digit strings, Go's client asks for it by default, and whether TensorX |
| +honours it is one response header on the same probe (§13). Note the behaviour as well as the number: **a provider that |
| accepts a parameter and discards it will do so again**, which is an |
| argument for asserting on what comes back rather than on what was asked |
| for. |
| @@ -1221,20 +1272,38 @@ gate 24's canary load-bearing, and pinning DeepSeek does not. |
| |
| **Both tiers are therefore DeepSeek:** `model.cheap` is |
| `deepseek/deepseek-v4-flash-0731` and `model.frontier` is |
| -`deepseek/deepseek-v4-pro-0813`. This is the rule below applied, not a |
| -quality judgement — Paul's ruling that the work is "categorisation and |
| -memory, not complex reasoning" (§13) is what makes a dated id worth more |
| -here than a preferred family. **The cost is concentration:** both tiers now |
| -come from one lab as well as one host, so a DeepSeek withdrawal moves both |
| -at once. Recorded rather than mitigated; §13. |
| - |
| -**The canary stays, demoted.** At first configuration, embed a fixed set of |
| -public, non-content sentences — not team content, so §3.2 is untouched — |
| -seal the vectors, re-embed on wake, and **state an anomaly when cosine |
| -drift exceeds a stated ε**. With both chat tiers dated it is no longer the |
| -only thing standing between us and a silent weight swap; it remains the |
| -only such instrument for **the embedding model**, which has no dated form |
| -and cannot be swapped without re-embedding the corpus. Gate 24. |
| +`deepseek/deepseek-v4-pro-0813`. Confirmed by Paul on 2026-09-05. |
| + |
| +**Be exact about how much of that the rule decided.** It forced the move |
| +off two undated ids. It did *not* force DeepSeek twice: the survey above |
| +lists a dated Qwen (`qwen3-235b-a22b-2507`), so a two-lab pair satisfies |
| +the rule equally. Choosing DeepSeek for both is a judgement about price, |
| +recency and family coherence — defensible, and **made without putting the |
| +Qwen option in front of Paul**, which §13 records rather than quietly |
| +keeps. |
| + |
| +**The concentration gets a stated response, not just a note.** Both tiers |
| +are now one lab as well as one host, so a DeepSeek withdrawal moves both at |
| +once. Name the fallback now — `qwen/qwen3-235b-a22b-2507` for the frontier |
| +tier, `z-ai/glm-5.3-flash` for the cheap one with gate 24 carrying it — so |
| +a withdrawal is a config change through §Review on a normal day rather than |
| +a research task on a bad one. |
| + |
| +**The canary is not demoted, and an earlier draft of this section demoted |
| +it wrongly.** At first configuration, embed a fixed set of public, |
| +non-content sentences — not team content, so §3.2 is untouched — seal the |
| +vectors, re-embed on wake, and **state an anomaly when cosine drift exceeds |
| +a stated ε**. Gate 24, covering all three pinned ids, and §11's sentence |
| +stands unamended: it is the only thing standing between a stable id and a |
| +silent swap. |
| + |
| +**A dated id at a reseller pins a label, not a behaviour.** DeepSeek does |
| +ship dated release artefacts, so `-0813` plausibly names lab weights — but |
| +TensorX did not train them, §13 records that id immutability *at TensorX* |
| +is unread, and everything short of the weights that moves a chat model's |
| +output is theirs to change under an unchanged id: quantisation, chat |
| +template, sampling defaults, context truncation. A dated id buys a name to |
| +print on an answer. It does not buy a detector. |
| |
| **`openrouter/auto` is disqualified**, and so is anything shaped like it. |
| It selects by trailing-7-day community spend, changes weekly, and cannot be |
| @@ -1284,9 +1353,18 @@ OpenAI on decision 4's own words — on one half of them it loses: |
| |
| - **Fewer parties, not one party.** OpenRouter is a proxy, so the proxy and |
| the upstream that runs the weights both see content, and "one named |
| - provider" would be naming one of two. TensorX runs the weights it serves. |
| - But **one contractual counterparty is not one organisation seeing the |
| - corpus**: a vendor this young on infrastructure in three cities is |
| + provider" would be naming one of two. TensorX **presents as** running the |
| + weights it serves — no proxy is interposed that we can see, and the one |
| + probe that could have shown otherwise returned 403 rather than an |
| + embedding. **That is weaker than it reads.** A 403 is a refusal, not a |
| + not-found, it was one key's refusal, and the probe had no control: nobody |
| + asked what a *nonsense* model id returns. If a made-up name 404s while |
| + `text-embedding-3-small` 403s, then that name is in their routing table |
| + and the probe found exactly the thing it was built to find, merely gated. |
| + §13 carries the two control calls that separate the readings; until they |
| + run this is a presentation, not a fact. |
| + Beyond it, **one contractual counterparty is not one organisation seeing |
| + the corpus**: a vendor this young on infrastructure in three cities is |
| plausibly renting it, and a subprocessor sees content in exactly the |
| sense §3.2 means. That is a DPA question and the DPA is unread (§13). |
| The claim is *narrowed*, not *closed*, and §13's item says so. |
| @@ -1446,16 +1524,22 @@ instructions — ~5k tokens in, ~500 out. Against the ids §8.2 pins: |
| `deepseek/deepseek-v4-flash-0731` at $0.25/$0.30 per M is **~$0.0014 a |
| query**, and `deepseek/deepseek-v4-pro-0813` at $2.00/$4.00 is |
| **~$0.012**. Five people × 20 queries/day × 22 days = 2,200 queries: |
| -**~$3/month if most route cheap, ~$26 if most route frontier.** Read that |
| -carefully: |
| -the cheap figure sits *inside* the $0.001–0.003 per-query range this |
| -section already carried; the frontier one, at $0.012, finally falls below |
| -its $0.02–0.10 range rather than merely sitting in it. **So the ceiling |
| -fell by about 4× and the floor did not move.** That is a consequence of |
| -§8.2 pinning dated ids from one family, not of the provider switch, and it |
| -is still not a reason to route more queries to the frontier tier — §8.2's |
| -three model-free classes remain the design, and this section's closing |
| -sentence still holds. |
| +**~$3/month if most route cheap, ~$26 if most route frontier.** |
| + |
| +Read that against one baseline, because an earlier draft used two. Draft 3 |
| +carried **~$5 cheap and ~$100 frontier** against a generic provider. So the |
| +frontier month fell about **4×** and the cheap month about **1.6×** — |
| +both moved, and the earlier claim that "the ceilings fell, the estimates |
| +did not" was true only of the intermediate glm/kimi pair and is now |
| +retired. Per query the cheap figure still sits inside the $0.001–0.003 |
| +range this section always carried; the frontier one, at $0.012, has fallen |
| +below its $0.02–0.10 range rather than sitting in it. |
| + |
| +Most of that came from §8.2 pinning dated ids from one family, not from the |
| +provider switch, and none of it is a reason to route more queries to the |
| +frontier tier — §8.2's three model-free classes remain the design, and this |
| +section's closing sentence still holds. |
| + |
| Derived-artefact regeneration (§5.2) adds a synthesis-class call per |
| invalidated artefact and is unbudgeted until the feature exists. |
| |
| @@ -1634,13 +1718,27 @@ renders on its own origin. The home never sees `q`. |
| `POST /documents`, `POST /documents/{id}/delete`, plus the auth routes. |
| Written that way so that adding a thread or chat route goes red on the |
| way in (decision 14) rather than being argued about afterwards. |
| - **Built 2026-09-04** as `TestNoUndeclaredRoutes`, and mutation-tested |
| - three ways: a `POST /chat` registration goes red as an undeclared |
| - route, a computed path goes red because the gate refuses a pattern it |
| - cannot read, and an empty walk root goes red on a `seen == 0` check |
| - rather than passing over nothing. It reads the AST, not a grep. What it |
| - does not catch is a route registered through a helper outside the walk, |
| - or on a receiver renamed away from `r`/`mux`. |
| + **Built 2026-09-04** as `TestNoUndeclaredRoutes`; **rewritten |
| + 2026-09-05 after a review mutation-tested it and found the first |
| + version blessing five idioms.** It keyed on path alone, so |
| + `r.Route("/chat", …)` passed on the declared `"/"` inside it, |
| + `r.With(mw).Post` was invisible because the receiver is a call rather |
| + than an identifier, `r.Delete` passed on a path declared only for GET, |
| + `r.Handle("/")` bound every method at once, and — worst — |
| + `mux.HandleFunc("/documents", …)` served a **declared** path while |
| + bypassing `requireSession`. It now keys on **method and path together, |
| + which is what this list already declared**, and keeps the chi and |
| + `ServeMux` namespaces apart. Eight mutations, each verified red and |
| + reverted, are named in the test's own comment. It reads the AST, not a |
| + grep. What it still does not catch: a route registered through a helper |
| + outside `internal/memoria` and `cmd`, or on a receiver renamed away |
| + from `r`/`mux`. |
| + **A note on how the rewrite nearly went wrong.** Following an arbitrary |
| + selector chain to find the receiver made `r.URL.Query().Get("err")` |
| + resolve to `r`, and the gate reported two query parameters as |
| + undeclared GET routes. `r` is the conventional name for a request as |
| + well as a router. The chain now follows only chi's own chaining |
| + constructors — caught by running it, not by reading it. |
| 18. **External-source authorization and disclosure.** A memoria-owned |
| document is governed by memoria's grants, an upstream revocation does |
| *not* remove it, and the copy the README promises is deletable is |
| @@ -1819,25 +1917,55 @@ rows. |
| parsing the snapshot and building id maps are all outside the 5-second |
| budget as computed. |
| - **The two-parties problem is narrowed, not closed.** §8.3 selects a |
| - provider that runs its own weights rather than a proxy, which removes |
| - OpenRouter's proxy-plus-upstream shape. It does not establish that one |
| - organisation sees the corpus: **whether a subprocessor sits under TensorX |
| - is a DPA question and the DPA is unread**, as are the retention terms |
| - §8.3 calls "advertised". Both before slice 2. |
| + provider that *presents as* running its own weights rather than a proxy, |
| + which removes OpenRouter's proxy-plus-upstream shape. It does not |
| + establish that one organisation sees the corpus: **whether a subprocessor |
| + sits under TensorX is a DPA question and the DPA is unread**, as are the |
| + retention terms §8.3 calls "advertised". |
| + **Paul ruled on 2026-09-05 to "proceed with TensorX for sure" rather than |
| + hold for these**, and the scope of that is stated here rather than |
| + assumed, because assuming it once already produced §1.6. The spec reads |
| + it as: *the provider decision is settled, and slice 2 may be designed and |
| + built against it.* It does **not** read it as permission to enable egress |
| + over real team content before the DPA and retention terms are read — |
| + that is a different act, it is the one §3.6 makes promises to the reader |
| + about, and nobody has been asked. **If Paul meant the wider thing, this |
| + is the line to change.** Until then: build against it; read them before |
| + the switch is thrown. |
| - **The live calls ran on 2026-09-05, and settled less than hoped.** |
| `/v1/embeddings` serves `qwen/qwen3-embedding-8b` as documented, and the |
| model is in `GET /v1/models`. The `text-embedding-3-small` probe — |
| the one that would have shown TensorX proxying to OpenAI — |
| - **returned 403.** It did not succeed, so no proxying was demonstrated. |
| - **But 403 is a refusal, not a not-found:** a 404 would have said "no such |
| - model", while 403 is consistent with a routable name this key is not |
| - entitled to. It rules out "they proxy for anyone"; it does not rule out |
| - "the name routes for some account tier". Re-probe with a different key if |
| - one is ever to hand, and treat the result as narrowing rather than |
| - closing — like the subprocessor item above, which the API cannot answer |
| - at all. |
| + **returned 403.** It did not succeed, so no proxying was demonstrated — |
| + but read that precisely, because **the probe had no control** and three |
| + readings fit it equally. *Benign:* a blanket "model not in your |
| + allowlist" 403 for any string at all. *Middle:* a routable name this one |
| + key is not entitled to. ***Worst, and the reading the probe existed to |
| + find:*** the name is in their routing or entitlement table because it |
| + **is** served for someone, and the 403 is the gate rather than the |
| + absence. Nothing run so far separates the three. |
| + **Two calls would, and neither needs more than a key.** Probe a nonsense |
| + model id — if it 404s where `text-embedding-3-small` 403s, that |
| + difference is the finding. Then `GET /v1/models/text-embedding-3-small`. |
| + **Record the error body verbatim** while there: it is the provider's own |
| + text about a model name we chose, not team content, so §3.2 does not |
| + reach it and quoting it costs nothing. Also capture the response headers |
| + for the `Content-Encoding` question in §7.2, and the accepted batch size. |
| + Until those run, §8.3 says "presents as" rather than "runs", and this |
| + narrows rather than closes — like the subprocessor question above, which |
| + no API call can answer at all. |
| - **`encoding_format: "base64"` is answered and the answer is no** — see |
| - §7.2. Accepted and silently discarded. |
| + §7.2. Accepted and silently discarded. The wire figure that replaced it |
| + is derived rather than measured, and `Content-Encoding: gzip` is |
| + unexamined; both are one probe away. |
| +- **Paul was not shown the two-lab option when he kept DeepSeek.** §8.2's |
| + survey lists a dated Qwen (`qwen3-235b-a22b-2507`), so the pinning rule |
| + permits DeepSeek for one tier and Qwen for the other. He ruled "keep |
| + DeepSeek" on 2026-09-05 against a presentation that framed |
| + DeepSeek-for-both as what the rule required. It was not; it is a |
| + judgement about price, recency and coherence. The ruling stands and |
| + nothing needs undoing — but the record should say what was in front of |
| + him, and §8.2 now names the Qwen id as the frontier fallback. |
| - **Is the model id stable?** Partly answered by the catalogue. It lists |
| `deepseek/deepseek-v4-pro` *and* `deepseek/deepseek-v4-pro-0813`, which |
| demonstrates that bare names here are aliases over dated snapshots. §8.2 |