Authority caps the actor

aimee

A two-service C runtime whose memory splits into typed facts and episodic rows, gates recall on per-fact confidence and PII sensitivity, and writes mutations to a hash-chained append-only store.

Carries 7 of 7 rubric mechanisms. Most systems here carry none or one (41%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

1. Executive Summary

aimee is a personal-assistant runtime written mostly in C — roughly 819,000 lines of C and 89,000 lines of headers, with 149,000 lines of Go, 113,000 of Python and a small TypeScript frontend beside them, across 7,831 commits since 3 June 2026. It is AGPL-3.0. The tree ships two services: aimee-server, which holds the memory of one human, and aimee-kb, which holds a corpus and a team's membership graph.

The memory layer is not one thing. There is an episodic store — a memories table with tiers, salience, surprise, validity bounds and a lifecycle state — and there is a typed-fact layer, a graph of (source, relation, target) edges carrying a confidence class, an authority rank and its own lifecycle columns. They are written by different paths, read by different queries, and corrected by different verbs. Most of what this report finds worth reading sits in the second one.

Seven marks. A refused value gets its own keyed row in memory_rejection_tombstones, consulted at the head of the mutation seam and repeated as a database trigger underneath it, so a later extraction that re-asserts the value is declined rather than admitted. The typed-fact layer excludes superseded, invalidated and suppressed rows from every recall query it has, unconditionally and behind no flag. The episodic row carries event-time validity bounds separate from its transaction timestamps, with a function that answers was this in force on 12 June. The recall path filters candidates by scope and records why it dropped each one, with row-level security under it. A person rejects and restores memories through three RPCs over one queue, and the verdict gates what the write path will admit next. The audit store is append-only by two independent mechanisms, says out loud which of them an attacker can remove, and receives memory mutations from a trigger in the same transaction as the row change. And the recall tests are built so that an empty result fails them.

What the report cannot claim is coverage. At over a million lines this is the largest tree in the corpus by an order of magnitude, and the sections below state where the trace stops.

2. Mental Model

Two sentences from docs/KNOWLEDGE.md set the intent: "Scope is an authorization boundary. Promotion into a broader scope is an explicit audited write," and "Raw text is not treated as a fact merely because a model extracted it."

The second is the one the code delivers most completely. A fact arriving from a model is not the same object as a fact the user stated, and the difference is carried in the row rather than in a comment. fact_class_for(authority, gate) maps an authenticated user's accepted assertion to Class A at confidence 1.0, a model's accepted assertion to Class B at 0.6, and anything novel to Class C at 0.4 — which is also the floor. The mapping from provenance to authority is deliberately narrow:

assert(fact_authority_from_provenance("user_stated") == FACT_AUTHORITY_USER);
assert(fact_authority_from_provenance("agent_message") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("web") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("document") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("tool") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("delegate") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("synthesis") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance("User_Stated") == FACT_AUTHORITY_MODEL); /* exact match */
assert(fact_authority_from_provenance("") == FACT_AUTHORITY_MODEL);
assert(fact_authority_from_provenance(NULL) == FACT_AUTHORITY_MODEL);

One string maps to user authority and nine do not. Every failure mode — wrong case, empty, null — lands on model authority, which is the fail-closed direction, and test_fact_lifecycle.c:166-175 asserts it in all ten forms.

3. Architecture

Diagram — a turn takes two recall paths that gate differently — the typed-fact block on per-row confidence and PII sensitivity, the episodic path on scope then lifecycle — while retraction, the value-keyed refusal it can install, and the audit trigger run down the write side
Diagram source
flowchart TD
%% caption: a turn takes two recall paths that gate differently — the typed-fact block on per-row confidence and PII sensitivity, the episodic path on scope then lifecycle — while retraction, the value-keyed refusal it can install, and the audit trigger run down the write side
    TURN["turn text"] --> RET{"retraction turn?<br/>scan provider"}
    RET -->|"no answer"| SKIP["log; do NOT retract"]
    RET -->|"yes + attribute"| CAP["authority CAPS the actor<br/>model authority cannot<br/>touch Class A or immutable"]
    CAP --> INV["invalidated_at stamped<br/>row retained"]
    INV --> TOMB["memory_rejection_tombstones<br/>keyed on the value, active=1"]
    INV --> WORM

    EXTRACT["offline facts drain<br/>re-asserts the same triple"] --> TSCHECK{"fm_tombstone_blocks<br/>+ DB trigger backstop"}
    TSCHECK -->|"live refusal"| REFUSE["FACT_MUTATION_TOMBSTONED<br/>nothing written"]
    TSCHECK -->|"none"| ADMIT["assert proceeds"]
    TOMB -.->|"consulted"| TSCHECK
    ADMIT --> WORM

    TURN --> FR["typed-fact recall block"]
    FR --> EX["AND superseded_at = ''<br/>AND invalidated_at = ''<br/>AND suppressed = 0"]
    EX --> FLOOR{"per-row confidence<br/>>= 0.4 floor?"}
    FLOOR -->|"no"| DROP1["withheld"]
    FLOOR -->|"yes"| PII{"row is PII and<br/>turn asks for it?"}
    PII -->|"no"| DROP2["withheld"]
    PII -->|"yes"| BLOCK["fact lines into the envelope"]

    TURN --> VEC["dense recall over memories<br/>WHERE lifecycle_state='active'"]
    VEC --> SCOPE["memory_filter_scope<br/>reject reason: scope_boundary"]
    SCOPE --> LIFE["archived / superseded /<br/>suppressed drop before rerank<br/>reject reason: withheld_state"]
    LIFE --> RERANK["rerank"]
    RERANK --> BLOCK

    WORM[("audit_event<br/>prev_hash / row_hash chain<br/>BEFORE UPDATE/DELETE triggers<br/>fed by kb_audit_outbox intents")]

The two recall paths converge on the same prompt envelope but share no gate. A fact withheld for low confidence and a memory withheld for scope are refused by different code, and neither refusal is silent — both write a reject reason into the recall trace.

4. Essential Implementation Paths

Turn-time retraction. db2_fact_ingest_turn scans the turn for a retraction intent, and the surrounding comment is explicit that extraction is not on this path: "Fact EXTRACTION is offline-only (the memory_facts drain runs pattern + LLM), so we do NOT run db2_fact_ingest_text() on the turn hot path." Retraction stays synchronous because it is a cheap Postgres write with no model in it.

Retraction storage. db2_fact_retract(source, relation, target, authority) normalises the relation, looks up its correction_behavior on the seeded relation type, refuses when that behaviour is CORR_IMMUTABLE and the caller is not a user authority, and otherwise calls db2_fact_mutation_invalidate. The key is the triple. There is no row id anywhere in the call. Per invalidated row that function does two things rather than one: it stamps invalidated_at and bumps version, and it calls fm_tombstone_add_assertion(conn, id, actor, "explicit fact invalidation"), which copies the row's (source, relation, target) into memory_rejection_tombstones under the actor's rank. A retraction therefore leaves both a dead row and a keyed refusal.

Admission. db2_fact_mutation_assert opens a transaction, backfills identity, and calls fm_tombstone_blocks(conn, in->source, in->relation, in->target) before it reads or writes anything else; a live refusal aborts the transaction and returns FACT_MUTATION_TOMBSTONED. The same call guards the approve arm of db2_fact_mutation_review. On the episodic side the Go store's write path (server-go/modules/memory/mutations.go:26-77) selects an active tombstone for the same key, content and scope before it inserts and returns write blocked by rejection tombstone when one exists; Reject (domain.go:135-150) is the one writer of that table.

Recall exclusion. Every query in fact_recall.c that assembles the fact block carries the same predicate. db2_fact_current_count shows the full shape:

SELECT COUNT(*) FROM entity_edges
 WHERE (source = ?1 OR target = ?2) AND edge_class = 'semantic'
   AND superseded_at = '' AND invalidated_at = '' AND suppressed = 0
   AND lifecycle_state IN ('persistent','promoted')

Four separate ways for a fact to be excluded, all read together, none of them optional.

5. Memory Data Model

The memories row carries forty-five stored columns once the schema's own ALTER TABLE additions have run, plus three generated full-text columns. The ones that matter here are lifecycle_state (defaulting to active, with archive_reason and ttl_at beside it), activation_suppressed, valid_from and valid_until held apart from created_at and updated_at, contradiction_group and merged_into, and a negation_tokens column for negative-polarity recall. confidence, evidence_strength, salience and surprise are floats and are not the trust state; the state is the enum. confidence_ceiling is a fourth float and a different kind of thing — a provenance cap the row carries rather than a score, so model-authored material cannot climb by being merged or re-exposed.

memory_rejection_tombstones is the negative half of the model: object_kind over ('fact','memory'), the fact tuple and the episodic tuple in the same row shape, authority_rank, reason, rejected_by, active, rejected_at, restored_at, restored_by. Two partial unique indexes — one per object kind, each restricted to active=1 — make one live refusal per value the schema's own constraint rather than the caller's discipline.

memory_provenance records (memory_id, session_id, action, details, created_at). memory_conflicts pairs two memory ids with a detection time, a resolved flag and a free-text resolution.

The typed-fact side lives in entity_edges with fact_graph_changes recording commits, and fact_evidence carrying a stance — a supporting or opposing citation is a row, not a score adjustment.

A third store holds decisions, and it schedules their re-examination. decision_log (src/modules/db2/c/schema_sqlite.sql:93, with the Postgres columns added by ALTER TABLE … ADD COLUMN IF NOT EXISTS at schema.sql:526-531) carries subject, options, chosen, rationale, assumptions, outcome, author, linked_policy_id, a supersedes_id, a status defaulting to active, and a revisit_when. Three transitions move it. Recording a decision on a subject flips the previous one: UPDATE decision_log SET status = 'superseded' WHERE id = ?1 AND status = 'active' AND subject = ?2 (decision_log.c:108), so one decision is live per subject and the losers are kept rather than deleted. And db2_decision_log_mark_revisit_due (decision_log.c:175) sweeps status = 'active' AND revisit_when != '' AND revisit_when <= pg_now_text() into revisit_due, backed by a partial index on exactly that predicate — a stored judgement that becomes stale on a date its author chose rather than on a later contradiction. The comment justifies the lexicographic comparison rather than assuming it: both sides are ISO-8601, so a date-only revisit_when sorts as due on the day it names.

What the store does not do is reach the read path. decision_log is absent from DB2_MEMORY_RECALL_FILTER_SQL and from the assembled context, so a decision marked revisit_due announces itself to whatever queries the table and to nothing the model sees.

6. Retrieval Mechanics

Dense recall routes through pgvector; the direct SQL collector was removed and the header says where it went. Around it sit a lexical lane, relation-token matching over the fact graph, and session-window expansion in both directions around a hit.

The lexical lane is two queries rather than one. db2_memory_find_facts_fts joins memories_fts to memories through rowid and is tried first; db2_memory_find_facts_like, an unindexed LOWER(...) LIKE '%…%' scan over key, content and use-cases, runs only when the indexed query returns nothing — kept because "FTS is word based" and substring recall would otherwise be lost. Both carry DB2_MEMORY_RECALL_FILTER_SQL and DB2_MEMORY_SCOPE_RANK_SQL in the same statement, so the lifecycle exclusion and the scope rank travel with the lane rather than being reapplied around it, and the substring fallback cannot admit what the indexed path would have excluded.

Sub-query expansion feeds the same array from its own lists. Both decomposition stages — the heuristic one in memory_generate_candidates, which reads a config key seeded to 1 under an AIMEE_MEMORY_DECOMPOSE_HEURISTIC override so a benchmark run can ablate it without writing to the operator's config, and the LLM one in memory_find_facts_scoped_impl — collect each fragment into a private buffer and merge with memory_candidates_merge_interleaved, taking rank 0 from every list before any list's rank 1, on the stated ground that appending whole lists in turn let the first fragment "spend the remaining pool capacity and evict what the later sub-questions found." The lane-membership globals are saved and restored around the fragment passes, because a floor built from a fragment is not the floor the caller asked for. The scope filter runs after the merge, on everything.

Two filters run before rerank. memory_filter_scope drops candidates whose scope does not match, calling memory_scope_matches, which returns 1 when no scope was requested — so the boundary holds only when the caller supplies one. Then the surviving candidate ids are batch-probed through db2_memory_filter_archived_ids, whose predicate is lifecycle_state IN ('archived','superseded') OR activation_suppressed<>0, and any hit is dropped with the reject reason withheld_state. That block sits under a comment stating the rule — "Negative retrieval is unconditional: corrected, archived and explicitly suppressed memories must not re-enter through lexical, dense or graph-fused candidates. memory_get() remains an audit/read-by-id surface." — and it is guarded by nothing but a non-empty candidate array.

Scope and lifecycle reach the SQL as three macros in memory_scope_query.h that are easy to confuse and are composed into one another. DB2_MEMORY_SCOPE_RANK_SQL is an ordering expression — an exactly matching (scope_type, scope_value) 4, active project 3, active workspace 2, shared or global including legacy untagged rows 1, everything else 0 — placed before the caller's own relevance ordering and LIMIT. DB2_MEMORY_SCOPE_FILTER_SQL wraps the same expression as a WHERE predicate, `AND (?101 = 0 OR ?102 = 1 OR (rank)

0), and it is the one doing the excluding. DB2_MEMORY_RECALL_FILTER_SQLwraps *that* in anEXISTSrequiringlifecycle_state='active' AND activation_suppressed=0, under a header comment that fixes the policy in place: *"Normal recall is deliberately stricter than scope-only history/review queries. Lifecycle visibility is not feature-gated: rejected, archived, pending, fulfilled, and superseded rows never enter an answer candidate set."* The recall filter is what the candidate-producing readers use — the Go store's queries in server-go/modules/memory/queries.goanddata.goand the dense path throughpgvec_scope_query.h` — while the bare scope filter is left to the history, review and by-id surfaces that are supposed to see more. A row outside the caller's scope is dropped rather than merely sorted last, and a row outside the active lifecycle never reaches the array the drop above re-checks.

The pair of flags that does exist, memory_lifecycle_enabled and memory_lifecycle_hide_archived, gates memory_list — the un-filtered listing path — and not recall. Both accessors read a config number into a variable initialised to 0 and ignore the read's return code, so an unset key is off, and docs/validation/flag-rollout-readiness.md tracks the pair against its six-point flip gate.

The two escapes in that predicate are worth reading. ?101 = 0 is an inactive scope context and ?102 = 1 is include_all; either short-circuits the check before the rank is evaluated, so an unset context returns everything. That is the opposite default from the RLS block in the same schema, whose comment makes a point of fail-closing on an unset GUC.

7. Write Mechanics

Retraction is where this system has done its hardest thinking, and it did it after being wrong three times. All three corrections are recorded in comments in fact_ingest.c, and they are worth quoting because each is a shape this atlas finds elsewhere without the diagnosis attached.

The first is a master flag:

"It used to sit behind config.typed_facts_enabled, a master gate that defaulted OFF and turned the whole layer — retraction, recall, class keying — into a silent no-op. A gate that silently disables a correctness feature is worse than no feature: this one returned 0 here, so a turn asking to forget a fact completed normally with the fact still standing and nothing logged."

The second is authority ordering. The code used to resolve the actor from the request first and fall back to the declared authority — but the request resolver returns user authority for any authenticated principal, so a model-composed query inside an authenticated human's session inherited the human's rank. The measured consequence is in the comment: a Class A row at authority rank 30 went from persistent to invalidated on the agent's own "please forget my email." The fix inverts the relation, and the comment states it as a rule in capitals: the declared authority caps the actor; it is not a fallback for it.

The third is a discarded return code:

"A REFUSED RETRACTION IS NOT A SUCCESSFUL ONE, and this used to discard the difference … every one of them was thrown away with a (void) cast. The turn then completed normally with the fact still standing, so 'I forgot it' and 'I refused to forget it' looked identical from outside and left nothing in the log."

And the detail that makes it a testing finding rather than a coding one: the failure "presented only as a unit-test count assertion three layers away with no indication that the mutation had been declined at all" — and it succeeded under the SQLite shim while failing under real Postgres. A backend substitution hid a refusal from the only assertion watching for it.

The current code logs -2 (annotate-only target) and -1 (policy needing operator authority, or a failed write) distinctly, and treats neither as an error, on the stated ground that refusals are legitimate outcomes — "What it must not do is stay silent."

A fourth silent failure of the same family sits on the store rather than on the retraction. memory_insert_epistemic_ex copied the caller's content through a fixed char safe_content[2048], so "a 3000- and a 5000-byte value both landed in DB2 as content_len=2047, with nothing logged and success returned" — and the exact-key merge path repeated it through preserved_content[2048], shortening a long row that was merely being re-stored under the same key. The classification went with the bytes: memory_scan_content "only ever saw the first 2047 bytes, so it classified a long note from a fragment," which makes the stored sensitivity label a description of less than the caller asked to keep. Both buffers are sized to the content, and test_long_content_survives_store_and_merge asserts the stored length at 2047, 2048, 4096 and 40,000 bytes on both the store and the merge path — reading length(content) out of Postgres rather than through memory_t, because memory_t.content is itself a fixed char[2048] and "a read back through the struct caps at 2047 no matter what the row holds, and would hide exactly the defect under test." That struct cap is the half still standing: it bounds every working-set read, and db2_kb_service_memory_get_json escapes it only for read-by-id, fetching the content in a second statement carrying the same DB2_MEMORY_SCOPE_FILTER_SQL predicate, on the ground that "a read-by-id response is an audit surface and must return the row verbatim."

Correction is a table, and refusing to admit a value is its own record. memory_rejection_tombstones holds one row per refused value — (source, relation, target) for a typed fact, (memory_key, memory_content, scope_type, scope_value) for an episodic row — each under a partial unique index restricted to active = 1, so a value has at most one live refusal. fm_tombstone_blocks consults it before the mutation seam admits anything, and db2_memory_rejection_blocks is the episodic side's consult.

Four properties make it more than a deny-list.

The database repeats the check the C seam makes. fact_rejection_tombstone_guard on entity_edges and memory_rejection_tombstone_guard on memories are BEFORE INSERT OR UPDATE triggers that raise when a row would become live while a matching active=1 refusal exists. The schema says what they are for — a "Database backstop for writers that do not use the C mutation seam" — and states the failure they close: "A rejected episodic value cannot become recallable merely because another extractor or maintenance script found a different path to the table." A consult in the application is where the good error message lives; a trigger is what holds when the next writer is a migration.

It is reversible, and the reversal is attributed. active, restored_at and restored_by mean a refusal can be lifted by a named actor rather than standing forever, and kb_handle_memory_restore resolves the actor from the request and refuses unless actor.authenticated. Rejection carries a reason and a rejected_by in the same way.

The runtime cannot erase it. A shipped Postgres check refuses the configuration outright when the role that runs the system could destroy the record:

IF has_table_privilege(current_user,'memory_rejection_tombstones','DELETE') OR
   has_table_privilege(current_user,'memory_rejection_tombstones','TRUNCATE') OR
   NOT has_table_privilege(current_user,'memory_rejection_tombstones','SELECT,INSERT,UPDATE') THEN
  RAISE EXCEPTION 'memory tombstone runtime privileges permit erasure or prevent review';

Most tombstones in this corpus are append-only by convention. This one asserts the grant, and the error message names both failure directions — a role that can erase the record, and a role that cannot read it for review.

And the typed-fact seam had the property structurally before the table existed. fm_load_exact looks up an incoming triple without filtering by lifecycle, so a re-assertion finds the invalidated row rather than missing it, and revival is gated on rank:

int reactivate = (strcmp(exact.lifecycle, FACT_LIFECYCLE_INVALIDATED) == 0 ||
                  strcmp(exact.lifecycle, FACT_LIFECYCLE_SUPERSEDED) == 0) &&
                 (int)actor->rank >= exact.authority_rank;

A model-authority extractor cannot raise what a user-authority actor invalidated. When the gate refuses, or when any surviving incumbent outranks the actor on a functional relation, the value lands as a quarantined candidate rather than as a live fact. That is the atlas's rejected-value tombstone written as a lookup that declines to filter — the consultation lives in the chokepoint every assert passes rather than in the extraction path that produces them.

8. Agent Integration

An MCP call table exposes memory operations including memory_provenance, which returns the mutation history of a single memory id to the agent. The CLI mirrors the RPC surface, with aimee memory get <id> --as-of <timestamp> carrying the event-time query. A Go control plane and several compose topologies sit around the two services.

The facts.retract request accepts an authority field, and the boundary that handles it is documented as the only place it may be resolved:

"authority reaches here ALREADY RESOLVED against the caller's authentication by the request boundary that has it … It is not a field a client can set on the way in; do not add a path that forwards a request body's value here unresolved."

At the server edge, a request asking for user authority gets it only when server_account_is_person(account) agrees.

9. Reliability, Safety, and Trust

The audit store is the strongest implementation of this shape in the corpus. audit_worm.c builds a hash chain: each row's row_hash is an HMAC-SHA256 over a length-prefixed injective encoding of the record and the previous hash, a single-writer mutex keeps seq gap-free and totally ordered, and ts is deliberately excluded from the hashed material. Triggers block UPDATE and DELETE. It is 949 lines.

What earns the description is the comment distinguishing the layers: the triggers "are NOT the adversarial guarantee (a process with file write access can drop them) — that is the hash-chain …". Most audit implementations in this corpus assert their own tamper-resistance. This one names which half of it an attacker can remove.

The Postgres side is a queue, not a second chain, and the distinction is worth stating precisely because it is easy to describe wrongly. The KB transaction writes an immutable outbox intent and nothing else; the file that owns the seam says so — "The KB transaction owns only an immutable PostgreSQL outbox intent. The separately credentialed aimee-kb-worm process claims committed intents and appends them through modules/audit/audit_worm.c … No PostgreSQL chain builder lives here." kb_audit_outbox and kb_audit_delivery carry BEFORE UPDATE, BEFORE DELETE and BEFORE TRUNCATE triggers raising 'WORM: % is append-only'.

The provisioning half is a grant argument rather than a chain argument, and schema_grants.sql is stricter than the design note that preceded it. The runtime role is REVOKE ALL on both queue tables and re-granted SELECT only; it holds EXECUTE on kb_audit_worm_submit and kb_audit_worm_pending and is deliberately not granted kb_audit_worm_append, which is REVOKEd from PUBLIC and granted to aimee_kb_owner"kb_audit_worm_append is intentionally NOT granted to runtime (only the owner-run definer mutations call it) so runtime cannot forge audit rows." The wording a reader might expect here — a writer role granted INSERT, SELECT only with REVOKE UPDATE, DELETE — belongs to docs/proposals/done/auditable-worm-audit-store.md:159 and describes an intent, not the shipped grants: the runtime never receives INSERT on an audit table at all, so the shipped provisioning is the stronger of the two and the proposal's phrasing should not be read as a description of it.

Memory mutations reach that queue from a trigger rather than from a call site. evidence_object_mutation, the AFTER INSERT OR UPDATE OR DELETE function behind the ten evidence_* triggers, carries an arm specific to one table: when TG_TABLE_NAME='memories' it calls public.memory_mutation_worm_append(authority, actor, action, id, detail), a SECURITY DEFINER wrapper over kb_audit_worm_append, choosing memory.reject when the lifecycle crossed into rejected and memory.<op> otherwise. Two properties are stated in the comment beside it and are the reason this is worth more than a log line: "Same transaction as the row mutation: a WORM failure aborts the memory mutation. Detail is intentionally content-free." Application code cannot forget to call it, and an audit that cannot be written takes the write down with it. scripts/memory-governance-pg-test.sql asserts the consequence directly, requiring at least five memory.assert / memory.reject / memory.invalidate / memory.restore rows in kb_audit_outbox after the governance flow, with the caveat written down: "The request process proves durable submission here. Chain construction is intentionally asynchronous and is covered by run-worm-worker-pg-test.sh."

A poison gate sits at every boundary where untrusted text becomes prompt context, and the memory write path is one of them. src/headers/integrity.h declares a deterministic Layer 1 pattern gate over six source classes — USER_STATED, WEB, DOCUMENT, TOOL, DELEGATE, AGENT_MESSAGE — returning one of four verdicts: ACCEPT, QUARANTINE, REJECT, REVIEW_NEEDED. The pattern categories are named for what they attack: MEMORY_RESET, IDENTITY_OVERRIDE, AUTHORITY_CLAIM, INSTRUCTION_INJECTION at block severity, and ENCODED_PAYLOAD at warn.

The asymmetry is the design. A block-severity hit rejects when the source is anything but the user, and only quarantines when it is the user — the header states the rule as "never auto-reject user input", which keeps a person able to say a sentence that looks like an attack.

integrity_ingress_decide is the materialization boundary, and it is wired at eight reachable call sites across five named boundaries: document for KB ingest and PDF chunks, retrieval on pre-injection, learning on the learning router, recall in the KB client, and memory through the Go module's content gate (server-go/modules/memory/content_gate.go). The memory sites carry the argument for why memory is treated as hostile by default:

Durable memory becomes future prompt context, so treat it as agent-message authority unless a future typed ingress carries an authenticated user provenance. Ambiguous provenance fails closed.

At the authority-aware site the source is chosen rather than fixed — MEMORY_AUTHORITY_USER maps to INTEGRITY_SOURCE_USER_STATED, everything else to INTEGRITY_SOURCE_AGENT_MESSAGE, with the autonomous flag set only for the non-user case — so a memory the user stated is quarantined where one the agent wrote is rejected. That a gate also runs on recall is the half most systems omit: a value that got in before the gate existed is still checked on the way out.

Retention runs server-side rather than as inline SQL. db2_memory_health calls kb_memory_retention_reap(days) and kb_memory_sensitivity_retention_reap, functions returning the number of rows reaped, rather than issuing a DELETE ... WHERE created_at < pg_now_text(?) from C.

Row-level security reaches the memory tables, and reaches them differently from the membership graph. memories and memory_rejection_tombstones each take ENABLE ROW LEVEL SECURITY and one policy — p_memories_row_scope and p_memory_rejections_row_scope — both USING and WITH CHECK over memory_row_scope_visible(scope_type, scope_value), so the same predicate governs what a query returns and what a write may land. The function admits a row when it is ('global','_global') or ('workspace','_shared'), when the request's aimee.memory_scope_type / _value GUCs match it exactly, when its workspace or project matches the corresponding GUC, or when aimee.memory_scope_all is '1'. The schema states the default it is aiming for: "An unset request context sees only global rows; project/workspace rows fail closed." The tombstone policy exempts object_kind='fact' from the scope test, which is correct — a typed-fact refusal has no scope tuple to test — and means fact refusals are visible to every scope, which is also what a value-keyed refusal wants.

Two things temper it. These two tables take ENABLE without FORCE, unlike the five membership and grant tables, so the table owner is not bound by the predicate and the protection rests on the runtime role being a non-owner. And aimee.memory_scope_all is a session GUC the runtime can set, so the policy bounds a mis-scoped query rather than a compromised process.

Sitting between them is the case a policy cannot express: the two tombstone guard triggers in section 7, which are not scope rules but value rules, and which run on the same tables for the same reason — that the next writer may not be the application.

On aimee-kb, row-level security is enabled and FORCEd on kb_team, kb_project, kb_team_membership, kb_project_membership and kb_admin_grant — the policy data itself. The content policies over kb_documents and kb_file_index exist, use kb_content_project_visible(project), and ship disabled: enabling them is described as an act rather than a migration, to be performed after rows have been attributed to projects. Shipping a control off with the enabling step named is a defensible choice and an unusual one; the consequence for a reader is that project visibility on documents waits on someone turning it on.

10. Tests, Evals, and Benchmarks

test_fact_recall.c is 210 lines and is the best-constructed negative retrieval test this atlas has read. Two user facts are committed, one normal and one PII-sensitive. Then:

int n = db2_fact_recall_block("user", 0, buf, sizeof(buf));
assert(n == 1);
assert(strstr(buf, "works_for: acme") != NULL);
assert(strstr(buf, "age: 30") == NULL);

The positive and the negative are asserted over the same buffer, so a recall returning nothing fails the test instead of passing it. The next block flips the sensitivity request and asserts the PII fact now appears — proving the gate admits as well as withholds. A third case inserts an over-long row and asserts it is skipped rather than truncated into the prompt.

The fourth case is the one to copy. A below-floor row is inserted and asserted absent while its high-confidence neighbours are asserted present, and the comment says why the fixture is shaped that way: "a gate that read one row's confidence for all of them would agree with all of the above." The test is constructed specifically to fail a whole-block implementation that would satisfy every earlier assertion.

docs/validation/flag-rollout-readiness.md deserves its own paragraph. It tracks every default-off flag against a six-point gate for flipping it on, requiring an A/B harness isolating that one flag on a real labelled corpus with numeric acceptance criteria pinned before the run, shadow mode for anything that blocks, and a documented rollback. It records that only two flags have ever been flipped, both via a written validation report. And it contains a ground-truth wiring audit: every default-off flag grepped for production readers excluding config and test files, sorted into WIRED — gating real behaviour, blocked on measurement rather than code — and INERT TOGGLE, where "the *_enabled field is never read in production." The count is published: five inert toggles, no fully-dead features.

That is this atlas's producer check, run by a project on itself, with the negative result written down rather than quietly fixed. The memory_lifecycle_enabled / _hide_archived pair from section 6 appears in that table with no tests recorded against it and a recall-with-archival A/B named as what would clear it — a useful thing for a reader to check against, since the recall path's own exclusion does not depend on that pair.

scripts/memory-governance-pg-test.sql is the other suite worth reading and is the one that drives the tombstone. It runs as aimee_kb_runtime against a real Postgres, asserts the runtime privilege shape on the tombstone table, asserts a project-scoped role sees its own and shared memory rows and not another project's, rejects a row and asserts the re-insert is refused by the trigger, restores it and asserts the retained row becomes recallable without creating a second copy, repeats the refusal test on the typed-fact side, and finishes by counting memory.* rows in the audit outbox. Every assertion drives the production path rather than a harness reimplementation of it, which the project's own proposal names as the discipline that matters: "A harness that reimplements its caller certifies the storage layer and takes the wiring on faith, and the wiring is where these bugs live."

11. For Your Own Build

Five things here transfer.

Put the refusal check in the chokepoint, then repeat it in the database. The C seam consults memory_rejection_tombstones before any assert, and a trigger on the table repeats the test for writers that never go through the seam — a maintenance script, a second extractor, a migration. The two together are worth more than either, because the first is where the good error message lives and the second is what holds when someone finds a different path to the table. The same argument applies to the grant: a refusal the runtime can DELETE is a convention, and asserting the privilege shape at startup is one query.

Make the declared authority a cap, not a default. The bug aimee measured — a model-composed string inheriting an authenticated human's rank because the request context existed — is available to any system that resolves identity from ambient context with a structural label as fallback. Inverting it costs one branch.

Give a refusal a distinct return value and log it. A forget that refuses and a forget that succeeds must not look identical from outside. aimee's version of this bug survived because the only assertion watching it counted rows three layers away, and because a SQLite shim accepted what Postgres refused.

Assert a negative beside a positive on the same buffer. The pattern in test_fact_recall.c costs one extra line per case and removes the entire class of exclusion tests that pass because nothing was returned.

Say which layer of your tamper-resistance an attacker can remove. A hash chain and a trigger are not the same guarantee. Naming the difference in the file is worth more than either.

12. Open Questions

Three, and the first is the honest limit of this reading.

Coverage. This is a tree of over a million lines. The trace here covers the typed-fact layer, the episodic recall path, the audit store and the scope plumbing. It does not cover the Go control plane, the Python tooling, the ingest workers, or the great majority of the 244 tables in the schema. Absence of a mechanism from this report is not evidence of its absence from the tree.

What an auditor does with two ledgers. A memory mutation writes to both, by two different triggers on the same table. memory_evidence_events is the detailed one, written by the ten evidence_* triggers on memories, docs, document_versions, entity_registry, entity_aliases, rel_types, derived_memory_registry, memory_scopes, memory_links and ontology_packages, plus evidence_change_item_event() on the fact-graph commit path; each row carries authenticated_actor, transport_identity, effective_authority, a changeset_id, before and after refs and the source span and hash, under CHECK constraints over a fourteen-value object kind and a twelve-value operation. The WORM chain is the tamper-evident one and gets a content-free row for the same mutation through memory_mutation_worm_append. Putting both producers in triggers is the stronger choice — application code cannot forget to call either — and it means the coverage question is which tables carry the trigger, not which call sites remember. The open part is what an auditor is supposed to reconcile: one ledger holds the detail and is an ordinary table, the other holds the proof and is content-free, and nothing read here joins a changeset_id to a chain seq.

Whether the refusal survives a rephrasing. The mechanism holds against a literal re-assertion and the tests drive it. What it is keyed on is narrower than what entity_edges is keyed on: that table carries identity_key, a normalized (source, relation, target) joined on U+001F after case folding, whitespace collapse and relation normalization, and fm_load_exact prefers it over the literal columns. memory_rejection_tombstones has no such column, so fm_tombstone_blocks and fact_rejection_tombstone_guard both compare the raw triple. An extractor emitting the same claim with a different surface form on the next pass computes a different literal triple and is not refused by the tombstone — though fm_load_exact would still find the dead row by identity_key and apply the rank gate to it, so the two mechanisms disagree about what counts as the same fact. The episodic side is keyed on exact memory_content and has no second mechanism behind it. Adding identity_key to the refusal row, and computing it on both the write and the consult, is the shape that would close the gap; the project's own correction-completeness-and-bounded-reachability.md names the same hole and attributes it to the extractor rather than to an adversary.

Appendix: File Index

Path What it holds
src/modules/db2/c/fact_recall.c Every typed-fact recall query, each opening with the exclusion predicate
src/modules/db2/c/fact_lifecycle.c db2_fact_retract, the immutable-relation guard, the current-count query
src/modules/db2/c/fact_ingest.c Turn-time retraction, and the three corrections quoted in section 7
src/modules/db2/c/fact_mutation.c fm_tombstone_blocks, fm_tombstone_add_assertion, fm_load_exact and the commit seal
src/modules/db2/c/memory_lifecycle.h The five-state episodic vocabulary and db2_memory_valid_at
src/modules/db2/c/memory_scope_query.h The rank expression, the scope filter built on it, and the recall filter built on that
server-go/modules/memory/mutations.go The write path that consults active rejection tombstones and fails closed
server-go/modules/memory/domain.go, queries.go Reject, Restore, ReviewList, the lifecycle filter and the valid-at predicate
server-go/modules/memory/scope.go normalizeScope and the placements that own their scopes
server-go/modules/memory/data.go, fact_recall.go The recall pipeline in Go: scope, lifecycle filter, ValidAt, typed-fact block
src/modules/audit/audit_worm.c The hash-chained append-only store, 949 lines
src/modules/db2/c/kb_audit_worm.c The outbox-intent producer seam, and the note that no Postgres chain builder exists
src/modules/db2/c/schema.sql 244 tables, the memory RLS policies, the two tombstone trigger backstops, the audit outbox and memory_mutation_worm_append
src/modules/db2/c/schema_grants.sql The WORM grant split — runtime submits through a definer and never holds the appender
src/server/server_facts.c The facts.retract boundary and its authority resolution
server-go/modules/memory/fact_recall_test.go The populated recall with the excluded facts named, and the open-bounds validity case
src/tests/test_fact_lifecycle.c The provenance-to-authority table and the tombstoned re-extraction case
src/tests/test_memory_advanced.c The dedupe_merge provenance assertion and the long-content store/merge case
src/tests/test_integration.sh The over-the-wire memory.get --as-of assertions, present and absent
scripts/memory-governance-pg-test.sql The privilege check, both trigger backstops, and the audit-row count
docs/validation/flag-rollout-readiness.md The six-point flip gate and the WIRED / INERT TOGGLE audit

History

2026-09-076083b85e… — re-pinned 79 commits on, still on testing, and the memory module moved languages: server-go/modules/memory/ (9,989 lines, 80 tests, 178 across the Go tree) holds the store, the recall pipeline, the lifecycle filter, the tombstone check, review and restore, ValidAt, scope normalization and placements, while 68 C files under src/modules/memory/ and src/modules/db2/c/ were deleted, among them memory_query.c, memory_score_fields.c, memory_core_search_*.c and memory_core_crud.c; fact_recall.c remains as the ABI to a registered provider and test_fact_recall.c is reduced to that ABI, so the paired exclusion cases this report cited are gone and negative_eval rests on fact_recall_test.go:69-100. Every evidence record is re-pointed and the body's three passages that named deleted files are rewritten; the schema, its row-level security, the rejection-tombstone trigger, the WORM audit store and its triggers, fact_mutation.c and fact_lifecycle.c are unchanged. Seven marks stand. Screened before reading: one auto-run surface (.claude/hooks/), one manifest inside the seven-day cooldown, nothing installed or run.

2026-09-01cccb1490… — re-pinned 15 commits on, and the memory mechanism did not move: the range touches five CI workflows, two release scripts, cli_attention_guard.c, hook_session_token.c, windows/platform_ipc.c and one test, and no file the marks rest on. Seven marks, unchanged. The reading did find a store the previous ones missed entirely — decision_log, described in section 5, which supersedes one decision per subject and sweeps active into revisit_due when a date the author set arrives. Its test carried a date bomb that detonated on the day of this reading: test_record_is_active recorded a fixture with revisit_when of 2026-09-01 while test_revisit_sweep asserts flipped == 1 over the whole table, so once that date was reached the sweep flipped two rows and the count assertion failed; 771baba9 moved the fixture to 2999-01-01, the idiom the sweep test already used for its not-yet-due row. The transition itself stays covered — test_revisit_sweep asserts the due row flips, the future and no-revisit rows do not, and a second sweep flips nothing. Screened again before reading: unchanged in kind — one auto-run surface, one build-time execution surface, three unpinned surfaces, five files inside the seven-day cooldown. Nothing was installed and nothing was built.

2026-09-01a75892e0… — re-pinned 230 commits on, still on testing, 321 files and roughly 13,000 added lines against 3,200 removed. Screened before reading: one auto-run surface (.claude/hooks/, four files, byte-identical to the previous pin and registered by no settings.json), one build-time execution surface (src/Makefile), three unpinned surfaces and five files inside the seven-day cooldown — one more than last time, because the four Go module files and the frontend lockfile all moved four days ago. No new RUNS finding. Nothing was installed, nothing was built and nothing was run. All seven marks re-verified and none moved; every file the marks rest on is byte-identical at this pin except for the line shifts recorded below.

main has caught up with testing. It was 2,719 commits behind at the 26 August pin and is three behind here, having merged testing in PR #2937 on 31 August. origin/HEAD still designates testing, and the pin is on it.

One published claim was wrong in the withholding direction and is corrected: the bitemporal evidence record said the mechanism was "not directly tested at this pin", and src/tests/test_integration.sh has driven memory.get over the real wire with and without as_of since before the previous pin, asserting that the event-time verdict comes back for the first request and is absent from the second. The test's own comment says why it is an integration case rather than a unit one — the flag was once "marshalled, sent, and dropped" between client, aimee-server and aimee-kb while every unit test at each end passed against a hand-written payload that already contained the field. Separately, section 7's reactivate block was fenced as C but was a paraphrase; it now carries the strcmp form the tree actually holds.

The lexical lane changed shape and is described in section 6. db2_memory_find_facts_fts joins memories_fts through rowid and is tried first, with the former unconditional LOWER(...) LIKE '%…%' table scan kept only as a fallback when the indexed query returns nothing; both statements carry DB2_MEMORY_RECALL_FILTER_SQL and DB2_MEMORY_SCOPE_RANK_SQL, so the lifecycle exclusion and the scope rank travel with the lane. Both sub-query decomposition stages now collect each fragment into a private buffer and merge with memory_candidates_merge_interleaved instead of appending whole lists into the shared pool, and memory_filter_scope still runs after the merge on everything. Per-lane candidate and served counters were added and the comment states they are measurement rather than control.

Section 7 gains a fourth silent failure, this one on the store rather than on retraction: memory_insert_epistemic_ex copied content through a fixed char safe_content[2048], and the exact-key merge path through preserved_content[2048], so long values were clipped with success returned and the sensitivity label was computed from the first 2047 bytes. Both buffers are sized to the content and test_long_content_survives_store_and_merge asserts the stored length at 2047, 2048, 4096 and 40,000 bytes on both paths. The read side keeps the cap in memory_t.content; db2_kb_service_memory_get_json escapes it for read-by-id through a second scope-filtered statement.

Six line citations moved with the diff and are re-verified: memory_score_fields.c:626-655,:679, memory_core_crud.c:340, memory_core_search_b.c:1142-1171, memory_core_search_c.c:1003-1040, memory_query.c:1816-1867,:1923-1936 and test_memory_advanced.c:687-699. The schema range for the FORCE ROW LEVEL SECURITY block is tightened to :2660-2672. Counts re-run at this pin and unchanged: audit_worm.c 949 lines, test_fact_recall.c 210, test_content_scope_pg.c 971, 244 CREATE TABLE statements, ten evidence_* triggers, eight reachable integrity_ingress_decide call sites, five inert toggles. Counts that moved: roughly 819,000 lines of C, 89,000 of headers, 149,000 of Go and 113,000 of Python, across 7,831 commits. memory_rejection_tombstones still carries no identity_key, so the open question in section 12 stands. No paper, no CITATION.cff; still AGPL-3.0.

2026-08-31eb86dda7… — same pin, re-read to settle a report that disagreed with itself. The 2026-08-26 entry below promoted tombstone and human_review to seven of seven and the frontmatter carried the promotion; the body did not. Section 1 opened "Five marks", section 12 argued the tombstone mark was withheld because "the consultation that would make a refusal durable was not found", and the matrix.risks field — which the compare page publishes verbatim — led with "Nothing consults the invalidated set before a later extraction re-asserts the same triple." All three are wrong at this pin and in the same direction: fm_tombstone_blocks runs at the head of db2_fact_mutation_assert and again on the review-approve arm, db2_memory_rejection_blocks is the episodic consult, and two database triggers repeat the test underneath both. All three are corrected; the risks field names the real remaining gap, which is that the refusal is keyed on the raw triple while entity_edges is keyed on a normalized identity_key.

Two quotations attributed to the tree were not in it and are removed. "preserved for review" was cited for human_review; the mechanism is real — db2_memory_reject writes the tombstone and sets lifecycle_state='rejected' with confidence=0 while keeping the row — and the record now quotes text that exists, scripts/memory-governance-pg-test.sql's "Rejecting preserves the row for review and installs a value-keyed refusal." "validated: REVOKE UPDATE/DELETE blocks mutation." was cited for audit_log as the Postgres provisioning; the nearest real text is docs/proposals/done/auditable-worm-audit-store.md:159, a design note rather than shipped provisioning, and the shipped grants take a stricter route the report now states — runtime holds SELECT on the queue tables and EXECUTE on the submit definer, and never kb_audit_worm_append. The mark stands and does not rest on that clause: memory mutations reach the chain through memory_mutation_worm_append, called from the evidence_memories trigger in the same transaction as the row change, which also retires the open question in section 12 that said no path from a memory RPC to the WORM store had been traced.

Six factual corrections beside those. audit_worm.c is 949 lines, not 710, in three places. The archived drop before rerank records withheld_state, not lifecycle_archived, in the diagram and the evidence record. That drop, and the lifecycle_state='active' predicate in DB2_MEMORY_RECALL_FILTER_SQL above it, are not behind the memory_lifecycle_enabled / _hide_archived pair — the header says "Lifecycle visibility is not feature-gated" and the flags gate memory_list instead, so section 6, the diagram and the risks field were describing a gate that is not on that path. integrity_ingress_decide has eight reachable call sites, not nine. The schema holds 244 CREATE TABLE statements, the memories row 45 stored columns rather than thirty, test_content_scope_pg.c 971 lines rather than 917, and the evidence_* triggers cover ten tables rather than nine. Section 9's claim that no CREATE POLICY names a memory table was false: p_memories_row_scope and p_memory_rejections_row_scope both exist, USING and WITH CHECK, though under ENABLE without FORCE. The fact_authority_from_provenance block is quoted whole rather than eliding four asserts silently, and several line cites moved.

2026-08-29eb86dda7… — re-pinned 42 commits on, still on testing, 416 files and roughly 76,500 added lines in two days — most of it release blockers, Windows and macOS build work, CI ratchets and a static-analysis baseline. All seven marks re-verified and none moved.

One change reaches the memory path and it is described in section 9: the deterministic poison gate declared in src/headers/integrity.h is now called from memory_core_crud.c at two sites and from memory_advanced.c at a third, joining document, retrieval, learning and recall for eight reachable call sites. The comment at the memory site states the reasoning — durable memory becomes future prompt context, so it is treated as agent-message authority and ambiguous provenance fails closed — and the authority-aware site picks USER_STATED over AGENT_MESSAGE from the memory's own authority, which is the difference between quarantining a user's odd sentence and rejecting an agent's. Retention also moved from inline DELETE statements to the server-side kb_memory_retention_reap and kb_memory_sensitivity_retention_reap functions, each returning a reaped count.

Screened before reading: one auto-run surface (.claude/hooks/), one build-time execution surface, three unpinned surfaces and five files inside the seven-day cooldown; nothing was installed and nothing was run.

2026-08-27bdf19051… — re-pinned 47 commits on, still on testing. Screened again: one auto-run surface, one build-time execution surface, three unpinned surfaces, two files inside the seven-day cooldown; nothing was installed or built. No mark moved.

The change worth recording is a whole class of silent failure closed at once. Removing an AIMEE_DB2_DISABLED fork left sixteen memory files that reach the relational store and "say nothing when it is gone" — and the fork had been the reporting mechanism: "the disabled branch returned 'memory storage unavailable', so deleting it removed the only signal. Empty then becomes indistinguishable from a genuine absence — a search that finds nothing, an entity with no edges, a key with no history all look identical to an outage."

Two details in the repair are the transferable part. The probe "sits on read paths where empty is genuinely ambiguous, not on write paths where 0 frequently means success and an early return would report a failed write as a clean one" — the fix is applied where the ambiguity is, rather than uniformly. And the sentinel is chosen per function against that function's own vocabulary: 0 for the count-returning searches, NULL for context assembly, a plain return for the void refreshes, and -1 for lifecycle counts, the maintenance run and the profile-card build, which already use -1 for failure and "must not report a store outage as a clean maintenance pass or an entity with no observations."

Also in range: every content-carrying KB write is screened, server state moved to Postgres with the SQLite WORM store isolated, and candidate ranking collapsed from one statement per candidate to one statement.

2026-08-266a1b61a9… — re-pinned 231 commits on. The branch needs stating, because its name misleads. origin/HEAD points at testing: it is the repository's default branch and its trunk. main sits 2,719 commits behind it and was last touched on 3 August 2026. The previous pin was already an ancestor of testing, so this is an ordinary re-pin on the same line of development rather than a move to an experimental branch. Screened again before reading: one auto-run surface — a .claude/hooks/ directory that did not exist at the previous pin — one build-time execution surface, three unpinned surfaces and two files inside the seven-day cooldown; nothing was installed and nothing was built.

Two marks added, to seven of seven. Several other systems already carried all seven — memsem, Perseus Vault, Plur1bus, Provem and Verel — so this is the sixth, and the first note written about it here claimed otherwise before the count was run.

tombstone was earnable at the previous pin and was missed. fm_load_exact looks up an incoming triple with no lifecycle predicate, so a re-assertion finds the invalidated row; reactivate then requires actor->rank >= exact.authority_rank, so a model-authority extractor cannot raise what a user-authority actor invalidated, and a refused revival lands as a quarantined candidate. That code is present at 958af1c5. The previous reading searched the offline extraction drain for something that consults an invalidated set, found nothing there, and concluded the property was absent — while the consultation sits in the mutation seam every assert passes, expressed as a lookup that declines to filter. It is the third time a reading of this system has looked in the place named for a mechanism instead of the chokepoint, after the scope filter and the evidence ledger, and the project's own proposal states the property plainly: "the exact-match lookup deliberately does not filter by lifecycle, so a re-assertion finds the dead row, and revival is gated on actor authority rank."

What is genuinely new is the generalisation. memory_rejection_tombstones extends the property to episodic rows, keys each object kind on its own value tuple under a partial unique index on active=1, is consulted by fm_tombstone_blocks before an assert, and carries reason, rejected_by, restored_at and restored_by. A shipped Postgres check refuses to start when the runtime role can DELETE or TRUNCATE it. Section 8 covers it.

human_review rests on the same surface read as a review queue: kb_handle_memory_reject records a reason against a memory the extractor produced and preserves the row for review rather than deleting it; idx_memory_rejection_review orders (active, object_kind, rejected_at DESC); and kb_handle_memory_restore refuses unless db2_fact_actor_from_request returns an authenticated actor, recording restored_by. The verdict is durable and gates what the write path admits next.

Also in range and not mark-bearing: the external vector database subsystem was removed outright, memory vector search routes through DB3, whose wire encoder refuses a relation label longer than relTypeMax rather than truncating it — "a length past the bound is a malformed request, not a long fact" (server-go/modules/memory/memory.go:169) — memory row scope is enforced outside RLS, and the WORM chain writer moved to a sidecar. One correction to the record above rather than to the code: the project's own correction-completeness-and-bounded-reachability.md opens its §1.2 with "No negative retrieval assertion — the substrate has suppression, invalidation, quarantine, erasure, scope filtering and lifecycle-filtered views. Nothing asserts that any of it survives contact with the read path." The negative_eval mark here rests on test_fact_recall.c, whose paired cases assert a below-floor and a PII-gated row are absent from the rendered block; that is a real read-path assertion and narrower than the coverage the proposal says is missing. Both statements are true and the proposal's is the more demanding one.

2026-08-25 (same-day correction) — two errors in the reading above were found and fixed while checking a second source against the same pin. The report had described DB2_MEMORY_SCOPE_RANK_SQL as the SQL-side scope mechanism and concluded it "excludes nothing"; that is true of the rank macro and wrong about the system, because DB2_MEMORY_SCOPE_FILTER_SQL wraps the same expression as a WHERE predicate and both are applied together in memory_briefing.c, memory_relations.c and pgvec_transport.c. Scope on memory rows is a filter, not only an ordering. The report also carried an open question asking whether memory mutations are audited at all, framed around the WORM audit_event store; they are, through a different ledger — memory_evidence_events, written by AFTER INSERT OR UPDATE OR DELETE triggers on nine tables including memories. Section 6, section 12 and the scoping row are corrected. The marks are unchanged.

2026-08-25958af1c5… — first reading, roughly 796,000 lines of C and 87,000 of headers plus 142,000 of Go and 109,000 of Python, 7,281 commits since 3 June 2026, AGPL-3.0. Screened before anything was read: one auto-run surface, one build-time execution, three unpinned surfaces and two files inside the seven-day cooldown; nothing was installed, nothing was compiled and no service was started, so every claim here comes from reading the tree. Five marks. trust_state rests on two discrete vocabularies held apart from the confidence floats beside them, with the typed-fact exclusion applied by every recall query behind no flag. bitemporal rests on valid_from/valid_until held separately from created_at/updated_at and read by db2_memory_valid_at, reachable as --as-of. scope_enforced rests on memory_filter_scope dropping candidates before rerank, with row-level security forced on the membership tables beside it. audit_log rests on the hash-chained WORM store and the per-memory memory_provenance rows; the path from a memory RPC to audit_worm_append was not traced, and section 12 says so. negative_eval rests on four exclusion cases in test_fact_recall.c, each paired with a positive over the same buffer. tombstone is withheld on one missing consultation: retraction retains the row and keys on the triple, which is the right key, but nothing in the offline extraction drain reads the invalidated set before asserting. human_review is absent — the pending state expires on a TTL sweep rather than a decision, and memory_conflicts.resolution is closed by the agent under a directive. This is the largest checkout in the corpus; the reading covers the typed-fact layer, the episodic recall path, the audit store and the scope plumbing, and not the Go control plane, the Python tooling, the ingest workers, or most of the 243 tables in the schema.