vta_keyspaces/lib.rs
1//! Central registry of the VTA's keyspace names.
2//!
3//! Every `store.keyspace(..)` call in the VTA (`vta-service` server, offline
4//! CLIs, backup, tests) names its keyspace through a `const` here rather than a
5//! bare string literal. This is the single source of truth that killed the
6//! `"imported"` / `"imported_secrets"` test-vs-production divergence (a test
7//! opened a *different*, empty keyspace than the one production writes). The
8//! `no_bare_keyspace_literals` guard in `vta-service` keeps it that way by
9//! scanning that crate's source for bare `.keyspace("…")` literals.
10//!
11//! Keyspace *names* live here; per-keyspace *key formats* (the `key:`, `seed:`,
12//! `path_counter:` … record families inside a keyspace) are a separate concern
13//! and are not yet centralised.
14//!
15//! A near-leaf crate: it holds the shared keyspace vocabulary (the name
16//! constants) plus the [`Keyspaces`] handle bundle, so that every VTA subsystem
17//! crate can name and pass keyspaces without depending on `vta-service`. Its
18//! only dependency is `vti-common` (for `KeyspaceHandle`).
19
20use vti_common::store::KeyspaceHandle;
21
22/// Shared bundle of borrowed keyspace handles passed to operations that need
23/// several keyspaces at once.
24///
25/// The struct is a pure field bundle — the constructors that borrow it from a
26/// concrete `AppState` / `VtaState` live in `vta-service` (they know those
27/// types), so this stays free of any `vta-service` dependency.
28pub struct Keyspaces<'a> {
29 pub keys: &'a KeyspaceHandle,
30 pub acl: &'a KeyspaceHandle,
31 pub contexts: &'a KeyspaceHandle,
32 pub did_templates: &'a KeyspaceHandle,
33 pub audit: &'a KeyspaceHandle,
34 pub imported: &'a KeyspaceHandle,
35 #[cfg(feature = "webvh")]
36 pub webvh: &'a KeyspaceHandle,
37}
38
39/// Master seed + key records (`key:`, `seed:`, `path_counter:`,
40/// `active_seed_id`, `imported_kek_salt`, …) and the backup import sentinel.
41pub const KEYS: &str = "keys";
42/// Auth sessions + challenges.
43pub const SESSIONS: &str = "sessions";
44/// ACL entries + the seal record + the integrity-anchor root.
45pub const ACL: &str = "acl";
46/// Trust contexts (the BIP-32 key hierarchy roots).
47pub const CONTEXTS: &str = "contexts";
48/// Stored DID templates (global + context-scoped).
49pub const DID_TEMPLATES: &str = "did_templates";
50/// Audit log.
51pub const AUDIT: &str = "audit";
52/// Imported secret material (KEK-wrapped). Named `imported_secrets`, **not**
53/// `imported` — the latter was a long-standing test-only typo that operated on
54/// an empty keyspace disjoint from production. Always reference this const.
55pub const IMPORTED_SECRETS: &str = "imported_secrets";
56/// Non-extractable internal signing keys.
57///
58/// Deliberately **not** [`IMPORTED_SECRETS`]: that keyspace wraps its contents
59/// under a KEK derived from the BIP-39 master seed, so anything stored there is
60/// reconstructible by whoever holds the mnemonic. Internal keys exist precisely
61/// to have no such path — their material is generated from the system CSPRNG,
62/// never derived, and lives here instead.
63///
64/// In [`EXCLUDED_FROM_BACKUP`] by design, not by omission. A backup containing
65/// this keyspace would be an export of keys the VTA promises never to export.
66pub const INTERNAL_KEYS: &str = "internal_keys";
67/// Ephemeral cache (resolver/auth caches).
68pub const CACHE: &str = "cache";
69/// Holder credential vault (third-party secrets stored on this VTA).
70pub const VAULT: &str = "vault";
71/// Persistent runtime service-enable state (`operations::protocol::runtime_state`).
72pub const SERVICE_STATE: &str = "service_state";
73/// Sealed-bootstrap anti-replay nonce log.
74pub const SEALED_NONCES: &str = "sealed_nonces";
75/// In-flight backup-bundle control-plane records.
76pub const BACKUP_BUNDLES: &str = "backup_bundles";
77/// WebVH DID records + `did.jsonl` state.
78pub const WEBVH: &str = "webvh";
79/// In-flight passkey-as-verificationMethod enrolment state.
80pub const PASSKEY_VMS: &str = "passkey_vms";
81/// Persisted protocol-management drain set.
82pub const DRAINS: &str = "drains";
83/// Per-kind previous-config snapshots for fail-forward rollback.
84/// (Historically `operations::protocol::snapshot::KEYSPACE_NAME`.)
85pub const SNAPSHOT: &str = "service_prev_config";
86/// KMS-protected, unencrypted boot keyspace (TEE integrity manifest, etc.).
87pub const BOOTSTRAP: &str = "bootstrap";
88/// Inbound-messaging consent: durable grants + TTL'd pending requests
89/// (`vti_common::consent`). The VTA is the first gate for bridged conversations.
90pub const CONSENT: &str = "consent";
91/// Per-(platform, context) approver bindings — who decides consent and how the
92/// prompt routes (`vti_common::consent::ApproverBinding`).
93pub const CONSENT_APPROVERS: &str = "consent_approvers";
94/// VTA-issued credentials (minted by `vta/credentials/issue/0.1`, revoked by
95/// `vta/credentials/revoke/0.1`). One record per credential keyed `cred:<id>`;
96/// revocation is a tombstone (`revokedAt` set in place), not a delete. Distinct
97/// from [`VAULT`] (which stores credentials the holder *holds*).
98pub const ISSUED_CREDENTIALS: &str = "issued_credentials";
99
100/// Per-context key/value store for AI-agent memory (`vta/memory/{put,list,
101/// delete}/0.1`). One record per `(contextId, key)` pair, keyed
102/// `mem:<contextId>:<key>`; `list` is a `mem:<contextId>:` prefix scan. Durable
103/// user data → in [`BACKED_UP`].
104pub const MEMORY: &str = "memory";
105
106/// Rego policy modules for the Policy Decision Point (`policy/{upsert,list,
107/// delete,evaluate}`). One `policy::PolicyModule` per id, keyed `policy:<id>`;
108/// the active set is every enabled row, priority-ordered. Durable operator
109/// security config → in [`BACKED_UP`] (a lost policy set would silently drop
110/// enforcement on restore).
111pub const POLICY: &str = "policy";
112
113/// Task-execution consent for the PDP's `requireConsent` disposition: pending
114/// approvals keyed by payload digest, and granted consents a re-submitted task
115/// consumes. Distinct from [`CONSENT`] (messaging-bridge conversation consent).
116/// One `policy::consent::PendingTaskConsent` per `pending:<digest>` and
117/// `policy::consent::TaskConsentGrant` per `grant:<digest>:<requester>`.
118/// Durable operator-facing security state → [`BACKED_UP`].
119pub const TASK_CONSENT: &str = "task_consent";
120
121/// Durable reliable-messaging outbox backing `vti_common::outbox_store::`
122/// `VtiOutboxStore` for the delivery-layer `MessagingService` (D2 P2a
123/// cut-over). Holds `Guaranteed`-delivery outbox entries; dormant in P2a (all
124/// current sends are `BestEffort`) but wired so the drain/confirmation loops
125/// persist across restarts once P2b adds guaranteed VTA pushes. Runtime state,
126/// not backed up.
127pub const OUTBOX: &str = "outbox";
128
129/// Idempotency records for keyed Trust Tasks — one row per
130/// `(actor, idempotency-key)`, holding the request digest and, for tasks whose
131/// response may be replayed, the original response. Lets a client's retry of a
132/// lost reply converge on the first execution instead of producing a second
133/// durable effect.
134///
135/// Persistent rather than in-memory (unlike the `(actor, envelope-id)` replay
136/// cache it sits beside) because the window that matters is exactly the one a
137/// restart falls inside: the VTA processed the request, the reply was lost, and
138/// the client is still retrying. Swept on TTL by
139/// `vta_sweepers::idempotency_sweeper`. Runtime state, not backed up.
140pub const IDEMPOTENCY: &str = "idempotency";
141
142/// Every production keyspace. Partitioned by [`BACKED_UP`] +
143/// [`EXCLUDED_FROM_BACKUP`]; the [`tests::backup_partition_is_total`] guard
144/// asserts the partition stays exhaustive so a newly-added keyspace can't be
145/// silently omitted from the backup decision.
146pub const ALL: &[&str] = &[
147 INTERNAL_KEYS,
148 KEYS,
149 SESSIONS,
150 ACL,
151 CONTEXTS,
152 DID_TEMPLATES,
153 AUDIT,
154 IMPORTED_SECRETS,
155 CACHE,
156 VAULT,
157 SERVICE_STATE,
158 SEALED_NONCES,
159 BACKUP_BUNDLES,
160 WEBVH,
161 PASSKEY_VMS,
162 DRAINS,
163 SNAPSHOT,
164 BOOTSTRAP,
165 CONSENT,
166 CONSENT_APPROVERS,
167 ISSUED_CREDENTIALS,
168 MEMORY,
169 POLICY,
170 TASK_CONSENT,
171 OUTBOX,
172 IDEMPOTENCY,
173];
174
175/// Keyspaces whose contents a full `export_backup` captures (as typed
176/// collections — see `operations::backup`).
177pub const BACKED_UP: &[&str] = &[
178 KEYS,
179 ACL,
180 CONTEXTS,
181 AUDIT,
182 IMPORTED_SECRETS,
183 WEBVH,
184 CONSENT,
185 CONSENT_APPROVERS,
186 // Durable agent memory is user data and must survive a restore.
187 MEMORY,
188 // Operator security policy — must survive a restore, else enforcement
189 // silently reverts to whatever defaults boot-install provides.
190 POLICY,
191 // Task-consent grants are durable authorizations a re-submitted task
192 // consumes; losing them on restore would strand in-flight approvals.
193 TASK_CONSENT,
194];
195
196/// Keyspaces deliberately **not** in a backup.
197///
198/// Most are ephemeral / runtime / re-derivable: [`SESSIONS`], [`CACHE`],
199/// [`SEALED_NONCES`], [`SERVICE_STATE`], [`BACKUP_BUNDLES`], [`PASSKEY_VMS`],
200/// [`DRAINS`], [`SNAPSHOT`], [`BOOTSTRAP`]. [`DID_TEMPLATES`] and [`VAULT`]
201/// hold durable operator/holder state and are **known backup gaps** — a
202/// backup-fidelity follow-up should move them into [`BACKED_UP`], not leave
203/// them silently dropped.
204pub const EXCLUDED_FROM_BACKUP: &[&str] = &[
205 // Non-extractable internal signing keys. Excluding them is the feature:
206 // a backup that carried them would export keys the VTA guarantees never
207 // to export, and restoring one elsewhere would silently clone a signer.
208 INTERNAL_KEYS,
209 SESSIONS,
210 DID_TEMPLATES,
211 CACHE,
212 VAULT,
213 SERVICE_STATE,
214 SEALED_NONCES,
215 BACKUP_BUNDLES,
216 PASSKEY_VMS,
217 DRAINS,
218 SNAPSHOT,
219 BOOTSTRAP,
220 // Durable VTA-issued holder credentials. Like [`VAULT`], a known backup
221 // gap — a backup-fidelity follow-up should move it into [`BACKED_UP`].
222 ISSUED_CREDENTIALS,
223 // Reliable-messaging outbox: runtime delivery state, re-driven from live
224 // sends, not part of a state backup.
225 OUTBOX,
226 // Trust-Task idempotency records. Short-lived by construction (a retry
227 // window, not durable state) and scoped to the VTA that served the original
228 // request — restoring one elsewhere would claim to have already performed
229 // operations that instance never did.
230 IDEMPOTENCY,
231];
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use std::collections::BTreeSet;
237
238 /// The backup partition must be total and disjoint: every production
239 /// keyspace is either backed up or explicitly excluded. Adding a keyspace
240 /// to [`ALL`] without classifying it fails here — that's the point.
241 #[test]
242 fn backup_partition_is_total() {
243 let all: BTreeSet<&str> = ALL.iter().copied().collect();
244 let backed: BTreeSet<&str> = BACKED_UP.iter().copied().collect();
245 let excluded: BTreeSet<&str> = EXCLUDED_FROM_BACKUP.iter().copied().collect();
246
247 assert_eq!(all.len(), ALL.len(), "ALL has a duplicate");
248 assert!(
249 backed.is_disjoint(&excluded),
250 "a keyspace is both backed up and excluded: {:?}",
251 backed.intersection(&excluded).collect::<Vec<_>>()
252 );
253 let union: BTreeSet<&str> = backed.union(&excluded).copied().collect();
254 assert_eq!(
255 union, all,
256 "backup partition is not exhaustive — every keyspace in ALL must be in \
257 exactly one of BACKED_UP / EXCLUDED_FROM_BACKUP"
258 );
259 }
260}