oficina / memoria Public

Decision 15, amended by Paul: deterministic first, then a cheap model

Paul ruled on 2026-09-19, on the question §13 had carried open since the
14th: "do as much as possible with deterministic analysis, and then
attempt to build metadata with the best value models via TensorX that
make sense."

He was asked something narrow — should a postal address, which has no
checksum, be extractable by a cheap model. The answer is general, so §8.9
records the general form. The strict reading decision 15 carried was
mine, not his, and it is now struck rather than deleted: it made "has a
checksum" the boundary of the feature rather than the boundary of the
deterministic layer.

Nothing in §8.8 changes. Recogniser, validator, label, cap, collision and
citation all survive the amendment untouched, and the model half sits
strictly after them: a model runs only where deterministic extraction
produced nothing, for a kind that has no validator. A model never
second-guesses a checksum, never picks among candidates, and never writes
a label — a composed label is the one part of the box no check can
verify.

The condition that makes it safe is a string comparison. Every value the
model returns must appear verbatim in a passage that was sent to it, and
memoria checks that itself; a value that fails is dropped unrendered. So
a hallucinated address cannot reach the page — not because the model is
good, but because a string that is not in the corpus fails Contains. The
model is reduced to pointing, the span displayed is sliced from the
document as before, and the model's own output text is never rendered.

Egress is NOT a new leak-list entry and should not be written as one: the
same provider, the same passages, the same moment as §8.3 already
describes. What is new is a second trigger for a request to leave, and
the disclosure copy should say so in the same breath as the first.

Cheap tier by rule, never frontier: a task whose output is checked by
string comparison does not improve with a better model, it only costs
more.

Not built — this is slice 2, and gate 31 holds nothing calling a provider
before it. Gate 39 written now rather than after, including the mutation
that matters most: stub the provider to return a different phone number
from the validated one, and assert not just that the recogniser's answer
renders but that the provider was never called. A gate reading only the
rendered value cannot tell "called and ignored" from "never called", and
the first is an egress nobody authorised.

What stays open is narrower and is his: which metadata is worth a
query-time call. Address is in; opening hours, a role, an effective date
are plausible and unruled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev e7fc91395fec4a58fe02adfd1ad11f701eccc666 parent 674e03b
2 files changed, +210 −10
  • NOTES.md +24 −0
  • docs/superpowers/specs/2026-09-03-memoria-design.md +186 −10
diff --git a/NOTES.md b/NOTES.md
new file mode 100644
index 0000000..8c8ee57
--- /dev/null
+++ b/NOTES.md
@@ -0,0 +1,24 @@
+# Memoria v0.1.0
+
+First release, and the first public one of Memoria at all: the suite's
+shared memory, one instance per team. Ask a question, get an answer
+grounded in your team's own documents — with a citation you can open,
+every time.
+
+What this release is, precisely: a search engine, not an assistant. There
+is no model call anywhere in this binary. Passages and index are sealed
+at rest under the team's own key, and no document leaves the instance at
+query time or at indexing time, because nothing is sent anywhere to be
+indexed. Signing in does fetch `/.well-known/keymail` for the domain in
+the address typed, so a DNS name leaves; no document does.
+
+The artifact is a stripped linux/arm64 binary, built with `make release`
+and stamped so that `GET /api/version` answers `v0.1.0` rather than the
+`dev` default — which is what lets a deploy check actually fail.
+
+Verify what you downloaded:
+
+ shasum -a 256 memoria-linux-arm64
+
+Built from this tag with CGO disabled, so it has no runtime library
+dependencies beyond the kernel.
diff --git a/docs/superpowers/specs/2026-09-03-memoria-design.md b/docs/superpowers/specs/2026-09-03-memoria-design.md
index 1a5f0a9..2de0391 100644
--- a/docs/superpowers/specs/2026-09-03-memoria-design.md
+++ b/docs/superpowers/specs/2026-09-03-memoria-design.md
@@ -108,7 +108,7 @@ embedding is a plaintext cell in a costume — §3.1.
| 12 | **Name: `memoria`.** House convention is plain Spanish office nouns. A *memoria* is both a memory and, in Spanish office usage, a written record. | `archivo`; `cerebro`; `base`; anything English | Paul, 2026-09-03 |
| 13 | **The provider is TensorX, and memoria supports exactly one.** A team owner's choice is on or off, not which — a configurable base URL would let an owner reinstate a proxy while the answer still printed "one named provider". Chosen for EU jurisdiction and open weights, criteria decision 4 did not state, at a known cost in id-pinning and frontier quality (§8.3). | OpenRouter (a proxy: two parties see content); OpenAI direct (better id-pinning, worse jurisdiction and cost) | Paul, 2026-09-04, both halves — *"we go with exactly one for now: TensorX. If we want to swap that later we can."* Swapping the *generation* provider is a code change and a review, not a setting; swapping the *embedding* provider is that plus a full corpus re-embed unless the new host serves the same open weights (§8.3, §13) |
| 14 | **memoria is a search page, not a chat.** One GET search form, `?q=`, one query in and one page out, with `/documents/{id}` as the only other passage surface. This follows from decision 5, not from decision 3 — §8.4's invariant is what decision 3 requires, and a conversational surface could satisfy it. | A chat UI with citations; a threaded follow-up. Deferred to §12, not forbidden — the invariant forbids the leak, this row chooses the shape | Spec author, 2026-09-04, answering a question Paul asked; **confirmed by Paul 2026-09-05**, parking the conversational surface in §12 rather than forbidding it |
-| 15 | **The fact box calls no model, ever.** A validated fact — eircode, phone, IBAN, VAT, email — is extracted deterministically and shown above the results with its citation. A kind with no validator is not a kind. §8.8 | A cheap extractive model picking the span (§8.7's retrieval-augmented calculator with a different noun); a model composing the label; postal address and `url` as kinds, neither having anything to validate | **The constraint is Paul's, 2026-09-14** — *"do as much deterministic ordering of the data before we go doing inference on it"*, asking for the box in the first place. **The "ever", the validator rule, the kind list and the postal exclusion are the spec author's reading of it and are unconfirmed — §13.** Decision 14 is the form this follows: record which half was ruled and by whom, rather than letting the interpretation live in a commit message (§1.9) |
+| 15 | ~~**The fact box calls no model, ever.**~~ **AMENDED BY PAUL, 2026-09-19.** The rule is **deterministic first, then a model for what deterministic analysis cannot reach**: *"do as much as possible with deterministic analysis, and then attempt to build metadata with the best value models via TensorX that make sense."* A validated fact is still extracted by recogniser and shown with its citation; a kind with **no** validator — a postal address — is no longer excluded, it is the model's half. §8.8, §8.9 | The strict reading, which was the spec author's and not Paul's, and which this row carried for five days: it foreclosed the obvious next request and made "has a checksum" the boundary of the feature rather than the boundary of the deterministic layer. **What is NOT amended:** a model never picks among validated candidates, never composes a label, and never produces a value that is not a verbatim span of a cited passage | **Paul, 2026-09-19**, ruling on the question §13 had recorded as open since 2026-09-14. The strict version was mine; he reversed it, and the reversal is the general rule and not a carve-out for addresses |
**Three of these are now suite rulings, and this spec cites them rather
than restating them.** Decision 1 is **suite ruling 1**, decision 3 is
@@ -2699,7 +2699,11 @@ 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.
+invalidated artefact and is unbudgeted until the feature exists. **So does
+§8.9**, at one cheap-tier call per query that names a valueless kind and
+whose recognisers found nothing — a small fraction of a small fraction,
+and cheap-tier by rule rather than by choice, since a task checked by
+string comparison does not improve with a better model.
The money is recurring query tokens by an order of magnitude — the
economic argument for §8.2's router and its three model-free classes.
@@ -2933,12 +2937,22 @@ class would have given one. The box is attempted, and then §8.2 routes the
query exactly as it would have. Paul's constraint is deterministic
**first**, not deterministic **instead**.
-It does not touch §3.2. No egress, no provider, no embedding — the box
-works on an instance with AI switched off, which is decision 5's floor and
-therefore the whole product today. It is part of §8.1's floor and gate 15
-covers it. **Nothing on this path is logged**, gate 4's rule applying
-unchanged: a fact, its label and its kind are all content, and the query
-that named the kind is content too.
+**This section's own path touches nothing in §3.2.** No egress, no
+provider, no embedding — the deterministic box works on an instance with
+AI switched off, which is decision 5's floor and therefore the whole
+product today. It is part of §8.1's floor and gate 15 covers it.
+**Nothing on this path is logged**, gate 4's rule applying unchanged: a
+fact, its label and its kind are all content, and the query that named
+the kind is content too.
+
+**That claim is now load-bearing in a way it was not, because decision 15
+has been amended and §8.9 adds a model.** What survives the amendment
+without qualification is everything in this section: recogniser,
+validator, label, cap, collision, citation. What §8.9 adds sits *after*
+all of it and is switched off with AI. The sentence to keep straight is
+**"the deterministic box has no egress"**, not "the fact box has no
+egress" — and §8.9 exists as its own section rather than a paragraph here
+precisely so that the two cannot be confused by someone reading quickly.
#### One extraction, over the ruled results, and why drafts 1 and 2 had two
@@ -3396,6 +3410,153 @@ natural anyway. Drafts 1 and 2 both said this gate could run against
that is the renamed-test failure §10 opens with, committed twice in the
section that cites it.
+### 8.9 The model half: metadata deterministic analysis cannot reach
+
+Paul's ruling, 2026-09-19, amending decision 15: *"do as much as possible
+with deterministic analysis, and then attempt to build metadata with the
+best value models via TensorX that make sense."*
+
+The question put to him was narrow — should a postal address, which has
+no checksum and therefore no validator, be extractable by a cheap model?
+**The answer he gave is general**, and this section records the general
+form rather than the carve-out: the deterministic layer is the floor and
+not the ceiling, and where it cannot reach, a cheap model may — under
+conditions that keep every guarantee §8.8 establishes.
+
+**Not built. This is slice 2**, because nothing in this binary calls a
+provider before slice 2 and gate 31 enforces it. What lands now is the
+ruling and its conditions, so that the section is written before the code
+rather than after — which is the order §1.9 exists to insist on.
+
+#### The ordering, which is the whole of the ruling
+
+1. **Recognisers run first, always.** If a validated candidate exists for
+ a wanted kind, §8.8 answers and no model is called. The deterministic
+ answer is never second-guessed, never re-ranked, and never handed to a
+ model for an opinion.
+2. **A model runs only where the deterministic layer produced nothing**,
+ for a kind that has no validator to produce it with.
+3. **With AI off, step 2 does not happen and step 1 is unchanged.**
+ Decision 5's floor is intact: the search page and §8.8's box are the
+ product, and this section is a garnish on a garnish.
+
+**The failure this ordering prevents is a model improving on a checksum.**
+A mod-97 check is right or wrong; a model asked to "find the IBAN" in the
+same passage can be neither. Letting it near a kind that validates would
+trade a provable answer for a plausible one, which is the direction §8.7
+spends a whole section refusing.
+
+#### The condition that makes it safe: the model proposes, a string
+#### comparison disposes
+
+**Every value the model returns must appear verbatim in one of the
+passages that were sent to it, and memoria checks that itself.** Not
+"should"; the check is a `strings.Contains` against passages memoria
+already holds, and a value that fails it is dropped without being
+rendered.
+
+This is the load-bearing sentence of the section, and it is what makes
+the rest of §8.8 survive intact:
+
+- **A hallucinated address cannot reach the page.** Not because the model
+ is good, but because a string that is not in the corpus fails a string
+ comparison. The model's job is reduced to *pointing*, and pointing is
+ checkable.
+- **§8.8's display rule is unchanged**: the span shown is the document's
+ bytes, because it is located in the document by the check above and
+ sliced from there. The model's own output text is never rendered — it is
+ a lookup key and then it is discarded.
+- **The label is still the document's**, derived by §8.8's five rules from
+ the span's position once located. **A model never writes a label**, and
+ this is not a stylistic preference: a composed label is the one part of
+ the box that no string comparison can verify.
+- **Gate 14 and §8.4 hold without amendment.** Every character of the box
+ still traces to a cited passage, and the citation is the passage the
+ span was found in.
+
+**Ambiguity, the cap and collision are §8.8's, unchanged.** If the model
+points at three addresses, three render; at four, none. It never picks,
+for the same reason the deterministic layer never picks — rank order
+carries no claim about which address is the office one, and a model's
+preference carries less.
+
+#### Egress, and what is not new about it
+
+**Passages go to TensorX at query time.** That is the same egress §8.3
+already describes for the 🤖 paragraph: the same provider, the same
+passages, the same moment in the same request, after §4.1 has ruled. It
+is **not a new entry on §3.2's leak list** and should not be written as
+one — the leak is "query-time passages reach the named provider when AI
+is on", and it exists the moment slice 2 does.
+
+What *is* new is the trigger. §8.2's router sends passages when a query
+needs an answer; this sends them when a query named a kind and
+deterministic extraction found nothing. A team with AI on now has a
+second reason for a request to leave the instance, and §8.3's disclosure
+copy should say so in the same breath as the first.
+
+**Model and tier:** `model.cheap` (§8.2, §8.3 — `deepseek/deepseek-v4-flash-0731`
+at the time of writing), because this is extraction from a handful of
+passages and §8.2 already classes that as the cheap tier's work. **Never
+the frontier tier**, whatever the kind: a task whose output is checked by
+string comparison does not improve with a better model, it only costs
+more.
+
+**Cost** is §8.6's to absorb and is unbudgeted until this is built. The
+shape: one cheap call per query that names a valueless kind and finds
+nothing, which is a small fraction of a small fraction.
+
+#### Injection
+
+§8.5 is unchanged and its mitigation is the same partial one. A poisoned
+document can make the model point at an attacker's string, and the verbatim
+check will pass — because the string really is in the corpus. What bounds
+it is what bounds §8.8: the passage passed §4.1, the citation is openable,
+and somebody in this team put the document there. **The check defends
+against the model inventing; it does not defend against the corpus lying**,
+and those are different problems with different answers.
+
+#### What "that make sense" leaves open, and it is Paul's
+
+The ruling names a principle and one instance. **Which metadata is worth
+building this way is a product judgement and §13 records it as his**, not
+because the spec is being timid but because the answer is a list that will
+grow and each entry costs a query-time model call.
+
+The first entry is **postal address**, which is what he was asked about
+and which §12 no longer defers. Plausible next ones, none of them ruled:
+opening hours, a person's role or title, a document's effective date, a
+company registration number in a jurisdiction whose format memoria does
+not know. **The test for admitting one is not "could a model do it" — a
+model could do all of them — but "is the answer a verbatim span somebody
+would want cited".** Anything that needs summarising is not metadata, it
+is an answer, and §8.2's router already owns answers.
+
+#### Gate
+
+**39. A value the model returns that is not in a sent passage never
+renders** (§8.9). Three fixtures against a stubbed provider, and the
+first is the presence half:
+
+- the stub returns a span that **is** in a passage → the box renders it,
+ with the document's bytes and the document's label;
+- the stub returns a plausible address that is **in no passage** → **no
+ box**, and nothing is logged that contains it (gate 4, and a provider's
+ response body is content — gate 31);
+- the stub returns a span that is in a passage belonging to a document
+ **§4.1 withheld from this reader** → no box. The passages sent are the
+ ruled ones, so this should be unreachable; the fixture exists because
+ "unreachable" is what §1.9 says to test rather than assume.
+
+Mutation: render the model's own output text rather than locating it and
+slicing the document, and fixture 2 goes red. Send the unruled passages
+and fixture 3 goes red. **And the deterministic-first ordering needs its
+own:** stub the provider to return a *different* phone number from the
+one the recogniser validated, and assert the recogniser's answer is what
+renders and **that the provider was never called at all** — a gate that
+checks only the rendered value cannot tell "called and ignored" from
+"never called", and the first of those is an egress nobody authorised.
+
---
## 9. Surfaces
@@ -4172,7 +4333,8 @@ memoria had been recording itself as blocked on something that was not in
its way, which is a more expensive mistake than being blocked.
**Slice 2 — the model layer (§8).** Named provider, router,
-answer-above-citations, the §3.6 README. *No Docs §11.2 qualification is
+answer-above-citations, **§8.9's metadata half and gate 39**, the §3.6
+README. *No Docs §11.2 qualification is
owed any more: §8.3 cites suite §8 and Docs cites the same sentence, which
is how discussion 33 closed.* **This is the slice that turns on
whole-corpus egress**,
@@ -4215,7 +4377,7 @@ commit. Fixed 2026-09-05; §1.9's lesson arriving inside the fix for §1.8.*
| Calendar as a source | Calendar has a spec and content; bodies off by default even then |
| Mail beyond forward-or-label | Never by sweep. A mail repo exists **and** a per-thread audience maps onto a team |
| **Reaching a fact the ranked query does not** | Somebody reports a fact they can see in a document the box did not surface. §8.8's candidates are the ruled top-25, so an eircode in a document that never says "eircode" is found only if something else about it ranks. **Whatever this turns out to be, it is not "index the kind words"** — §8.8 measures that collapsing `bm25()` for the injected term from 2.7169 to 0.0000 |
-| **Postal address as a fact kind** | Decision 15 keeps it out because there is nothing to validate. The two openings are an anchored one — an eircode certifies the lines above it, deterministically — and §8.2's extractive single-source class, which is a model and therefore a decision-15 amendment |
+| ~~**Postal address as a fact kind**~~ | **PROMOTED 2026-09-19 to §8.9, slice 2.** Decision 15's amendment makes "nothing to validate" the reason a model handles it rather than the reason it is excluded. Kept struck rather than deleted, because the row's reasoning is what the amendment overturned |
| **A durable, sealed facts table** | The per-query extraction shows up in a profile. §8.8 rejects it today on derived-state grounds and because it would freeze the recognisers at ingest — **not** on leak grounds, which is what draft 1 argued and which attacked an unsealed design nobody proposed. Note the direction: storing facts makes recogniser improvements *harder* to ship, so this row gets triggered by cost and never by correctness |
| Cached answers | Query cost becomes visible. Requires §5.2's provenance invalidation first. **Triggering this expires §8.4's "stored nowhere"** |
| A conversational surface | Someone wants one, and it keeps §8.4's invariant: no passage crosses a request, in a prompt or a response. Decision 14 chose the shape on decision 5's grounds, not decision 3's, so this is a product argument to have — not a rule to break. §6.5 affords it: a four-turn transcript re-authorizes in one batch, one round |
@@ -4251,6 +4413,20 @@ the rest is work, measurement or somebody else's repo.**
nothing. Defensible, and recorded here for the same reason decision
15's widening is: §1.9's lesson is that the load-bearing half of a
decision must not live only in a commit message.
+ - **~~Decision 15 records more than Paul ruled.~~ CLOSED 2026-09-19,
+ and he reversed it.** The strict reading — no model, ever — was the
+ spec author's. Paul ruled the opposite and gave the general rule:
+ deterministic as far as it goes, then a cheap model for what it
+ cannot reach. §8.9. **What replaces it as open is narrower and is
+ his:** *which* metadata is worth a query-time model call. Postal
+ address is ruled in; opening hours, a role, an effective date, a
+ foreign company number are all plausible and none is ruled. The spec's
+ proposed test is "is the answer a verbatim span somebody would want
+ cited" — anything needing summary is an answer, not metadata, and
+ §8.2 already owns answers.
+ - **The struck text is kept below**, because what the amendment
+ overturned is only legible beside what it replaced.
+
- **Decision 15 records more than Paul ruled, and says so rather than
quietly widening.** What he asked for on 2026-09-14 is the box, and
the constraint — *"do as much deterministic ordering of the data