# v0.7.0 Epic — `attested-cortex`

> **One sentence:** v0.7.0 rolls the v0.6.5 cortex-fluent legibility work together with ROADMAP2 §7.3's full v0.7 trust + A2A maturity scope into a single release — the substrate becomes both **more articulate** (loaders that say "load," capabilities that pre-compute their own description, schemas at half the token cost) and **cryptographically trustworthy** (Ed25519 attestation, sidechain transcripts, hook pipeline, namespace inheritance enforcement, A2A maturity).

**Status:** PLANNED · 2026-05-05
**Supersedes:** [v0.6.5 epic (`cortex-fluent`)](../v0.6.5/V0.6.5-EPIC.md) — rolled into this document
**Predecessor:** [v0.6.4 — `quiet-tools`](../v0.6.4/V0.6.4-EPIC.md) — shipped 2026-05-05
**Original v0.7 spec:** [ROADMAP2.md §7.3](../../ROADMAP2.md) (Q2 2026, June target — now consolidating into `attested-cortex`)
**Tracking issues:** [#545](https://github.com/alphaonedev/ai-memory-mcp/issues/545) · [#546](https://github.com/alphaonedev/ai-memory-mcp/issues/546) · [#512](https://github.com/alphaonedev/ai-memory-mcp/issues/512) · plus all G/H/I/J/K issues to be filed at kickoff

---

## Why this release exists

Two narratives converge in v0.7.0:

**Narrative 1 — `cortex-fluent` (legibility) — from v0.6.5 epic:**
The 2026-05-05 NHI Discovery Gate verdict on v0.6.4 came back **6/6 PASS, GATE GREEN**, but three real-world LLM observation cells captured the same day showed a *legibility* gap — reasoning-class LLMs (Grok 4.2 reasoning) didn't find the runtime loader because it's hidden inside an introspection tool's parameter set. The substrate did its job; the language hadn't quite caught up.

**Narrative 2 — `attested` (trust) — from ROADMAP2 §7.3:**
The v0.6.3 release left Ed25519 signature columns in `memory_links` that nothing populates ("dead column" — flagged in §5.3 audit). Hook events are advertised via subscription system but lifecycle hooks aren't programmable. Permissions are advisory, not enforced. Namespace inheritance is display-only — leaf-policy gating means a parent `Approve` rule doesn't actually block a child write. A2A correlation, ACK retries, replay protection are all known gaps.

v0.7.0 closes both at once. The substrate becomes the thing it has been *describing itself as*: a self-documenting, attested, programmable, cryptographically trustworthy memory cortex that an NHI fleet can ground its identity in.

---

## Goals (priority-ordered)

### Mandatory cutline (per ROADMAP2 §7.3)
1. **G1 — Namespace inheritance enforcement.** *"Even if everything else slips, this fix ships."* `resolve_governance_policy` walks the namespace chain; first non-null policy wins.

### Required for `cortex-fluent` narrative (from v0.6.5 epic)
2. Loader tools (`memory_load_family`, `memory_smart_load(intent)`) get names that say "load." Reasoning-class LLMs find them on first ask.
3. `memory_capabilities` v3 carries `summary`, `to_describe_to_user`, `callable_now`, `agent_permitted_families` — pre-computed calibration.
4. `--profile full` drops to ≤ 3,500 input tokens via schema compaction.

### Required for `attested` narrative (from ROADMAP2 §7.3)
5. Hook pipeline ships: 20 lifecycle events, JSON-stdio + daemon-mode IPC, decision types, chain ordering, hot reload.
6. Ed25519 attestation: per-agent keypair, outbound signing, inbound verification, `attest_level` enum, append-only `signed_events` audit table.
7. Sidechain transcripts: zstd-compressed BLOBs, per-namespace TTL, `memory_replay`, R5 auto-extraction substrate.
8. Apache AGE acceleration: AGE detected in Postgres SAL, Cypher implementations of KG ops, dual-path tests, R2 `memory_find_paths`.
9. A2A maturity: correlation IDs, ACKs, retry, TTL, replay protection. Subscription reliability (DLQ, replay-from-cursor, HMAC). Per-agent quotas. Permission system with deny-first semantics. Approval API (HTTP + SSE + MCP, HMAC, `remember=forever`).

### Strongly recommended (validation layer)
10. Discovery Gate T0 calibration cells re-run after ship; ≥95% convergence across 4 LLMs.
11. Cross-harness benchmark + per-harness positioning updates.

## Non-goals

- **R4** (curator CLI surface) → v0.8 Pillar 2.5
- **R6** (consensus memory truth-determination) → v0.8 Pillar 3
- **R8** (TOON v2 schema inference) → v0.9 or formally cut
- **Per-agent profile pre-warm** (NHI guardrails phase 2) → v0.7.1 or v0.8
- **Hardware-backed key storage** (TPM/HSM/Secure Enclave) → AgenticMem commercial layer, not OSS
- **Pool-of-N reranker batching** → v0.9 alongside default-on rerank
- **Long-term per-namespace HNSW shard / sqlite-vec migration** → v0.9
- **Removing v0.6.4 capabilities v2** — backward compat preserved; v3 is additive

---

## Workstream summary

| Track | Scope | Tasks | Effort | Required? | Source |
|---|---|---|---|---|---|
| **A** — Capabilities v3 response shape | 5 | ~1 wk | 🔴 REQUIRED | v0.6.5 #545 |
| **B** — Loader tools (`memory_load_family`, `memory_smart_load`) | 5 | ~2-3 wk | 🔴 REQUIRED | v0.6.5 #546 |
| **C** — Schema compaction | 5 | ~1 wk | 🟢 STRONGLY REC | v0.6.5 #546 |
| **D** — Per-harness positioning + tests | 4 | ~3-5 d | 🟢 STRONGLY REC | v0.6.5 |
| **E** — Discovery Gate T0 cells | 3 | ~3 d | 🟢 STRONGLY REC | v0.6.5 |
| **G** — Hook Pipeline (Bucket 0) | 11 | ~3-4 wk | 🔴 REQUIRED | ROADMAP2 §7.3 Bucket 0 |
| **H** — Ed25519 Attested Identity (Bucket 1) | 6 | ~2 wk | 🔴 REQUIRED | ROADMAP2 §7.3 Bucket 1 |
| **I** — Sidechain Transcripts (Bucket 1.7) | 5 | ~2 wk | 🔴 REQUIRED | ROADMAP2 §7.3 Bucket 1.7 |
| **J** — Apache AGE Acceleration (Bucket 2) | 8 | ~3 wk | 🔴 REQUIRED | ROADMAP2 §7.3 Bucket 2 |
| **K** — A2A + Permissions + G1 cutline (Bucket 3) | 11 | ~3-4 wk | 🔴 REQUIRED (G1 mandatory) | ROADMAP2 §7.3 Bucket 3 |
| **F** — Docs + release | 6 | ~3-5 d | 🔴 REQUIRED (every release) | v0.6.5 + this rollup |

**Total:** **69 tasks** across **11 tracks**.

**Sequential effort:** ~17-20 weeks (~4-5 months). **With moderate parallelism (2-3 contributors):** ~10-12 weeks. **Highly parallel (5+):** ~6-8 weeks.

The original v0.7 ROADMAP2.md target was Q2 2026 (June). Adding the v0.6.5 cortex-fluent rollup pushes that to roughly Q3 2026 (July-August) at the same staffing. Cuts:
- Defer Track C (compaction) to v0.7.1 → −1 week
- Defer Track D, E to v0.7.1 → −1 week
- Defer Track J (AGE acceleration) to v0.7.1 → −3 weeks (preserves the SQLite CTE path; AGE becomes opt-in next release)
- Defer Track I (sidechain transcripts) to v0.7.1 → −2 weeks (Bucket 0 hook pipeline still ships; R5 just slips to v0.7.1)

Most aggressive cut path: ship Tracks A + B + G + H + K (core legibility + hooks + attestation + G1 cutline + permissions) in **~8-10 weeks**. That's the *real* v0.7.0 minimum. Everything else slips to v0.7.1.

---

## How to use the NHI starter prompts

Same conventions as the v0.6.5 epic — every task below has a self-contained NHI starter prompt that any agent (Claude Code, Grok, Codex, etc.) can take to begin work without further briefing.

**Branch naming:** `feat/v0.7-<track>-<task>-<short-name>` (e.g., `feat/v0.7-h-2-link-signing`)

**Commit convention:** `feat(<scope>): <imperative summary> (#<task-id>)`

**PR title:** `feat: v0.7-<track><number> — <one-line title>`

**Definition of done:** explicit per task, agent self-checks against the listed criteria.

---

## Track A — Capabilities v3 response shape

> **Track goal:** `memory_capabilities` v3 response pre-computes the calibration work LLMs do today. Agents converge on accurate first-answer descriptions regardless of calibration bias.

*This track is unchanged from the v0.6.5 epic. Five tasks A1-A5. See [v0.6.5 epic Track A](../v0.6.5/V0.6.5-EPIC.md#track-a--substrate-response-shape) for full details + starter prompts. Summary:*

- **A1** — top-level `summary` field
- **A2** — `to_describe_to_user` field
- **A3** — per-tool `callable_now: bool`
- **A4** — `agent_permitted_families` (when allowlist applies)
- **A5** — schema bumped to v3, v2 backward compat

---

## Track B — Loader tools

> **Track goal:** the loader of last resort gets a name that says "load." Reasoning-class LLMs find it on first ask. Path 1's reliability stops being curiosity-gated.

*This track is unchanged from the v0.6.5 epic. Five tasks B1-B5. See [v0.6.5 epic Track B](../v0.6.5/V0.6.5-EPIC.md#track-b--loader-tools) for full details + starter prompts. Summary:*

- **B1** — `memory_load_family(family)` tool in always-on
- **B2** — `memory_smart_load(intent)` tool with embedding-based intent matching
- **B3** — pre-compute family-descriptor embeddings at build time
- **B4** — detect harness from MCP `clientInfo`; surface `your_harness_supports_deferred_registration`
- **B5** — update `memory_capabilities` description to point at the new loaders

---

## Track C — Schema compaction

> **Track goal:** `--profile full` drops from ~6,200 input tokens to ≤3,500. Every harness benefits.

*This track is unchanged from the v0.6.5 epic. Five tasks C1-C5. Summary:*

- **C1** — audit all 43 tool descriptions; identify verbosity hotspots
- **C2** — move docstrings to `docs` field included only with `verbose=true`
- **C3** — drop redundant inline examples
- **C4** — optional params not advertised by default
- **C5** — CI gate enforces full-profile ≤ 3,500 tokens

---

## Track D — Per-harness positioning + tests

*Unchanged from v0.6.5 epic. Tasks D1-D4: cross-harness benchmark, landing-page compatibility-matrix update, install-time system-prompt snippet, harness integration tests.*

---

## Track E — Discovery Gate T0 cells

*Unchanged from v0.6.5 epic. Tasks E1-E3: orchestrate T0 cells across 4 LLMs, post-ship convergence verification, loader-cell additions to T1-T3 tiers.*

---

## Track G — Hook Pipeline (Bucket 0)

> **Track goal:** programmable lifecycle events at every memory operation point. Subprocess JSON-over-stdio with daemon-mode IPC for hot paths. The substrate that R3 (auto-link inference) and R5 (auto-extraction) depend on, plus a permanent extension surface for downstream automation.

### Task G1: Hook config schema (`hooks.toml`)

**File(s):** `src/hooks/config.rs` (new), `~/.config/ai-memory/hooks.toml` (config path)
**Branch:** `feat/v0.7-g-1-hooks-config`

**Deliverable:** TOML schema for hook configuration, parsed at boot, hot-reloadable on SIGHUP. Each hook entry: `event` (enum), `command` (path), `priority` (i32), `timeout_ms` (u32), `mode` (`exec` | `daemon`), `enabled` (bool).

**Schema example:**
```toml
[[hook]]
event = "post_store"
command = "/usr/local/bin/auto-link-detector"
priority = 100
timeout_ms = 5000
mode = "daemon"
enabled = true
namespace = "team/*"   # optional pattern; default = "*"
```

#### NHI starter prompt for G1

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G1.

Goal: define the hooks.toml schema and config-loading pipeline. This is
the substrate every other Track G task depends on.

What to do:
1. Create src/hooks/config.rs with:
   - struct HookConfig { event, command, priority, timeout_ms, mode, enabled, namespace }
   - enum HookEvent { 20 variants — see G2 task for the full list }
   - enum HookMode { Exec, Daemon }
   - impl HookConfig::load_from_file(path) -> Result<Vec<HookConfig>>
2. Default config path: ~/.config/ai-memory/hooks.toml (use dirs crate).
3. SIGHUP triggers reload; in-flight hook executions complete on old config.
4. Validation: priority is i32, timeout_ms is u32 ≤ 30_000, namespace is
   a glob pattern that matches src/namespace.rs::matches_pattern.
5. Parse-time errors must include line numbers from the toml::Spanned API.
6. Add unit tests covering valid/invalid configs.

Branch: feat/v0.7-g-1-hooks-config
Commit: feat(hooks): hooks.toml config schema with hot reload (#<filed>)

Definition of done:
- HookConfig parses canonical examples
- SIGHUP reload tested
- Invalid config produces line-number errors
- Hot reload doesn't kill in-flight hook executions
```

---

### Task G2: 20 hook event types

**File(s):** `src/hooks/events.rs` (new)
**Branch:** `feat/v0.7-g-2-hook-events`

**Deliverable:** Define the 20 lifecycle events the hook pipeline supports. Each event has a payload type and a documented invocation point.

**Event matrix (16 base + 2 compaction + 2 transcripts):**

| Event | When fired | Payload |
|---|---|---|
| `pre_store` | before persist | `MemoryDelta` (writable) |
| `post_store` | after persist | `Memory` (read) |
| `pre_recall` | before recall query | `RecallQuery` (writable) |
| `post_recall` | after recall returns | `RecallResult` (read) |
| `pre_search` | before FTS query | `SearchQuery` (writable) |
| `post_search` | after FTS returns | `SearchResult` (read) |
| `pre_delete` | before delete | `MemoryRef` (writable) |
| `post_delete` | after delete | `MemoryRef` (read) |
| `pre_promote` | before tier promotion | `MemoryRef` + new tier |
| `post_promote` | after promotion | `MemoryRef` + tier change |
| `pre_link` | before link creation | `LinkDelta` (writable) |
| `post_link` | after link creation | `Link` (read) |
| `pre_consolidate` | before consolidation | `ConsolidationDelta` |
| `post_consolidate` | after consolidation | `ConsolidationResult` |
| `pre_governance_decision` | before gate decision | `GovernanceContext` |
| `post_governance_decision` | after gate decision | `GovernanceDecision` |
| `on_index_eviction` | (G2 audit) ANN eviction | `EvictionEvent` |
| `pre_archive` | before archive | `MemoryRef` |
| `pre_transcript_store` | (1.7) before transcript store | `TranscriptDelta` |
| `post_transcript_store` | (1.7) after transcript store | `Transcript` (read) |

#### NHI starter prompt for G2

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G2.
Depends on: G1 (hook config exists).

Goal: define the 20 lifecycle event types and their payloads.

What to do:
1. Create src/hooks/events.rs.
2. enum HookEvent — 20 variants per the table in V0.7-EPIC Track G2.
3. For each event, define the payload struct. Pre- events have writable
   deltas; post- events have read-only payloads.
4. Implement Serialize for each payload (JSON for stdio IPC).
5. Document each event's invocation point with a doc comment that names
   the source-code location (file:line) where it will fire.
6. Add unit tests asserting each variant round-trips through JSON.
7. Update CHANGELOG with the full enum for the v0.7.0 release notes.

Branch: feat/v0.7-g-2-hook-events
Commit: feat(hooks): 20 lifecycle event types with payloads (#<filed>)

Definition of done:
- 20 variants present
- All payloads serialize to JSON
- Each event documented with invocation file:line
```

---

### Task G3: Hook executor (subprocess JSON-stdio + daemon mode)

**File(s):** `src/hooks/executor.rs` (new)
**Branch:** `feat/v0.7-g-3-hook-executor`

**Deliverable:** Two execution modes:
1. **`exec`** — subprocess per fire; JSON over stdin → stdout; clean shutdown on close.
2. **`daemon`** — long-lived child process; JSON-RPC framed; reconnection on crash; backpressure.

**Hot-path constraint:** `post_recall` and `post_search` default to `daemon` mode (preserves the v0.6.3 50ms recall budget). `mode = "exec"` requires explicit override.

#### NHI starter prompt for G3

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G3.
Depends on: G1, G2.

Goal: build the hook executor. Two modes: exec (subprocess per fire)
and daemon (long-lived child with JSON-RPC framing).

What to do:
1. Create src/hooks/executor.rs.
2. trait HookExecutor { fn fire(event: HookEvent, payload: serde_json::Value)
   -> Result<HookDecision>; }
3. struct ExecExecutor — uses tokio::process::Command per fire. Stdin =
   JSON payload. Stdout = JSON decision. Clean shutdown on stdin close.
4. struct DaemonExecutor — long-lived child process. Connection pool of
   1 per hook command. JSON-RPC framing (newline-delimited or length-prefixed
   — pick one; document choice). Reconnect-on-crash with exponential backoff.
   Backpressure: if child can't keep up, queue drains to deadline; oldest
   first dropped with warning log.
5. ExecutorRegistry — maps HookConfig → HookExecutor instance.
6. Integration tests with a fake subprocess (in tests/hooks_executor_test.rs):
   - exec mode: 100 concurrent fires complete within their timeout
   - daemon mode: 1000 fires through one daemon child complete within
     deadline; child crash mid-stream triggers reconnect
7. Add backpressure metrics to ai-memory doctor --tokens (new --hooks
   subcommand): events fired, events dropped, mean latency.

Branch: feat/v0.7-g-3-hook-executor
Commit: feat(hooks): subprocess executor with exec + daemon modes (#<filed>)

Definition of done:
- Both modes pass integration tests
- 50ms recall budget preserved when post_recall is daemon-mode
- Child crash recovery tested
- Doctor reports executor metrics
```

---

### Task G4: Decision types (`Allow` / `Modify(MemoryDelta)` / `Deny` / `AskUser`)

**File(s):** `src/hooks/decision.rs` (new)
**Branch:** `feat/v0.7-g-4-decision-types`

**Deliverable:** Hook subprocess returns one of four decision shapes; hook chain processes them.

```rust
enum HookDecision {
    Allow,
    Modify(MemoryDelta),  // pre- events only
    Deny { reason: String, code: i32 },
    AskUser { prompt: String, options: Vec<String>, default: Option<String> },
}
```

#### NHI starter prompt for G4

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G4.
Depends on: G2 (HookEvent + payload types).

Goal: define HookDecision and its serialization contract.

What to do:
1. Create src/hooks/decision.rs.
2. enum HookDecision { Allow, Modify(...), Deny{reason, code}, AskUser{...} }.
3. Modify is only valid on pre- events (compile-time guard via separate types
   or runtime validation in the dispatcher).
4. Document the JSON serialization at the top of the file:
   {"action": "allow"} | {"action": "modify", "delta": {...}}
   {"action": "deny", "reason": "...", "code": 403}
   {"action": "ask_user", "prompt": "...", "options": [...], "default": "..."}
5. Implement Deserialize with strict validation — unknown actions or
   missing required fields produce a parse error that the executor
   surfaces as a "hook returned malformed decision" warning.
6. Unit tests covering each variant + invalid payloads.

Branch: feat/v0.7-g-4-decision-types
Commit: feat(hooks): decision type contract (#<filed>)

Definition of done:
- 4 variants present and serialized correctly
- Invalid payloads rejected with named errors
- Modify validated against pre-event-only constraint
```

---

### Task G5: Chain ordering + first-deny-wins short-circuit

**File(s):** `src/hooks/chain.rs` (new)
**Branch:** `feat/v0.7-g-5-chain-ordering`

**Deliverable:** When multiple hooks fire on the same event, they execute in priority order (descending). First `Deny` short-circuits the chain. `Modify` accumulates (later hooks see the latest delta). `AskUser` is queued; first non-`AskUser` decision continues.

#### NHI starter prompt for G5

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G5.
Depends on: G3, G4.

Goal: chain multiple hooks on the same event; resolve conflicts.

What to do:
1. Create src/hooks/chain.rs.
2. struct HookChain { hooks: Vec<HookConfig> } — sorted by priority desc.
3. impl HookChain::fire(event, payload) -> ChainResult:
   - iterate hooks in priority order
   - call executor.fire() for each
   - if Deny: short-circuit, return Deny
   - if Modify: update payload with delta, continue chain
   - if AskUser: queue prompt, continue chain (first non-AskUser
     decision wins)
   - if Allow: continue chain
4. ChainResult enum: Allow, ModifiedAllow(payload), Deny, AskUser(queue).
5. Unit tests:
   - 3 hooks priority 100/50/0; first denies → chain stops at first
   - 3 hooks all allow with modifications → final payload is composition
   - Hook crashes mid-chain → log warning, continue chain, treat crash
     as Allow (fail-open). Configurable per hook with fail_mode = "open"|"closed".
6. Integration with the existing dispatch_event path in src/subscriptions.rs
   — hooks fire BEFORE subscriptions for pre- events, AFTER for post-.

Branch: feat/v0.7-g-5-chain-ordering
Commit: feat(hooks): chain ordering + first-deny-wins (#<filed>)

Definition of done:
- Priority-desc ordering tested
- Modify-then-allow composition tested
- Crash fallback tested (default fail-open)
- Subscription integration verified
```

---

### Task G6: Hard timeouts per event class

**File(s):** `src/hooks/timeouts.rs` (new)
**Branch:** `feat/v0.7-g-6-event-class-timeouts`

**Deliverable:** Per-event-class deadlines: 5000ms for write events (`pre_store`, `pre_link`, etc.), 2000ms for read events (`pre_recall`, `pre_search`). Per-hook `timeout_ms` cannot exceed its event class's class deadline. Total chain runtime is bounded.

#### NHI starter prompt for G6

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G6.
Depends on: G2, G5.

Goal: enforce hard per-event-class timeouts so hooks cannot collectively
break the 50ms-recall p95 budget.

What to do:
1. Create src/hooks/timeouts.rs.
2. fn event_class(event: HookEvent) -> EventClass { Write, Read, Index, Transcript }
3. const CLASS_DEADLINES: HashMap<EventClass, Duration> — 5000ms write,
   2000ms read, 1000ms index, 5000ms transcript.
4. At HookChain::fire entry, compute deadline = now + class_deadline.
   Each hook gets min(class_remaining, hook_timeout_ms).
5. If a hook exceeds its budget: kill the subprocess (or close the daemon
   stream, depending on mode), log warning, treat as fail-open Allow.
6. CI test: a deliberately-slow hook chain on post_recall must not push
   total recall p95 above the existing 50ms budget. Run the bench suite.
7. Doctor reports deadline-violation count alongside other hook metrics.

Branch: feat/v0.7-g-6-event-class-timeouts
Commit: feat(hooks): per-event-class timeout enforcement (#<filed>)

Definition of done:
- Class deadlines hardcoded per the table
- Per-hook timeout cannot exceed class
- Recall-budget bench test stays green with hooks active
- Doctor reports timeout-violation counts
```

---

### Task G7: Hot reload via SIGHUP

*See G1 (already covers SIGHUP reload). This is a verification task.*

**Branch:** `feat/v0.7-g-7-hot-reload-test`

**Deliverable:** Integration test that fires SIGHUP, replaces hooks.toml, asserts new hooks fire on next event without server restart.

#### NHI starter prompt for G7

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G7.
Depends on: G1.

Goal: verify hot reload works end-to-end.

What to do:
1. tests/hooks_hot_reload.rs — start ai-memory in test mode with
   hooks.toml = "config A".
2. Fire a memory_store, assert hook A's command was invoked.
3. Replace hooks.toml on disk with "config B" (different hook command).
4. Send SIGHUP.
5. Wait for reload signal (or poll for ≤500ms).
6. Fire a memory_store, assert hook B's command was invoked (not A).
7. In-flight hook from before SIGHUP must complete on old config.

Branch: feat/v0.7-g-7-hot-reload-test
Commit: test(hooks): hot reload integration test (#<filed>)
```

---

### Task G8: G2 audit absorption — `on_index_eviction` event

**File(s):** `src/storage/embedding_index.rs`, `src/hooks/events.rs`
**Branch:** `feat/v0.7-g-8-eviction-event`

**Deliverable:** When the ANN index evicts (LRU or capacity), fire `on_index_eviction` with the evicted memory_id. Surface eviction count in `ai-memory stats`.

---

### Task G9: G7 audit absorption — reranker batching (Mutex throughput)

**File(s):** `src/reranker.rs`
**Branch:** `feat/v0.7-g-9-reranker-batching`

**Deliverable:** Concurrent rerank requests group into a single forward pass over the union of inputs; demux on output. Closes the v0.6.3 audit's G7 finding (single Mutex serializing rerank calls).

#### NHI starter prompt for G9

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G9.

Goal: close the G7 audit finding (Mutex throughput on reranker).

What to do:
1. Read src/reranker.rs and locate the existing Mutex-protected single-
   request forward pass.
2. Replace with a batching layer:
   - Incoming requests join a queue; each request gets a oneshot::Sender
     for its result.
   - A worker drains the queue every 5ms (configurable) OR when N requests
     are queued (configurable; default 8).
   - Worker runs a single forward pass over the union of all queued
     query+candidate sets.
   - Worker demuxes results back to each oneshot::Sender.
3. Bench: 100 concurrent rerank requests should complete in 2× the time
   of a single request (not 100× as today's Mutex serialization implies).
4. Add a CI bench test that asserts batched throughput ≥10× single-request
   throughput at concurrency=20.

Branch: feat/v0.7-g-9-reranker-batching
Commit: perf(reranker): batched single-pass throughput (#<filed>)

Definition of done:
- Mutex serialization removed
- Throughput bench test green at ≥10× speedup
- Single-request latency unchanged or improved
```

---

### Task G10: G10 audit absorption — `pre_recall` daemon-mode hook for query expansion

**File(s):** `src/hooks/events.rs`, `src/recall.rs`
**Branch:** `feat/v0.7-g-10-pre-recall-expand`

**Deliverable:** `memory_expand_query` (advertised as a planned tool, currently MIA) becomes pipeable through the new `pre_recall` daemon-mode hook. Doesn't violate "zero tokens until recall" — the expansion is server-side and never advertised in `tools/list`.

---

### Task G11: R3 — Auto-link inference as `post_store` daemon-mode hook

**File(s):** `tools/auto-link-detector/` (new utility), `docs/hooks/auto-link.md` (new)
**Branch:** `feat/v0.7-g-11-auto-link-r3`

**Deliverable:** A reference `post_store` hook implementation that examines the just-stored memory + recent neighbors, proposes `related_to` / `contradicts` links to a downstream LLM scoring step, persists high-confidence links via `memory_link`. Default off; opt-in per namespace.

#### NHI starter prompt for G11

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G11 (R3).
Depends on: G1, G2, G3 (hook pipeline).

Goal: ship a reference auto-link detector hook. Recovers commitment R3
from the prior phased roadmap.

What to do:
1. Create tools/auto-link-detector/ as a separate Rust binary with its
   own Cargo.toml.
2. The binary reads JSON HookEvent::PostStore from stdin (daemon mode):
   - Receives the just-stored Memory.
   - Queries ai-memory for neighbors via memory_recall (uses a thin
     ai-memory SDK Rust client).
   - Embeds the new memory + each neighbor; computes cosine.
   - Heuristic: if cosine > 0.85 and neighbor is in same namespace → propose
     "related_to" link.
   - Heuristic: if cosine in (0.6, 0.85) but content has obvious negation
     ("not", "won't", "doesn't") aligned with neighbor positive form →
     propose "contradicts" link.
   - For each proposal: emit memory_link(...) call back through HookDecision::
     Modify (so the chain can persist it transactionally with the original
     store).
3. Emit metrics: proposals_emitted, links_persisted, conflicts_detected.
4. Default off — example hooks.toml entry shipped in
   docs/hooks/auto-link.md as opt-in instructions.
5. Integration test in tests/hooks_auto_link.rs that runs ai-memory with
   the hook enabled, stores a contradiction pair, asserts the link is
   created.

Branch: feat/v0.7-g-11-auto-link-r3
Commit: feat(hooks): R3 auto-link detector reference hook (#<filed>)

Definition of done:
- Reference binary builds + runs as daemon-mode hook
- Heuristic produces correct link types in test corpus
- Default off; opt-in documented
- Per-namespace opt-in tested
```

---

## Track H — Ed25519 Attested Identity (Bucket 1)

> **Track goal:** fill the v0.6.3 dead `signature` column with real cryptographic attestation. Per-agent Ed25519 keypair (operator-supplied, explicit; not derived from agent_id). Outbound signing on every `memory_links` write. Inbound verification against `observed_by` claim. `attest_level` enum: `unsigned` / `self-signed` / `peer-attested`. Append-only `signed_events` audit table.

### Task H1: Per-agent Ed25519 keypair management

**File(s):** `src/identity/keypair.rs` (new), CLI: `ai-memory identity generate`, `ai-memory identity import`, `ai-memory identity list`
**Branch:** `feat/v0.7-h-1-agent-keypair`

**Deliverable:** CLI to generate, import, list per-agent Ed25519 keypairs. Keys stored at `~/.config/ai-memory/keys/<agent_id>.{pub,priv}`. Private keys mode 0600; public keys mode 0644. Operator-supplied — not derived from `agent_id`.

#### NHI starter prompt for H1

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task H1.

Goal: introduce per-agent Ed25519 identity. CLI surface to generate /
import / list. File-based key storage with strict permissions.

What to do:
1. Create src/identity/keypair.rs:
   - struct AgentKeypair { agent_id, public, private (Option) }
   - fn generate(agent_id: &str) -> AgentKeypair (uses ed25519-dalek)
   - fn save(keypair, dir) -> Result<()>; mode 0600 priv, 0644 pub
   - fn load(agent_id, dir) -> Result<AgentKeypair>
   - fn list(dir) -> Result<Vec<AgentKeypair>>; private key NOT loaded
2. CLI subcommand: src/cli/identity.rs
   - ai-memory identity generate --agent-id <id>
   - ai-memory identity import --agent-id <id> --pub <path> --priv <path>
   - ai-memory identity list
   - ai-memory identity export-pub --agent-id <id>  (for sharing)
3. Default key storage: ~/.config/ai-memory/keys/
4. Integration with NHI agent_id: the CLI default agent_id matches the
   NHI-hardened default (ai:<client>@<host>:pid-<pid> etc.).
5. Test: round-trip a keypair (generate → save → load), permissions
   verified.
6. Hardware-backed storage is OUT of OSS scope per ROADMAP2.md; just
   doc-comment this clearly and point at AgenticMem commercial layer.

Branch: feat/v0.7-h-1-agent-keypair
Commit: feat(identity): per-agent Ed25519 keypair CLI (#<filed>)

Definition of done:
- generate / import / list / export-pub all work
- File permissions strictly enforced
- Round-trip test green
- Hardware-key disclaimer documented
```

---

### Task H2: Outbound link signing

**File(s):** `src/storage/links.rs`, `src/identity/sign.rs` (new)
**Branch:** `feat/v0.7-h-2-link-signing`

**Deliverable:** Every `memory_links` insert signs the canonical-CBOR-encoded link with the active agent's private key. Signature stored in the `signature` column (currently dead). `attest_level` set to `self-signed`.

**Canonical CBOR encoding** (per RFC 8949 §4.2.1) of: `{src_id, dst_id, relation, observed_by, valid_from, valid_until}`.

#### NHI starter prompt for H2

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task H2.
Depends on: H1.

Goal: every outbound memory_link write signs the link with the active
agent's private key. Fills the dead signature column shipped in v0.6.3.

What to do:
1. Create src/identity/sign.rs:
   - fn canonical_cbor(link: &Link) -> Vec<u8> — RFC 8949 §4.2.1 encoding
     of {src_id, dst_id, relation, observed_by, valid_from, valid_until}
   - fn sign(keypair: &AgentKeypair, link: &Link) -> Vec<u8>
2. Update src/storage/links.rs::insert_link to:
   - Look up the active agent's keypair (from AppState::active_keypair)
   - Compute canonical CBOR
   - Sign with private key
   - Store signature (64 bytes) in the existing signature column
   - Set attest_level = "self-signed"
3. If active agent has no keypair: leave signature NULL, attest_level =
   "unsigned" (preserves v0.6.4 backward compat).
4. Unit test: insert a link with a generated keypair, read it back,
   verify signature against the public key.
5. Integration test against existing memory_link MCP tool — assert the
   wire-level response includes attest_level field.

Branch: feat/v0.7-h-2-link-signing
Commit: feat(identity): outbound Ed25519 signing on memory_links (#<filed>)

Definition of done:
- All outbound links carry a valid signature when keypair present
- attest_level correctly populated (unsigned / self-signed)
- Unit + integration tests green
- v0.6.4-compatible: links without keypair still write
```

---

### Task H3: Inbound verification against `observed_by` claim

**File(s):** `src/storage/links.rs`, `src/identity/verify.rs` (new)
**Branch:** `feat/v0.7-h-3-inbound-verify`

**Deliverable:** When a link arrives from a peer (federation), verify the signature against the public key associated with the link's `observed_by` claim. If verification fails, reject the link with a logged warning. If `observed_by` has no known public key, accept-and-flag as `unsigned`.

---

### Task H4: `attest_level` enum + `verify()` MCP tool

**File(s):** `src/storage/links.rs`, `src/mcp.rs`
**Branch:** `feat/v0.7-h-4-attest-level`

**Deliverable:** `attest_level` enum with three variants (`unsigned` | `self_signed` | `peer_attested`). New MCP tool `memory_verify(link_id)` returns `{signature_verified: bool, attest_level, signed_by, signed_at}`.

---

### Task H5: Append-only `signed_events` audit table

**File(s):** schema migration to v21, `src/storage/signed_events.rs` (new)
**Branch:** `feat/v0.7-h-5-signed-events-table`

**Deliverable:** New table `signed_events(id, agent_id, event_type, payload_hash, signature, attest_level, timestamp)`. Append-only — no UPDATE or DELETE statements supported via the application layer. Each `memory_link` write also appends to this table for an immutable audit chain.

---

### Task H6: Verification end-to-end test

**File(s):** `tests/identity_e2e.rs` (new)
**Branch:** `feat/v0.7-h-6-verify-e2e-test`

**Deliverable:** Test that:
1. Generates a keypair for agent A
2. Stores a link as agent A
3. Reads the link back, confirms signature column is non-NULL
4. Calls `memory_verify(link_id)` → asserts `signature_verified: true`
5. Negative test: tamper with the link content; verify returns `false`

**Closes G12 audit finding** (signature column was dead in v0.6.3).

---

## Track I — Sidechain Transcripts (Bucket 1.7)

> **Track goal:** raw conversation/reasoning trail in zstd-compressed BLOBs, linked to derived memories via `memory_transcript_links`. Default off (opt-in per namespace). Substrate for R5 auto-extraction.

### Task I1: `memory_transcripts` table schema (BLOB + zstd-3)

**File(s):** schema migration v21 → v22, `src/storage/transcripts.rs` (new)
**Branch:** `feat/v0.7-i-1-transcripts-schema`

**Deliverable:** New table with columns: `id`, `namespace`, `created_at`, `expires_at`, `compressed_size`, `original_size`, `zstd_level` (3), `content_blob` (BLOB).

#### NHI starter prompt for I1

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task I1.

Goal: schema for compressed transcript storage.

What to do:
1. Add migration v21 -> v22 in src/storage/migrations/.
2. CREATE TABLE memory_transcripts (
     id TEXT PRIMARY KEY,
     namespace TEXT NOT NULL,
     created_at TEXT NOT NULL,
     expires_at TEXT,
     compressed_size INTEGER NOT NULL,
     original_size INTEGER NOT NULL,
     zstd_level INTEGER NOT NULL DEFAULT 3,
     content_blob BLOB NOT NULL
   );
3. Index on (namespace, created_at) for archive sweeps.
4. cli::boot::MAX_SUPPORTED_SCHEMA bumped to 22.
5. src/storage/transcripts.rs:
   - fn store(namespace, content: &str, ttl: Duration) -> Transcript
     - Compresses with zstd level 3
     - Writes row with computed sizes
   - fn fetch(id) -> Result<String>
     - Reads row, decompresses, returns content
   - fn purge_expired() -> usize
     - DELETE WHERE expires_at < now AND archived = true
6. Unit tests: store + fetch roundtrip; expected compression ratio 5-10×
   on chat-shaped text.

Branch: feat/v0.7-i-1-transcripts-schema
Commit: feat(transcripts): schema v22 with zstd-3 BLOB storage (#<filed>)

Definition of done:
- Migration is idempotent
- Round-trip test green
- Compression ratio ≥5× on chat-shaped input
```

---

### Task I2: `memory_transcript_links` join table

**Branch:** `feat/v0.7-i-2-transcript-links`

**Deliverable:** Join table `memory_transcript_links(memory_id, transcript_id, span_start, span_end)`. Allows multiple memories derived from one transcript and one transcript shared across multiple memories.

---

### Task I3: Per-namespace TTL with archive → prune lifecycle

**Branch:** `feat/v0.7-i-3-transcript-lifecycle`

**Deliverable:** Each namespace can configure a transcript TTL via `[transcripts]` table in config. Background sweeper archives transcripts whose memories are all expired, then prunes after a grace period.

---

### Task I4: `memory_replay <id>` MCP tool

**Branch:** `feat/v0.7-i-4-memory-replay`

**Deliverable:** New MCP tool `memory_replay(memory_id)` that traverses `memory_transcript_links` and reconstructs the transcript chain for a given memory. Returns the decompressed text + span metadata.

---

### Task I5: Substrate hook for R5 auto-extraction

**Branch:** `feat/v0.7-i-5-pre-store-transcript-hook`

**Deliverable:** A reference `pre_store` hook that, when given a transcript, extracts derived memories from it (uses an LLM via the existing `query_expand` infrastructure). Defaults off; opt-in per namespace. Recovers commitment R5.

---

## Track J — Apache AGE Acceleration (Bucket 2)

> **Track goal:** Postgres SAL adapter detects Apache AGE and projects `memory_links` as a property graph for Cypher access. Recursive CTE path stays as the SQLite fallback. Closes commitment R2 (`memory_find_paths`).

### Task J1: AGE detection in Postgres SAL

**Branch:** `feat/v0.7-j-1-age-detection`

**Deliverable:** At Postgres SAL initialization, run `SELECT * FROM pg_extension WHERE extname='age';`. If present, set `kg_backend = "age"`; else `kg_backend = "cte"`.

---

### Task J2: Cypher implementation of `memory_kg_query`

**Branch:** `feat/v0.7-j-2-kg-query-cypher`

**Deliverable:** When `kg_backend == "age"`, route `memory_kg_query` through Cypher (`MATCH (a)-[:RELATION*1..N]->(b) ...`). SQLite path unchanged.

---

### Task J3: Cypher implementation of `memory_kg_timeline`

**Branch:** `feat/v0.7-j-3-kg-timeline-cypher`

---

### Task J4: Cypher implementation of `memory_kg_invalidate`

**Branch:** `feat/v0.7-j-4-kg-invalidate-cypher`

**Deliverable:** Cypher implementation of `memory_kg_invalidate` plus G14 absorption (audit-edge emission on invalidate).

---

### Task J5: Dual-path tests (AGE-Postgres vs. CTE-SQLite identical results)

**Branch:** `feat/v0.7-j-5-dual-path-tests`

**Deliverable:** Test harness that runs the same query against AGE-Postgres + CTE-SQLite + asserts the result sets are equivalent under set-equivalence (since order may differ). Required across all three KG operations.

#### NHI starter prompt for J5

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task J5.
Depends on: J2, J3, J4.

Goal: assert AGE and CTE paths produce identical results. Dual-path
discipline is the safety net against Cypher-vs-CTE divergence.

What to do:
1. tests/kg_dual_path.rs (new file).
2. setUp: a fixture corpus of 200 memories + 800 links covering enough
   topology to exercise depth-1 through depth-5 traversals.
3. For each KG operation (kg_query, kg_timeline, kg_invalidate):
   - Run against an AGE-enabled Postgres test DB
   - Run against a CTE SQLite test DB (same fixture)
   - Assert result sets equivalent under set-equivalence (since order
     may differ between paths)
4. Test depths: 1, 2, 3, 5. Cyclic and non-cyclic graphs.
5. Run only when AI_MEMORY_TEST_AGE_URL env var is set; skip otherwise
   (CI matrix configures this for the AGE-postgres job).

Branch: feat/v0.7-j-5-dual-path-tests
Commit: test(kg): AGE vs CTE dual-path equivalence (#<filed>)

Definition of done:
- 12 test cases (3 ops × 4 depths) passing on both paths
- Set-equivalence assertion (not order-sensitive)
- CI runs both paths
```

---

### Task J6: PERFORMANCE.md updated with separate AGE/CTE budgets

**Branch:** `feat/v0.7-j-6-perf-budgets`

---

### Task J7: R2 — `memory_find_paths(source, target)` MCP tool

**Branch:** `feat/v0.7-j-7-r2-find-paths`

**Deliverable:** New MCP tool `memory_find_paths(source, target, max_depth=5)`. Cypher one-liner on AGE; recursive CTE on SQLite fallback. Recovers commitment R2.

---

### Task J8: Bench gate — AGE-mode p95 ≥ 30% faster than CTE-mode at depth=5

**Branch:** `feat/v0.7-j-8-age-bench-gate`

**Deliverable:** New `bench.yml` CI gate that runs AGE + CTE at depth=5, asserts AGE p95 ≥ 30% faster. Justifies the AGE complexity. If AGE isn't faster, drop AGE.

---

## Track K — A2A + Permissions + G1 Inheritance (Bucket 3)

> **Track goal:** the **mandatory cutline.** G1 inheritance is *"even if everything else slips, this fix ships"* per ROADMAP2 §7.3. Plus A2A maturity, subscription reliability, per-agent quotas, permissions overhaul, approval API.

### Task K1 — G1: Namespace inheritance enforcement (CUTLINE-PROTECTED)

**File(s):** `src/governance/policy.rs`, `src/namespace.rs`
**Branch:** `feat/v0.7-k-1-g1-inheritance-fix`

**Deliverable:** `resolve_governance_policy(namespace)` walks `build_namespace_chain(namespace)` and returns the first non-null policy encountered, not just the leaf. Per-policy `inherit: bool` flag (default `true`).

**Test (mandatory per ROADMAP2 §7.3):** Parent namespace has `Approve` policy; child namespace has none. Write to child must require approval.

#### NHI starter prompt for K1

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task K1.
THIS IS THE CUTLINE FIX. Mandatory per ROADMAP2 §7.3 even if everything
else in v0.7.0 slips.

Goal: namespace inheritance enforcement. Today's behavior: a parent
"Approve" policy doesn't block a child write because resolve_governance_policy
only consults the leaf namespace. Fix: walk the namespace chain.

What to do:
1. Locate src/governance/policy.rs::resolve_governance_policy.
2. Replace its leaf-only logic with:
   for ancestor in build_namespace_chain(namespace).iter().rev():
       if let Some(policy) = lookup_policy(ancestor):
           if policy.inherit OR ancestor == namespace:
               return policy
   return None
3. Update GovernancePolicy struct: add `inherit: bool` field, default true.
4. Migration: existing rows get inherit=true (backfill via UPDATE).
5. Ship-gate test:
   - Parent namespace "team" has GovernancePolicy(approve_required=true).
   - Child namespace "team/alice" has no policy.
   - Write to "team/alice" → must require approval (currently does not).
   This test goes in tests/governance_inheritance.rs and is FATAL —
   if it fails, the release is blocked.
6. Update docs/governance.html with a worked example of inheritance.
7. Add an audit-log entry on every gate decision: which ancestor's
   policy fired.

Branch: feat/v0.7-k-1-g1-inheritance-fix
Commit: fix(governance): G1 namespace inheritance enforcement (#<filed>)

Definition of done:
- resolve_governance_policy walks the chain
- inherit:bool added with default true and backfill migration
- Cutline test green
- Audit log records ancestor whose policy fired
- Doc updated with worked example

CRITICAL: this fix MUST ship in v0.7.0. If anything else slips, this
stays.
```

---

### Task K2: `pending_actions` timeout sweeper

**Branch:** `feat/v0.7-k-2-pending-actions-sweeper`

**Deliverable:** Background task sweeps `pending_actions` table every 60 seconds; expires entries past `default_timeout_seconds`. Single SELECT-and-UPDATE statement. Closes the v0.6.3.1 honest-Capabilities-v2 disclosure that `default_timeout_seconds` was advertised but unused.

---

### Task K3: `permissions.mode` actually consulted by gate

**Branch:** `feat/v0.7-k-3-permissions-mode-honest`

**Deliverable:** The `permissions.mode = "advisory"` honest-disclosure shipped in v0.6.3.1 becomes obsolete because permissions are now actually enforced. Gate consults `permissions.mode` and changes behavior accordingly: `"enforce"` → blocks; `"advisory"` → logs; `"off"` → allows.

---

### Task K4: Approval-event routing through subscription system

**Branch:** `feat/v0.7-k-4-approval-event-routing`

**Deliverable:** When a `pending_action` requires approval, dispatch an `approval.requested` event through the existing `subscriptions` system. The Approval API HTTP+SSE handler in K10 consumes these events. Closes the v0.6.3.1 honest disclosure that `approval.subscribers` was advertised but never published.

---

### Task K5: `rule_summary` populated

**Branch:** `feat/v0.7-k-5-rule-summary`

**Deliverable:** Capabilities v3's `rule_summary` field gets a real value: ordered list of active governance rules with a one-line summary each. Closes the v0.6.3.1 honest-Capabilities-v2 disclosure that left this field as a placeholder.

---

### Task K6: A2A correlation IDs + ACKs + retry + TTL + replay protection

**Branch:** `feat/v0.7-k-6-a2a-maturity`

**Deliverable:** Every A2A message carries a UUIDv4 correlation ID. Recipients ACK within configurable TTL. Sender retries up to 3× on no-ACK. Recipients track seen correlation IDs in a bounded LRU; duplicates dropped (replay protection).

#### NHI starter prompt for K6

```
You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task K6.

Goal: A2A maturity. Today, federation messages can be lost or replayed
with no detection. Add correlation IDs, ACK semantics, retry, replay
protection.

What to do:
1. src/federation/a2a.rs gets:
   - struct A2AMessage { correlation_id: Uuid, sender, recipients,
     payload, expires_at }
   - struct A2AAck { correlation_id: Uuid, recipient, accepted: bool,
     reason: Option<String> }
2. Sender:
   - Generate UUIDv4 for each outbound message
   - Wait for ACK with configurable TTL (default 30s)
   - Retry up to 3× on no-ACK with exponential backoff
3. Receiver:
   - Maintain a bounded LRU of seen correlation IDs (size 10_000)
   - On duplicate: ACK with accepted=false, reason="duplicate"
   - On expired (now > expires_at): drop, log warning
4. Tests:
   - 100 messages: all ACK'd
   - Replay attack (same UUID 2× from same sender): rejected
   - Network partition simulation: retries kick in
   - Expired message: dropped, not processed
5. Performance bench: A2A throughput under realistic mesh sizes
   (3-node, 5-node, 10-node) — record p50/p95/p99 in PERFORMANCE.md.

Branch: feat/v0.7-k-6-a2a-maturity
Commit: feat(federation): A2A correlation IDs + ACK retry + replay protection (#<filed>)

Definition of done:
- All 4 tests pass
- Replay attacks verifiably rejected
- A2A bench numbers recorded in PERFORMANCE.md
```

---

### Task K7: Subscription reliability (DLQ, replay-from-cursor, HMAC)

**Branch:** `feat/v0.7-k-7-subscription-reliability`

**Deliverable:** Subscription dispatcher gains:
- 5xx retry with exponential backoff
- Dead-letter queue table (`subscription_dlq`)
- Replay-from-cursor support (for restart recovery)
- HMAC signing of every dispatched event (was opt-in; now mandatory)

---

### Task K8: Per-agent rate limits and storage caps

**Branch:** `feat/v0.7-k-8-per-agent-quotas`

**Deliverable:** Config table `[quotas.per_agent]` with `rps_limit`, `storage_mb_cap`. Enforced at MCP entry. 429 returned on RPS overage; insert blocked on storage cap.

---

### Task K9: Permission system (rules + modes + hooks → decision; deny-first)

**Branch:** `feat/v0.7-k-9-permission-system`

**Deliverable:** Refactor existing `governance` system into: `rules` (declarative policies) + `modes` (`enforce`/`advisory`/`off`) + `hooks` (programmable from Track G) → `Decision`. Default deny-first; ask-by-default for ambiguous cases.

---

### Task K10: Approval API (HTTP + SSE + MCP, HMAC mandatory, `remember=forever`)

**Branch:** `feat/v0.7-k-10-approval-api`

**Deliverable:** Three surfaces:
- HTTP: `GET /api/v1/approvals/pending` + `POST /api/v1/approvals/:id/decide`
- SSE: `GET /api/v1/approvals/stream` (server-sent events for live updates)
- MCP: `memory_approval_pending`, `memory_approval_decide` tools

`remember=forever` parameter on `decide`: subsequent identical requests auto-approve (progressive trust). HMAC signing **mandatory** — non-optional per ROADMAP2 §7.3.

---

### Task K11: `ai-memory governance migrate-to-permissions` CLI

**Branch:** `feat/v0.7-k-11-permissions-migration`

**Deliverable:** One-shot migration tool that converts existing `governance` rows into the new `permissions` schema. Idempotent. Dry-run by default; `--apply` commits.

---

## Track F — Docs + release (consolidated v0.7.0 scope)

### Task F1: `docs/MIGRATION_v0.7.md`

**Branch:** `feat/v0.7-f-1-migration-guide`

**Deliverable:** Comprehensive migration guide for users coming from v0.6.4. Covers:
- Capabilities v3 schema additions
- New tools (`memory_load_family`, `memory_smart_load`, `memory_find_paths`, `memory_replay`, `memory_approval_*`)
- Hook pipeline (opt-in; no default change)
- Ed25519 attestation (opt-in via `ai-memory identity generate`)
- Sidechain transcripts (opt-in per namespace)
- Apache AGE (opt-in via Postgres extension)
- G1 inheritance fix (**behavior change** — child writes may now require approval where they didn't before)
- Permissions migration via `ai-memory governance migrate-to-permissions`

### Task F2: `docs/whats-new-v07.html` (Pages page)

### Task F3: Top-nav + landing-page badges + v0.6.4→v0.7.0 references

### Task F4: README + ADMIN_GUIDE updates (substantial — multiple new tracks of features to document)

### Task F5: CHANGELOG, version bumps, tag, CI release

### Task F6: `docs/v0.7/rfc-attested-cortex.md` (design RFC)

**Deliverable:** Design RFC documenting the architectural decisions for v0.7.0:
- Why Ed25519 over X25519 + ChaCha20 (separate concern; X25519 belongs to v0.8 end-to-end encryption per ROADMAP2 v0.8)
- Why subprocess-stdio + daemon-mode for hooks (vs. dynamic library plugins)
- Why AGE behind a feature flag (vs. hard dependency)
- Why permission system replaces governance instead of augmenting (and the migration story)

---

## Critical-path sequencing

```
Phase 1 (Weeks 1-3) — Cortex-fluent + cutline
   Track A complete (A1→A2→A3→A4→A5)
   Track B complete (B4→B1→B3→B2→B5)
   Track C complete (C1→C2→C3→C4→C5) — parallel with A/B
   Track K cutline: K1 (G1 inheritance) — parallel with A/B
   F1 + F6 drafted

Phase 2 (Weeks 3-7) — Hooks + Attestation
   Track G complete (G1→G2→G3→G4→G5→G6→G7→G8→G9→G10→G11)
   Track H complete (H1→H2→H3→H4→H5→H6) — parallel with G
   Track I drafted (I1→I2 in this phase; I3-I5 spillover into Phase 3)
   Track K2-K5 (the smaller permissions cleanups) — parallel

Phase 3 (Weeks 7-10) — AGE + Permissions overhaul
   Track J complete (J1→J2→J3→J4→J5→J6→J7→J8)
   Track K complete (K6→K7→K8→K9→K10→K11)
   Track I3-I5 finished

Phase 4 (Weeks 10-11) — Validation + Release
   Track D complete (D1-D4)
   Track E complete (E1-E3)
   F2-F5 finished
   ai-memory-test-hub campaigns/v0.7.md — full evidence
   ai-memory-discovery-gate runs/v0.7-ship-date/ — T0+ convergence
   v0.7.0 release tagged + 5/5 distribution channels published
```

**With one full-time engineer:** 11-12 weeks (~3 months).
**With two engineers in parallel:** 7-8 weeks (~2 months).
**With three engineers + good track decomposition:** 5-6 weeks (~6 weeks).

The **mandatory cutline** for v0.7.0 ship: K1 (G1 inheritance) + Track A + Track B + Track G + Track H + F1+F5. **That alone is ~6-8 weeks** with one engineer. Tracks I, J, C, D, E can defer to v0.7.1 if scope pressure forces it — the v0.7.0 narrative (`attested-cortex`) still holds without them.

---

## Definition of release-ready

v0.7.0 ships when **all of these are true**:

- [ ] **K1 (G1 inheritance fix) shipped, tested, evidence on test-hub** — non-negotiable
- [ ] All A1-A5 + B1-B5 + F1-F6 tasks merged
- [ ] Track G hook pipeline functional end-to-end (G1-G7 minimum; G8-G11 strongly recommended)
- [ ] Track H Ed25519 attestation functional end-to-end (H1-H6 all required)
- [ ] Track K permission system functional (K1-K5 minimum; K6-K11 strongly recommended)
- [ ] Capabilities v3 default + v2/v1 backward compat preserved
- [ ] Hook pipeline does not regress the v0.6.3 50ms recall p95 budget (CI bench gate)
- [ ] Schema migration v20 → v22 idempotent + tested against real production-shaped DB
- [ ] CI green on main, all required checks passing
- [ ] T0 calibration cells (E2) re-run against v0.7.0 binary, ≥95% pass rate across all 4 LLMs
- [ ] Cross-harness benchmark (D1) reproduced
- [ ] No SDK regression — existing 0.6.4 SDKs still work against v0.7.0 server
- [ ] 5/5 distribution channels live (release / brew / ghcr / COPR / crates.io)
- [ ] SHA256SUMS published as a release asset
- [ ] OIDC SDK publish via `publish-sdks.yml` workflow

## What's deferred (out of v0.7.0 scope, per agreement)

- **R4** (curator CLI surface) → v0.8 Pillar 2.5
- **R6** (consensus memory truth-determination) → v0.8 Pillar 3
- **R8** (TOON v2 schema inference) → v0.9 or formally cut
- **Per-agent profile pre-warm** (NHI guardrails phase 2) → v0.7.1 or v0.8
- **Hardware-backed key storage** (TPM/HSM/Secure Enclave) → AgenticMem commercial layer
- **Pool-of-N reranker** → v0.9 alongside default-on rerank
- **Long-term per-namespace HNSW shard / sqlite-vec migration** → v0.9
- **End-to-end memory encryption** (X25519 + ChaCha20-Poly1305 layer 3 peer-meshed) → v0.8 per existing #228
- **API stability guarantee** → v1.0
- **Security audit** → v1.0

---

## Refs

- [v0.6.4 release](https://github.com/alphaonedev/ai-memory-mcp/releases/tag/v0.6.4) — predecessor (`quiet-tools`, shipped 2026-05-05)
- [v0.6.5 epic (now superseded)](../v0.6.5/V0.6.5-EPIC.md) — cortex-fluent narrative, rolled into this epic
- [ROADMAP2.md §7.3](../../ROADMAP2.md) — original v0.7 spec (Q2 2026 target; now consolidating into `attested-cortex`)
- [`docs/v0.7/compatibility-matrix.html`](compatibility-matrix.html) — D2: per-harness × per-feature compat grid (Claude Code, Claude Desktop, Codex, Cursor, Cline, Continue, Aider, Goose, generic JSON-RPC) with status emojis (✅ / ⚠️ / 🚧 / ❌ / N/A) and cross-links to the RFC, migration guide, and Discovery Gate
- [`docs/v0.7/rfc-attested-cortex.md`](rfc-attested-cortex.md) — F6: design RFC (compat-matrix authoritative source)
- [`docs/v0.7/canonical-phrasings.md`](canonical-phrasings.md) — A1+A2: canonical phrasings for capabilities v3 / loaders
- [`docs/MIGRATION_v0.7.md`](../MIGRATION_v0.7.md) — F1: operator upgrade guide (v0.6.4 → v0.7.0)
- Issues: [#545](https://github.com/alphaonedev/ai-memory-mcp/issues/545) · [#546](https://github.com/alphaonedev/ai-memory-mcp/issues/546) · [#512](https://github.com/alphaonedev/ai-memory-mcp/issues/512)
- Discovery Gate: [alphaonedev/ai-memory-discovery-gate#1](https://github.com/alphaonedev/ai-memory-discovery-gate/pull/1)
- v0.6.4 NHI Witness transcripts (Grok 4.2 reasoning before/after): observation cells under [Discovery Gate `runs/2026-05-05/`](https://alphaonedev.github.io/ai-memory-discovery-gate/)

---

**Codename:** `attested-cortex` — the substrate becomes both more articulate and cryptographically trustworthy in one release. The two narratives (legibility from v0.6.5 + trust from ROADMAP2's v0.7) are not incidental siblings; they're the same story told from two angles. An NHI fleet needs to know what its memory cortex says about itself **and** that what it says is signed.
