Add offline resource capability discovery

This commit is contained in:
Eric Wendland 2026-07-18 17:21:12 +02:00
commit 47d16eb714
6 changed files with 200 additions and 12 deletions

View file

@ -5,7 +5,7 @@ mod local_control;
mod local_transport;
mod peer_client;
mod peer_control;
mod resource_contracts;
pub mod resource_contracts;
mod runtime;
pub mod service;
mod sync;

View file

@ -1,20 +1,20 @@
//! Reviewable resource-family contracts for daemon handlers.
//!
//! These contracts are intentionally small and static. They make every
//! resource family name its resource ID pattern, remote capabilities, and
//! resource family name its resource ID pattern, resource-scoped capabilities, and
//! mutation or host-access points in one place while the implementation
//! continues to live in the focused handler functions in `lib.rs` and
//! `local_control.rs`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ResourceModuleContract {
pub(crate) family: &'static str,
pub(crate) resource_ids: &'static [&'static str],
pub(crate) capabilities: &'static [&'static str],
pub(crate) mutation_points: &'static [&'static str],
pub struct ResourceModuleContract {
pub family: &'static str,
pub resource_ids: &'static [&'static str],
pub capabilities: &'static [&'static str],
pub mutation_points: &'static [&'static str],
}
pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
pub const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
ResourceModuleContract {
family: "cas-file-root",
resource_ids: &["resource:cas:local", "resource:cas-tree:<name>"],
@ -29,7 +29,12 @@ pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
ResourceModuleContract {
family: "kv",
resource_ids: &["resource:kv:<name>"],
capabilities: &["kv.read", "kv.write_prefix:<prefix>", "kv.write_key:<key>"],
capabilities: &[
"kv.read",
"kv.write",
"kv.write_prefix:<prefix>",
"kv.write_key:<key>",
],
mutation_points: &[
"local SQLite-backed KV create/set",
"authorized sync imports newer or equal timestamp entries idempotently",
@ -87,7 +92,14 @@ pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
"resource:ssh-proxy:local",
],
capabilities: &[
"ssh_cert.request",
"ssh_cert.read",
"ssh_cert.approve",
"ssh_cert.import",
"ssh_cert.sync",
"ssh_revocation.publish",
"ssh_revocation.read",
"ssh_revocation.import",
"ssh_revocation.sync",
"ssh_proxy.connect",
"ssh_proxy.admin_shell",
@ -102,7 +114,7 @@ pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
ResourceModuleContract {
family: "overlay",
resource_ids: &["resource:overlay:<name>"],
capabilities: &["overlay.route"],
capabilities: &["overlay.join", "overlay.route"],
mutation_points: &[
"local overlay network membership metadata",
"daemon-lifetime packet queue and optional TUN/Wintun injection",
@ -111,7 +123,8 @@ pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
},
];
pub(crate) fn contracts() -> &'static [ResourceModuleContract] {
#[must_use]
pub fn contracts() -> &'static [ResourceModuleContract] {
RESOURCE_MODULE_CONTRACTS
}
@ -160,4 +173,43 @@ mod tests {
);
}
}
#[test]
fn catalog_includes_operator_facing_grant_vocabulary() {
let capabilities = RESOURCE_MODULE_CONTRACTS
.iter()
.flat_map(|contract| contract.capabilities.iter().copied())
.collect::<std::collections::BTreeSet<_>>();
for expected in [
"cas.fetch",
"kv.read",
"kv.write",
"db.sync",
"document.read",
"pubsub.publish",
"pubsub.subscribe",
"pipe.connect",
"pipe.listen",
"pipe.forward",
"ssh_cert.request",
"ssh_cert.read",
"ssh_cert.approve",
"ssh_cert.import",
"ssh_cert.sync",
"ssh_revocation.publish",
"ssh_revocation.read",
"ssh_revocation.import",
"ssh_revocation.sync",
"ssh_proxy.connect",
"ssh_proxy.admin_shell",
"overlay.join",
"overlay.route",
] {
assert!(
capabilities.contains(expected),
"missing capability {expected}"
);
}
}
}