Skip to main content

clawft_kernel/
governance_project.rs

1//! Project attribution of governance requests (ADR-103 A6, Phase 2 package I).
2//!
3//! A kernel serves at most one project. The daemon records that project once
4//! at boot from the only source it trusts for it (the kernel's own bound
5//! project, [`AttestSource::BoundKernel`]) with [`set_instance_project`].
6//! Every [`GovernanceRequest`] is then attributed from that attestation, and
7//! never from request parameters:
8//!
9//! * [`GovernanceRequest::new`] and the production struct-literal sites call
10//!   [`GovernanceRequest::attributed`], which sets `principal.project_id` /
11//!   `instance_id` and the `project_id` context key from the attestation, and
12//!   drops any `project_id` the caller put in the context;
13//! * [`GovernanceRequest::with_context_entry`] refuses the reserved keys
14//!   ([`RESERVED_CONTEXT_KEYS`]), so a client-supplied map cannot forge them;
15//! * [`GovernanceRequest::resolved_principal`] (what the engine evaluates and
16//!   the chain records) re-stamps from the same attestation.
17//!
18//! Honest limit: an attestation is only as strong as the transport that
19//! produced it. A same-uid local process can still lie through
20//! `Request.project` on the claim path of a daemon that has no binding; this
21//! is an isolation guard between one user's projects, not a boundary against
22//! a hostile local process (Phase 3 peer credentials, Phase 4 sandboxes).
23//!
24//! # GovernanceRequest construction sites (production code)
25//!
26//! None derives a project from request parameters. The population test
27//! `weave/tests/project_principal_population.rs` greps for new ones.
28//!
29//! | Site | How it is attributed |
30//! |---|---|
31//! | `GovernanceRequest::new` (`http_api`, `profile_store`, `hnsw_service`, `causal`, `wasm_runner`) | `attributed()` inside `new` |
32//! | `gate::GovernanceGate::check` | literal; strips reserved context keys, `attributed()` |
33//! | `workload_governance::gate::WorkloadGate::decide` | literal; `attributed_with` the gate's / instance attestation |
34//! | `governance.rs` unit tests | explicit, test-only |
35
36use std::sync::OnceLock;
37
38use crate::governance::{AttestSource, GovernanceRequest, ProjectAttestation};
39
40/// Context keys only the kernel may set.
41pub const RESERVED_CONTEXT_KEYS: &[&str] = &["project_id", "instance_id"];
42
43static INSTANCE: OnceLock<(ProjectAttestation, String)> = OnceLock::new();
44
45/// Record the project this kernel serves and its node id (daemon boot,
46/// once). Refuses anything but a [`AttestSource::BoundKernel`] attestation;
47/// returns `false` if one is already set.
48pub fn set_instance_project(att: ProjectAttestation, instance_id: impl Into<String>) -> bool {
49    att.source() == AttestSource::BoundKernel && INSTANCE.set((att, instance_id.into())).is_ok()
50}
51
52/// The project this kernel serves, if it has been attested.
53pub fn instance_project() -> Option<(&'static ProjectAttestation, &'static str)> {
54    INSTANCE.get().map(|(a, i)| (a, i.as_str()))
55}
56
57impl GovernanceRequest {
58    /// Attribute this request to the kernel's attested project (if any).
59    pub fn attributed(self) -> Self {
60        let inst = instance_project();
61        self.attributed_with(inst.map(|(a, _)| a), inst.map(|(_, i)| i))
62    }
63
64    /// [`attributed`](Self::attributed) with an explicit attestation. With
65    /// `None` the request ends up with no `project_id` / `instance_id` in its
66    /// context or principal (the user daemon attributes nothing).
67    pub fn attributed_with(mut self, att: Option<&ProjectAttestation>, instance: Option<&str>) -> Self {
68        for k in RESERVED_CONTEXT_KEYS {
69            self.context.remove(*k);
70        }
71        let mut p = self.base_principal().without_attribution();
72        if let Some(att) = att {
73            p = p.with_project(att);
74            self.context.insert("project_id".into(), att.project_id().to_owned());
75        }
76        if let Some(i) = instance {
77            p = p.with_instance(i);
78            self.context.insert("instance_id".into(), i.to_owned());
79        }
80        self.principal = Some(p);
81        self
82    }
83}
84
85#[cfg(test)]
86#[path = "governance_project_tests.rs"]
87mod tests;