The governance gate.

Three decisions: Allow, Deny, Pending. Three actions: store, delete, promote. Four levels: Any, Registered, Owner, Approve. Three approver types: Human, Agent, Consensus. Every governed write either returns immediately or queues for approval — never silently succeeds.

Task 1.8 — policy shape Task 1.9 — enforcement Task 1.10 — approver types v0.6.2 S34 — federation propagation
The decision

Allow / Deny / Pending.

Every governed write goes through one function and produces one of three outcomes. The decision is not advisory — callers MAY execute on Allow, MUST reject on Deny, and SHOULD queue + return the pending_id on Pending.

verdict 1

Allow

Proceed with the action. Caller receives the new memory or success response. No row in pending_actions.

verdict 2

Deny(reason)

Reject with HTTP 403. The wrapped String surfaces the human-readable reason — not a placeholder. AI clients can quote it verbatim.

verdict 3

Pending(id)

Queued in pending_actions; caller receives the new pending_id and HTTP 202. The action replays once approved.

// src/models.rs pub enum GovernanceDecision { Allow, Deny(String), Pending(String), }
The decision tree

How a write becomes Allow / Deny / Pending.

For every governed action, the daemon walks this tree. The lookup is namespace-local — the policy lives in the namespace's standard memory at metadata.governance; absent a standard, the default policy applies.

Governed action arrives store / delete / promote Resolve namespace policy walk standards.metadata.governance Pick gate from policy store→write · delete→delete · promote→promote Any no gate Registered in _agents? Owner caller == metadata.agent_id? Approve queue + run approver Allow execute immediately Allow caller is registered Deny not registered Allow caller is owner Deny other agent Pending(id) replay once approver decides
GovernanceLevel × GovernedAction

The 12-cell verdict matrix.

Each policy field (write, promote, delete) holds one GovernanceLevel. Pick the field by action; the level determines the outcome shape.

Level store action promote action delete action Used when
Any Allow Allow Allow Wide-open namespaces (no gate). Default for write and promote.
Registered Allow if in _agents Allow if in _agents Allow if in _agents Caller must have run POST /agents/register first. Otherwise Deny("agent not registered").
Owner Allow if caller == agent_id Allow if caller == agent_id Allow if caller == agent_id Default for delete. Compares caller_agent_id to memory.metadata.agent_id.
Approve Pending(id) Pending(id) Pending(id) Routes to ApproverType: Human / Agent(id) / Consensus(N).
GovernedAction

The three actions a policy gates.

These are the only operations that pass through the governance gate. Reads (memory_get, memory_list, memory_recall, memory_search) bypass governance — they go through the visibility/scope filter instead.

Store
Creating a new memory in a governed namespace. Picks policy.write.
discriminator: "store"
Delete
Hard-deleting a memory. Picks policy.delete. Default Owner — unowned memories can be deleted by anyone unless the namespace is opted into stricter rules.
discriminator: "delete"
Promote
Tier change Mid → Long. Picks policy.promote. Long-tier memories don't expire — promotion is the only edit that affects retention.
discriminator: "promote"
ApproverType — who decides on Approve

Three flavors of approval.

When a level resolves to Approve, the approver shape determines what unblocks the pending action. ApproverType serializes externally-tagged so policy JSON stays unambiguous.

approver 1

Human

Out-of-band — UI button, email, Slack approve. Resolves via POST /pending/:id/approve with a human session.

{"approver": "human"}
approver 2

Agent(id)

A single specific registered agent must approve. Used for AI-supervisor patterns: junior agents queue, supervising agent approves.

{"approver": {"agent": "alice"}}
approver 3

Consensus(N)

N approval votes from any mix of human/agent registrations. Each vote appends to PendingAction.approvals; clears at threshold.

{"approver": {"consensus": 3}}
Policy shape — GovernancePolicy

What a namespace standard stores.

A namespace's standard memory carries the policy at metadata.governance. Absent a standard, the default applies. The default opens writes/promotes wide and gates deletes to owners with human-approver fallback for any Approve usage.

// Default GovernancePolicy { "write": "any", "promote": "any", "delete": "owner", "approver": "human" } // Tight policy for a research-team namespace { "write": "registered", // only registered agents can write "promote": "approve", // promotion goes through the approver "delete": "owner", "approver": {"consensus": 2} // any 2 votes unblock promote } // Sole-author policy { "write": "approve", // every write needs approval "promote": "approve", "delete": "approve", "approver": {"agent": "alice"} // alice approves everything }

Defensive defaults (v0.6.2 S34): promote, delete, and approver carry #[serde(default)] so partial-policy payloads (a common shape for operator CLIs / test harnesses that only care about write) round-trip instead of 400-ing on missing fields. write remains required.

Pending-action lifecycle

From queue to replay, in five steps.

An Approve verdict isn't terminal — it parks the action. The original payload sits in pending_actions.payload until a decision arrives, then the daemon replays it as if the caller had just submitted it.

step 1
Caller submits
HTTP/MCP write hits the daemon. Validators pass. Governance check picks policy.write.
step 2
Verdict: Pending
Daemon inserts a row into pending_actions: action_type, namespace, payload (full action JSON), requested_by, status="pending".
step 3
Caller gets 202
Response: {"status":"pending","pending_id":"..."}. The HTTP request returns immediately — no blocking poll.
step 4
Approver decides
Human, agent, or consensus vote (POST /pending/:id/approve or /reject). Each consensus vote appends to approvals[]; threshold flips status.
step 5a
Approved → replay
Daemon re-executes the original payload. The new memory or delete completes. Status=approved; decided_by, decided_at stamped.
step 5b
Rejected → drop
Status=rejected. Payload retained for audit. Caller's original write does not happen.
Federation propagation — v0.6.2 S34/S35

Decisions and policies cross peer boundaries.

Without propagation, a decision on node-2 wouldn't reach node-1, and a caller on node-1 would still see pending after the action was already approved. Two additive sync_push fields close that gap:

S34 — pending decisions

PendingDecision

sync_push.pending_decisions carries { id, approved, decider }. Peers apply via db::decide_pending_action. Already-decided rows: idempotent no-op.

S35 — namespace meta

NamespaceMetaEntry

sync_push.namespace_meta carries { namespace, standard_id, parent_namespace }. Without this, peers see the standard memory itself but miss the parent-link tuple, breaking inheritance walks.

Decisions also use quorum-fanout writes via broadcast_store_quorum at W of N — a single decision sticks across the federation with the same durability guarantee as a memory write.

What governance does not cover

The boundary, drawn explicit.

Governance is a write-side gate. Reads, federation, and audit live elsewhere. Calling these out so AI clients don't assume the wrong contract.

Not covered: read paths (memory_get, memory_list, memory_recall, memory_search) — those use the scope/as_agent visibility filter, not governance.
Not covered: link creation (memory_link) — passes validate_link only; KG edges are not gated. May change in v0.7 (Hook Pipeline can run KG-write hooks).
Not covered: sync push receive — peers trust each other on validated payloads, but governance is not re-checked on the receiver. The sender's verdict is authoritative.
Not covered (yet): hooks (v0.7 Bucket 0) and Permission System refactor (v0.7 Bucket 3) — current governance is the foundation those layers extend.