commit 46d77b432290040e236273a01f3556c933c3732b
parent a907604040814954fc02fde21a4d1bb1a19f0beb
Author: triesap <tyson@radroots.org>
Date: Sun, 23 Aug 2026 21:56:56 +0000
state(rhi): define typed repository topology
Diffstat:
7 files changed, 908 insertions(+), 5 deletions(-)
diff --git a/README b/README
@@ -139,6 +139,18 @@ credential remain excluded from the state-backup contract. Actual online
backup, offline restore, and recovery execution remain owned by their later
ordered checkpoint and are not claimed by this boundary proof.
+RHI's state surface is partitioned into eighteen distinct non-forgeable typed
+repository capabilities bound to one already-opened `RhiStateHost`. Their
+closed topology covers source results, cursors, completions, admitted signed
+events, canonical mutations, provenance, dirty generations, reconciliation
+jobs and attempts, immutable manifests/projections/reports, supersession,
+signed attestation events, publication outbox/targets/attempts, and desired
+presence. Each capability has one exact backing-table identity and an
+append-only, compare-and-swap, or immutable write class. It exposes no raw
+pool, connection, transaction, SQL, path, or cloneable write authority.
+Schema migration and verification, CRUD behavior, backup/restore, network I/O,
+and task supervision remain owned by their later ordered checkpoints.
+
Validate the standalone crate through extbuild:
```text
diff --git a/contracts/services_hardening/state_repository_topology.v1.json b/contracts/services_hardening/state_repository_topology.v1.json
@@ -0,0 +1,35 @@
+{
+ "schema": "radroots.rhi.state-repository-topology",
+ "schema_version": 1,
+ "contract_version": 1,
+ "repository_count": 18,
+ "construction": "sealed_to_open_rhi_state_host",
+ "raw_sqlite_authority_exposed": false,
+ "repositories": [
+ { "kind": "source", "backing_table": "evidence_reconciliation_sources", "write_class": "append_only" },
+ { "kind": "source_cursor", "backing_table": "relay_checkpoints", "write_class": "compare_and_swap" },
+ { "kind": "source_completion", "backing_table": "evidence_reconciliation_sources", "write_class": "append_only" },
+ { "kind": "signed_event", "backing_table": "nostr_events", "write_class": "append_only" },
+ { "kind": "mutation", "backing_table": "trade_mutations", "write_class": "append_only" },
+ { "kind": "provenance", "backing_table": "relay_observations", "write_class": "append_only" },
+ { "kind": "dirty_trade", "backing_table": "trade_dirty_generations", "write_class": "compare_and_swap" },
+ { "kind": "reconciliation_job", "backing_table": "reconciliation_jobs", "write_class": "compare_and_swap" },
+ { "kind": "reconciliation_attempt", "backing_table": "evidence_reconciliations", "write_class": "append_only" },
+ { "kind": "evidence_manifest", "backing_table": "evidence_manifests", "write_class": "immutable" },
+ { "kind": "projection", "backing_table": "trade_projections", "write_class": "immutable" },
+ { "kind": "report", "backing_table": "attestation_reports", "write_class": "immutable" },
+ { "kind": "supersession", "backing_table": "attestation_reports", "write_class": "append_only" },
+ { "kind": "signed_attestation_event", "backing_table": "signed_attestation_events", "write_class": "immutable" },
+ { "kind": "publication_outbox", "backing_table": "publication_outbox", "write_class": "compare_and_swap" },
+ { "kind": "publication_target", "backing_table": "publication_targets", "write_class": "compare_and_swap" },
+ { "kind": "publication_attempt", "backing_table": "publication_attempts", "write_class": "append_only" },
+ { "kind": "desired_presence", "backing_table": "presence_desired_state", "write_class": "compare_and_swap" }
+ ],
+ "deferred_behavior": [
+ "schema_migration_and_verification",
+ "repository_crud",
+ "backup_restore_and_recovery",
+ "network_io",
+ "task_supervision"
+ ]
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -10,6 +10,7 @@ mod runtime_context;
mod state_catalog;
mod state_host;
mod state_metadata;
+mod state_repository;
pub use cli_v1::{
RhiBootstrapProfileV1, RhiCliInvocationV1, RhiCliOutputModeV1, RhiCliV1Error,
@@ -61,3 +62,15 @@ pub use state_metadata::{
RhiNormalizedConfigDigest, RhiStateMetadata, RhiStateMetadataError, RhiStateMetadataErrorKind,
RhiStatePolicyVersions,
};
+pub use state_repository::{
+ RHI_STATE_REPOSITORY_CONTRACT_VERSION, RHI_STATE_REPOSITORY_COUNT,
+ RhiDesiredPresenceRepository, RhiDirtyTradeRepository, RhiEvidenceManifestRepository,
+ RhiMutationRepository, RhiProjectionRepository, RhiProvenanceRepository,
+ RhiPublicationAttemptRepository, RhiPublicationOutboxRepository,
+ RhiPublicationTargetRepository, RhiReconciliationAttemptRepository,
+ RhiReconciliationJobRepository, RhiReportRepository, RhiSignedAttestationEventRepository,
+ RhiSignedEventRepository, RhiSourceCompletionRepository, RhiSourceCursorRepository,
+ RhiSourceRepository, RhiStateRepositories, RhiStateRepositoryDescriptor,
+ RhiStateRepositoryKind, RhiStateRepositoryWriteClass, RhiSupersessionRepository,
+ rhi_state_repository_descriptors,
+};
diff --git a/src/state_host.rs b/src/state_host.rs
@@ -10,8 +10,8 @@ use radroots_service_sqlite::{
use sqlx::{ConnectOptions, Connection, SqliteConnection, sqlite::SqliteConnectOptions};
use crate::{
- RHI_STATE_SCHEMA_VERSION, RhiRuntimeContext, RhiStateMetadata, rhi_migration_catalog,
- rhi_schema_catalog, validate_rhi_state_catalogs,
+ RHI_STATE_SCHEMA_VERSION, RhiRuntimeContext, RhiStateMetadata, RhiStateRepositories,
+ rhi_migration_catalog, rhi_schema_catalog, validate_rhi_state_catalogs,
};
/// Stable lifecycle mode of one opened RHI state host.
@@ -139,6 +139,12 @@ impl RhiStateHost {
&self.metadata
}
+ /// Returns the sealed family of typed repository capabilities.
+ #[must_use]
+ pub const fn repositories(&self) -> RhiStateRepositories<'_> {
+ RhiStateRepositories::new(self)
+ }
+
/// Drains the shared host and explicitly releases retained authority.
pub async fn close(&self) -> Result<(), RhiStateHostError> {
self.host
diff --git a/src/state_repository.rs b/src/state_repository.rs
@@ -0,0 +1,491 @@
+//! Sealed typed capability topology for RHI-owned state repositories.
+
+use core::fmt;
+
+use crate::RhiStateHost;
+
+/// Exact version of the RHI state-repository topology contract.
+pub const RHI_STATE_REPOSITORY_CONTRACT_VERSION: u32 = 1;
+
+/// Number of distinct typed repository capabilities in the v1 topology.
+pub const RHI_STATE_REPOSITORY_COUNT: usize = 18;
+
+/// Closed mutation class for one governed repository.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum RhiStateRepositoryWriteClass {
+ AppendOnly,
+ CompareAndSwap,
+ Immutable,
+}
+
+impl RhiStateRepositoryWriteClass {
+ /// Returns the exact machine-contract spelling.
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::AppendOnly => "append_only",
+ Self::CompareAndSwap => "compare_and_swap",
+ Self::Immutable => "immutable",
+ }
+ }
+}
+
+/// Closed inventory of RHI repository responsibilities.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum RhiStateRepositoryKind {
+ Source,
+ SourceCursor,
+ SourceCompletion,
+ SignedEvent,
+ Mutation,
+ Provenance,
+ DirtyTrade,
+ ReconciliationJob,
+ ReconciliationAttempt,
+ EvidenceManifest,
+ Projection,
+ Report,
+ Supersession,
+ SignedAttestationEvent,
+ PublicationOutbox,
+ PublicationTarget,
+ PublicationAttempt,
+ DesiredPresence,
+}
+
+impl RhiStateRepositoryKind {
+ /// Returns the exact machine-contract spelling.
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ descriptor(self).code
+ }
+
+ /// Returns the sole governed backing-table identity.
+ #[must_use]
+ pub const fn backing_table(self) -> &'static str {
+ descriptor(self).backing_table
+ }
+
+ /// Returns the closed mutation class.
+ #[must_use]
+ pub const fn write_class(self) -> RhiStateRepositoryWriteClass {
+ descriptor(self).write_class
+ }
+}
+
+/// Immutable description of one repository capability.
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct RhiStateRepositoryDescriptor {
+ kind: RhiStateRepositoryKind,
+ code: &'static str,
+ backing_table: &'static str,
+ write_class: RhiStateRepositoryWriteClass,
+}
+
+impl RhiStateRepositoryDescriptor {
+ const fn new(
+ kind: RhiStateRepositoryKind,
+ code: &'static str,
+ backing_table: &'static str,
+ write_class: RhiStateRepositoryWriteClass,
+ ) -> Self {
+ Self {
+ kind,
+ code,
+ backing_table,
+ write_class,
+ }
+ }
+
+ /// Returns the closed repository kind.
+ #[must_use]
+ pub const fn kind(self) -> RhiStateRepositoryKind {
+ self.kind
+ }
+
+ /// Returns the exact machine-contract spelling.
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ self.code
+ }
+
+ /// Returns the exact governed backing-table identity.
+ #[must_use]
+ pub const fn backing_table(self) -> &'static str {
+ self.backing_table
+ }
+
+ /// Returns the repository mutation class.
+ #[must_use]
+ pub const fn write_class(self) -> RhiStateRepositoryWriteClass {
+ self.write_class
+ }
+}
+
+impl fmt::Debug for RhiStateRepositoryDescriptor {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RhiStateRepositoryDescriptor")
+ .field("kind", &self.kind)
+ .field("write_class", &self.write_class)
+ .finish()
+ }
+}
+
+use RhiStateRepositoryKind as Kind;
+use RhiStateRepositoryWriteClass as Write;
+
+const DESCRIPTORS: [RhiStateRepositoryDescriptor; RHI_STATE_REPOSITORY_COUNT] = [
+ RhiStateRepositoryDescriptor::new(
+ Kind::Source,
+ "source",
+ "evidence_reconciliation_sources",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::SourceCursor,
+ "source_cursor",
+ "relay_checkpoints",
+ Write::CompareAndSwap,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::SourceCompletion,
+ "source_completion",
+ "evidence_reconciliation_sources",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::SignedEvent,
+ "signed_event",
+ "nostr_events",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::Mutation,
+ "mutation",
+ "trade_mutations",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::Provenance,
+ "provenance",
+ "relay_observations",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::DirtyTrade,
+ "dirty_trade",
+ "trade_dirty_generations",
+ Write::CompareAndSwap,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::ReconciliationJob,
+ "reconciliation_job",
+ "reconciliation_jobs",
+ Write::CompareAndSwap,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::ReconciliationAttempt,
+ "reconciliation_attempt",
+ "evidence_reconciliations",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::EvidenceManifest,
+ "evidence_manifest",
+ "evidence_manifests",
+ Write::Immutable,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::Projection,
+ "projection",
+ "trade_projections",
+ Write::Immutable,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::Report,
+ "report",
+ "attestation_reports",
+ Write::Immutable,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::Supersession,
+ "supersession",
+ "attestation_reports",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::SignedAttestationEvent,
+ "signed_attestation_event",
+ "signed_attestation_events",
+ Write::Immutable,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::PublicationOutbox,
+ "publication_outbox",
+ "publication_outbox",
+ Write::CompareAndSwap,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::PublicationTarget,
+ "publication_target",
+ "publication_targets",
+ Write::CompareAndSwap,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::PublicationAttempt,
+ "publication_attempt",
+ "publication_attempts",
+ Write::AppendOnly,
+ ),
+ RhiStateRepositoryDescriptor::new(
+ Kind::DesiredPresence,
+ "desired_presence",
+ "presence_desired_state",
+ Write::CompareAndSwap,
+ ),
+];
+
+const fn descriptor(kind: RhiStateRepositoryKind) -> RhiStateRepositoryDescriptor {
+ DESCRIPTORS[kind as usize]
+}
+
+/// Returns the exact ordered v1 repository topology.
+#[must_use]
+pub const fn rhi_state_repository_descriptors()
+-> &'static [RhiStateRepositoryDescriptor; RHI_STATE_REPOSITORY_COUNT] {
+ &DESCRIPTORS
+}
+
+/// Sealed repository family bound to one already-opened RHI state host.
+///
+/// Construction is available only through [`RhiStateHost::repositories`]:
+///
+/// ```compile_fail
+/// use rhi::RhiStateRepositories;
+///
+/// let _ = RhiStateRepositories { host: todo!() };
+/// ```
+///
+/// Distinct repository responsibilities cannot be interchanged:
+///
+/// ```compile_fail
+/// use rhi::{RhiMutationRepository, RhiStateRepositories};
+///
+/// fn wrong(repository: &RhiStateRepositories<'_>) {
+/// let _: RhiMutationRepository<'_> = repository.sources();
+/// }
+/// ```
+pub struct RhiStateRepositories<'host> {
+ host: &'host RhiStateHost,
+}
+
+impl<'host> RhiStateRepositories<'host> {
+ pub(crate) const fn new(host: &'host RhiStateHost) -> Self {
+ Self { host }
+ }
+
+ /// Returns typed source-result access.
+ #[must_use]
+ pub const fn sources(&self) -> RhiSourceRepository<'host> {
+ RhiSourceRepository { host: self.host }
+ }
+
+ /// Returns typed source-cursor access.
+ #[must_use]
+ pub const fn source_cursors(&self) -> RhiSourceCursorRepository<'host> {
+ RhiSourceCursorRepository { host: self.host }
+ }
+
+ /// Returns typed source-completion access.
+ #[must_use]
+ pub const fn source_completions(&self) -> RhiSourceCompletionRepository<'host> {
+ RhiSourceCompletionRepository { host: self.host }
+ }
+
+ /// Returns typed admitted-event access.
+ #[must_use]
+ pub const fn signed_events(&self) -> RhiSignedEventRepository<'host> {
+ RhiSignedEventRepository { host: self.host }
+ }
+
+ /// Returns typed canonical-mutation access.
+ #[must_use]
+ pub const fn mutations(&self) -> RhiMutationRepository<'host> {
+ RhiMutationRepository { host: self.host }
+ }
+
+ /// Returns typed provenance-observation access.
+ #[must_use]
+ pub const fn provenance(&self) -> RhiProvenanceRepository<'host> {
+ RhiProvenanceRepository { host: self.host }
+ }
+
+ /// Returns typed dirty-trade generation access.
+ #[must_use]
+ pub const fn dirty_trades(&self) -> RhiDirtyTradeRepository<'host> {
+ RhiDirtyTradeRepository { host: self.host }
+ }
+
+ /// Returns typed reconciliation-job access.
+ #[must_use]
+ pub const fn reconciliation_jobs(&self) -> RhiReconciliationJobRepository<'host> {
+ RhiReconciliationJobRepository { host: self.host }
+ }
+
+ /// Returns typed reconciliation-attempt access.
+ #[must_use]
+ pub const fn reconciliation_attempts(&self) -> RhiReconciliationAttemptRepository<'host> {
+ RhiReconciliationAttemptRepository { host: self.host }
+ }
+
+ /// Returns typed immutable evidence-manifest access.
+ #[must_use]
+ pub const fn evidence_manifests(&self) -> RhiEvidenceManifestRepository<'host> {
+ RhiEvidenceManifestRepository { host: self.host }
+ }
+
+ /// Returns typed immutable projection access.
+ #[must_use]
+ pub const fn projections(&self) -> RhiProjectionRepository<'host> {
+ RhiProjectionRepository { host: self.host }
+ }
+
+ /// Returns typed immutable report access.
+ #[must_use]
+ pub const fn reports(&self) -> RhiReportRepository<'host> {
+ RhiReportRepository { host: self.host }
+ }
+
+ /// Returns typed append-only supersession access.
+ #[must_use]
+ pub const fn supersessions(&self) -> RhiSupersessionRepository<'host> {
+ RhiSupersessionRepository { host: self.host }
+ }
+
+ /// Returns typed immutable signed-attestation-event access.
+ #[must_use]
+ pub const fn signed_attestation_events(&self) -> RhiSignedAttestationEventRepository<'host> {
+ RhiSignedAttestationEventRepository { host: self.host }
+ }
+
+ /// Returns typed publication-outbox access.
+ #[must_use]
+ pub const fn publication_outbox(&self) -> RhiPublicationOutboxRepository<'host> {
+ RhiPublicationOutboxRepository { host: self.host }
+ }
+
+ /// Returns typed immutable-target workflow access.
+ #[must_use]
+ pub const fn publication_targets(&self) -> RhiPublicationTargetRepository<'host> {
+ RhiPublicationTargetRepository { host: self.host }
+ }
+
+ /// Returns typed append-only publication-attempt access.
+ #[must_use]
+ pub const fn publication_attempts(&self) -> RhiPublicationAttemptRepository<'host> {
+ RhiPublicationAttemptRepository { host: self.host }
+ }
+
+ /// Returns typed desired-presence access.
+ #[must_use]
+ pub const fn desired_presence(&self) -> RhiDesiredPresenceRepository<'host> {
+ RhiDesiredPresenceRepository { host: self.host }
+ }
+}
+
+impl fmt::Debug for RhiStateRepositories<'_> {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RhiStateRepositories")
+ .field("mode", &self.host.mode())
+ .field("state", &"[sealed]")
+ .finish()
+ }
+}
+
+macro_rules! repository_handle {
+ ($name:ident, $kind:ident) => {
+ #[doc = "One non-forgeable typed view of an opened RHI state host."]
+ pub struct $name<'host> {
+ host: &'host RhiStateHost,
+ }
+
+ impl $name<'_> {
+ /// Returns this repository's closed kind.
+ #[must_use]
+ pub const fn kind(&self) -> RhiStateRepositoryKind {
+ Kind::$kind
+ }
+
+ /// Returns this repository's immutable descriptor.
+ #[must_use]
+ pub const fn descriptor(&self) -> RhiStateRepositoryDescriptor {
+ descriptor(self.kind())
+ }
+ }
+
+ impl fmt::Debug for $name<'_> {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct(stringify!($name))
+ .field("mode", &self.host.mode())
+ .field("state", &"[sealed]")
+ .finish()
+ }
+ }
+ };
+}
+
+repository_handle!(RhiSourceRepository, Source);
+repository_handle!(RhiSourceCursorRepository, SourceCursor);
+repository_handle!(RhiSourceCompletionRepository, SourceCompletion);
+repository_handle!(RhiSignedEventRepository, SignedEvent);
+repository_handle!(RhiMutationRepository, Mutation);
+repository_handle!(RhiProvenanceRepository, Provenance);
+repository_handle!(RhiDirtyTradeRepository, DirtyTrade);
+repository_handle!(RhiReconciliationJobRepository, ReconciliationJob);
+repository_handle!(RhiReconciliationAttemptRepository, ReconciliationAttempt);
+repository_handle!(RhiEvidenceManifestRepository, EvidenceManifest);
+repository_handle!(RhiProjectionRepository, Projection);
+repository_handle!(RhiReportRepository, Report);
+repository_handle!(RhiSupersessionRepository, Supersession);
+repository_handle!(RhiSignedAttestationEventRepository, SignedAttestationEvent);
+repository_handle!(RhiPublicationOutboxRepository, PublicationOutbox);
+repository_handle!(RhiPublicationTargetRepository, PublicationTarget);
+repository_handle!(RhiPublicationAttemptRepository, PublicationAttempt);
+repository_handle!(RhiDesiredPresenceRepository, DesiredPresence);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn descriptors_are_closed_unique_and_nonempty() {
+ assert_eq!(DESCRIPTORS.len(), RHI_STATE_REPOSITORY_COUNT);
+ for (index, descriptor) in DESCRIPTORS.iter().enumerate() {
+ assert_eq!(descriptor.kind() as usize, index);
+ assert!(!descriptor.code().is_empty());
+ assert!(!descriptor.backing_table().is_empty());
+ assert_eq!(descriptor.kind().code(), descriptor.code());
+ assert_eq!(
+ descriptor.kind().backing_table(),
+ descriptor.backing_table()
+ );
+ assert_eq!(descriptor.kind().write_class(), descriptor.write_class());
+ assert_eq!(
+ descriptor.write_class().code(),
+ match descriptor.write_class() {
+ Write::AppendOnly => "append_only",
+ Write::CompareAndSwap => "compare_and_swap",
+ Write::Immutable => "immutable",
+ }
+ );
+ for other in &DESCRIPTORS[index + 1..] {
+ assert_ne!(descriptor.kind(), other.kind());
+ assert_ne!(descriptor.code(), other.code());
+ }
+ }
+ }
+}
diff --git a/tests/services_hardening_state_host.rs b/tests/services_hardening_state_host.rs
@@ -7,9 +7,9 @@ use radroots_service_sqlite::{MigrationAppliedAtUnixSeconds, MigrationBuildIdent
use radroots_storage::event::SourceGeneration;
use rhi::{
RadrootsHostEnvironment, RadrootsPathResolver, RadrootsPlatform, RhiConfigProfile,
- RhiStateHostErrorKind, RhiStateHostMode, RhiStateMetadata, initialize_rhi_state,
- open_rhi_state_inspection, open_rhi_state_read_write, parse_rhi_cli_v1_from,
- parse_rhi_config_v1, resolve_rhi_runtime_context,
+ RhiStateHostErrorKind, RhiStateHostMode, RhiStateMetadata, RhiStateRepositoryKind,
+ initialize_rhi_state, open_rhi_state_inspection, open_rhi_state_read_write,
+ parse_rhi_cli_v1_from, parse_rhi_config_v1, resolve_rhi_runtime_context,
};
const EXAMPLE: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
@@ -124,6 +124,83 @@ async fn initialize_is_create_new_and_both_existing_open_modes_close_explicitly(
.await
.expect("existing inspection state");
assert_eq!(inspection.mode(), RhiStateHostMode::ReadOnlyInspection);
+ let repositories = inspection.repositories();
+ assert_eq!(
+ format!("{repositories:?}"),
+ "RhiStateRepositories { mode: ReadOnlyInspection, state: \"[sealed]\" }"
+ );
+ assert_eq!(
+ repositories.sources().kind(),
+ RhiStateRepositoryKind::Source
+ );
+ assert_eq!(
+ repositories.source_cursors().kind(),
+ RhiStateRepositoryKind::SourceCursor
+ );
+ assert_eq!(
+ repositories.source_completions().kind(),
+ RhiStateRepositoryKind::SourceCompletion
+ );
+ assert_eq!(
+ repositories.signed_events().kind(),
+ RhiStateRepositoryKind::SignedEvent
+ );
+ assert_eq!(
+ repositories.mutations().kind(),
+ RhiStateRepositoryKind::Mutation
+ );
+ assert_eq!(
+ repositories.provenance().kind(),
+ RhiStateRepositoryKind::Provenance
+ );
+ assert_eq!(
+ repositories.dirty_trades().kind(),
+ RhiStateRepositoryKind::DirtyTrade
+ );
+ assert_eq!(
+ repositories.reconciliation_jobs().kind(),
+ RhiStateRepositoryKind::ReconciliationJob
+ );
+ assert_eq!(
+ repositories.reconciliation_attempts().kind(),
+ RhiStateRepositoryKind::ReconciliationAttempt
+ );
+ assert_eq!(
+ repositories.evidence_manifests().kind(),
+ RhiStateRepositoryKind::EvidenceManifest
+ );
+ assert_eq!(
+ repositories.projections().kind(),
+ RhiStateRepositoryKind::Projection
+ );
+ assert_eq!(
+ repositories.reports().kind(),
+ RhiStateRepositoryKind::Report
+ );
+ assert_eq!(
+ repositories.supersessions().kind(),
+ RhiStateRepositoryKind::Supersession
+ );
+ assert_eq!(
+ repositories.signed_attestation_events().kind(),
+ RhiStateRepositoryKind::SignedAttestationEvent
+ );
+ assert_eq!(
+ repositories.publication_outbox().kind(),
+ RhiStateRepositoryKind::PublicationOutbox
+ );
+ assert_eq!(
+ repositories.publication_targets().kind(),
+ RhiStateRepositoryKind::PublicationTarget
+ );
+ assert_eq!(
+ repositories.publication_attempts().kind(),
+ RhiStateRepositoryKind::PublicationAttempt
+ );
+ assert_eq!(
+ repositories.desired_presence().kind(),
+ RhiStateRepositoryKind::DesiredPresence
+ );
inspection.close().await.expect("inspection close");
let writer = open_rhi_state_read_write(&runtime, &metadata, applied_at, &build)
diff --git a/tests/services_hardening_state_repository_topology.rs b/tests/services_hardening_state_repository_topology.rs
@@ -0,0 +1,269 @@
+#![forbid(unsafe_code)]
+
+use rhi::{
+ RHI_STATE_REPOSITORY_CONTRACT_VERSION, RHI_STATE_REPOSITORY_COUNT, RhiStateRepositoryKind,
+ RhiStateRepositoryWriteClass, rhi_state_repository_descriptors,
+};
+use serde_json::json;
+
+const CONTRACT: &str =
+ include_str!("../contracts/services_hardening/state_repository_topology.v1.json");
+const LIB_SOURCE: &str = include_str!("../src/lib.rs");
+const HOST_SOURCE: &str = include_str!("../src/state_host.rs");
+const REPOSITORY_SOURCE: &str = include_str!("../src/state_repository.rs");
+
+#[test]
+fn machine_contract_and_typed_descriptor_inventory_are_exact() {
+ let contract: serde_json::Value = serde_json::from_str(CONTRACT).expect("repository contract");
+ assert_eq!(contract["schema"], "radroots.rhi.state-repository-topology");
+ assert_eq!(contract["schema_version"], 1);
+ assert_eq!(
+ contract["contract_version"],
+ RHI_STATE_REPOSITORY_CONTRACT_VERSION
+ );
+ assert_eq!(contract["repository_count"], RHI_STATE_REPOSITORY_COUNT);
+ assert_eq!(contract["construction"], "sealed_to_open_rhi_state_host");
+ assert_eq!(contract["raw_sqlite_authority_exposed"], false);
+ assert_eq!(
+ contract["deferred_behavior"],
+ json!([
+ "schema_migration_and_verification",
+ "repository_crud",
+ "backup_restore_and_recovery",
+ "network_io",
+ "task_supervision"
+ ])
+ );
+ assert_eq!(
+ contract
+ .as_object()
+ .expect("contract object")
+ .keys()
+ .map(String::as_str)
+ .collect::<std::collections::BTreeSet<_>>(),
+ [
+ "schema",
+ "schema_version",
+ "contract_version",
+ "repository_count",
+ "construction",
+ "raw_sqlite_authority_exposed",
+ "repositories",
+ "deferred_behavior",
+ ]
+ .into_iter()
+ .collect()
+ );
+
+ let descriptors = rhi_state_repository_descriptors();
+ let actual = descriptors
+ .iter()
+ .map(|descriptor| {
+ json!({
+ "kind": descriptor.code(),
+ "backing_table": descriptor.backing_table(),
+ "write_class": descriptor.write_class().code(),
+ })
+ })
+ .collect::<Vec<_>>();
+ assert_eq!(
+ contract["repositories"]
+ .as_array()
+ .expect("repository array"),
+ &actual
+ );
+ assert_eq!(descriptors.len(), RHI_STATE_REPOSITORY_COUNT);
+ for repository in contract["repositories"]
+ .as_array()
+ .expect("repository array")
+ {
+ assert_eq!(
+ repository
+ .as_object()
+ .expect("repository object")
+ .keys()
+ .map(String::as_str)
+ .collect::<std::collections::BTreeSet<_>>(),
+ ["kind", "backing_table", "write_class"]
+ .into_iter()
+ .collect()
+ );
+ }
+}
+
+#[test]
+fn repository_kinds_are_closed_ordered_and_cross_bound() {
+ use RhiStateRepositoryKind as Kind;
+ use RhiStateRepositoryWriteClass as Write;
+
+ let expected = [
+ (
+ Kind::Source,
+ "source",
+ "evidence_reconciliation_sources",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::SourceCursor,
+ "source_cursor",
+ "relay_checkpoints",
+ Write::CompareAndSwap,
+ ),
+ (
+ Kind::SourceCompletion,
+ "source_completion",
+ "evidence_reconciliation_sources",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::SignedEvent,
+ "signed_event",
+ "nostr_events",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::Mutation,
+ "mutation",
+ "trade_mutations",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::Provenance,
+ "provenance",
+ "relay_observations",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::DirtyTrade,
+ "dirty_trade",
+ "trade_dirty_generations",
+ Write::CompareAndSwap,
+ ),
+ (
+ Kind::ReconciliationJob,
+ "reconciliation_job",
+ "reconciliation_jobs",
+ Write::CompareAndSwap,
+ ),
+ (
+ Kind::ReconciliationAttempt,
+ "reconciliation_attempt",
+ "evidence_reconciliations",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::EvidenceManifest,
+ "evidence_manifest",
+ "evidence_manifests",
+ Write::Immutable,
+ ),
+ (
+ Kind::Projection,
+ "projection",
+ "trade_projections",
+ Write::Immutable,
+ ),
+ (
+ Kind::Report,
+ "report",
+ "attestation_reports",
+ Write::Immutable,
+ ),
+ (
+ Kind::Supersession,
+ "supersession",
+ "attestation_reports",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::SignedAttestationEvent,
+ "signed_attestation_event",
+ "signed_attestation_events",
+ Write::Immutable,
+ ),
+ (
+ Kind::PublicationOutbox,
+ "publication_outbox",
+ "publication_outbox",
+ Write::CompareAndSwap,
+ ),
+ (
+ Kind::PublicationTarget,
+ "publication_target",
+ "publication_targets",
+ Write::CompareAndSwap,
+ ),
+ (
+ Kind::PublicationAttempt,
+ "publication_attempt",
+ "publication_attempts",
+ Write::AppendOnly,
+ ),
+ (
+ Kind::DesiredPresence,
+ "desired_presence",
+ "presence_desired_state",
+ Write::CompareAndSwap,
+ ),
+ ];
+ for (descriptor, (kind, code, table, write_class)) in
+ rhi_state_repository_descriptors().iter().zip(expected)
+ {
+ assert_eq!(descriptor.kind(), kind);
+ assert_eq!(descriptor.code(), code);
+ assert_eq!(descriptor.backing_table(), table);
+ assert_eq!(descriptor.write_class(), write_class);
+ }
+}
+
+#[test]
+fn capabilities_are_private_sealed_and_defer_unowned_behavior() {
+ assert!(LIB_SOURCE.contains("mod state_repository;"));
+ assert!(!LIB_SOURCE.contains("pub mod state_repository;"));
+ assert!(HOST_SOURCE.contains("pub const fn repositories(&self) -> RhiStateRepositories<'_>"));
+ for required in [
+ "pub const fn sources(&self) -> RhiSourceRepository<'host>",
+ "pub const fn source_cursors(&self) -> RhiSourceCursorRepository<'host>",
+ "pub const fn source_completions(&self) -> RhiSourceCompletionRepository<'host>",
+ "pub const fn signed_events(&self) -> RhiSignedEventRepository<'host>",
+ "pub const fn mutations(&self) -> RhiMutationRepository<'host>",
+ "pub const fn provenance(&self) -> RhiProvenanceRepository<'host>",
+ "pub const fn dirty_trades(&self) -> RhiDirtyTradeRepository<'host>",
+ "pub const fn reconciliation_jobs(&self) -> RhiReconciliationJobRepository<'host>",
+ "pub const fn reconciliation_attempts(&self) -> RhiReconciliationAttemptRepository<'host>",
+ "pub const fn evidence_manifests(&self) -> RhiEvidenceManifestRepository<'host>",
+ "pub const fn projections(&self) -> RhiProjectionRepository<'host>",
+ "pub const fn reports(&self) -> RhiReportRepository<'host>",
+ "pub const fn supersessions(&self) -> RhiSupersessionRepository<'host>",
+ "pub const fn signed_attestation_events(&self) -> RhiSignedAttestationEventRepository<'host>",
+ "pub const fn publication_outbox(&self) -> RhiPublicationOutboxRepository<'host>",
+ "pub const fn publication_targets(&self) -> RhiPublicationTargetRepository<'host>",
+ "pub const fn publication_attempts(&self) -> RhiPublicationAttemptRepository<'host>",
+ "pub const fn desired_presence(&self) -> RhiDesiredPresenceRepository<'host>",
+ ] {
+ assert!(REPOSITORY_SOURCE.contains(required), "missing {required}");
+ }
+ for forbidden in [
+ "SqlitePool",
+ "SqliteConnection",
+ "ServiceSqliteTransaction",
+ "sqlx::",
+ "rusqlite::",
+ "CREATE TABLE",
+ "INSERT INTO",
+ "UPDATE ",
+ "DELETE FROM",
+ "std::fs",
+ "std::path",
+ "std::env",
+ "tokio::",
+ "nostr::",
+ "Deref",
+ "AsRef",
+ ] {
+ assert!(
+ !REPOSITORY_SOURCE.contains(forbidden),
+ "premature or escaping repository authority {forbidden}"
+ );
+ }
+}