Skip to main content

newt_core/agentic/
crew_attest.rs

1//! crew_attest.rs — the human-presence (`attest`) authority surface for the
2//! crew/team tools (#479, ROADMAP 23.2).
3//!
4//! Wraps agent-bridle's `step_up` (the merged `attest` primitive, agent-bridle#24)
5//! for the crew. A crew **dispatch** is an *amplify* — it grants a sub-agent the
6//! authority to act — which §7.5 (knowledge#40) says must ride a **live human
7//! gesture**: the `{allow, attest, deny}` surface, not a bare env toggle.
8//! `compose_roster` only *proposes* (no effects), so it needs nothing.
9//!
10//! This is the **structure** — the decision surface + the policy. The real Passkey
11//! enforcement (a WebAuthn `DischargeVerifier` + the ceremony) lands with **BOOT**
12//! (#472). Today the established presence is `Prompt` (the operator's `/team`
13//! enable — a soft human affirmation, exactly `Presence::Prompt`); a
14//! `Passkey`-required action surfaces `NeedsAttest`, awaiting BOOT's verifier.
15//!
16//! Pure (no I/O); the caller surfaces `NeedsAttest` to the overseer.
17
18use agent_bridle::{AttestRequirement, CallRequest, Rule, StepUpPolicy};
19
20// Re-export the presence ladder so callers (e.g. the CLI's LocalCrewRunner) can
21// name the established presence without a direct agent-bridle dependency.
22pub use agent_bridle::Presence;
23
24/// The default crew/team step-up policy. `crew`/`team` dispatch requires a human
25/// gesture (`Prompt` today; raise to `Passkey` for amplifying crews once BOOT
26/// lands); `compose_roster` only proposes, so it needs none. Unknown crew ops
27/// fail *toward* attestation (the default demands a gesture).
28#[must_use]
29pub fn crew_step_up_policy() -> StepUpPolicy {
30    StepUpPolicy::new(
31        vec![
32            Rule {
33                selector: "compose_roster".to_string(),
34                requirement: AttestRequirement::NONE,
35            },
36            Rule {
37                selector: "crew".to_string(),
38                requirement: AttestRequirement::presence(Presence::Prompt),
39            },
40            Rule {
41                selector: "team".to_string(),
42                requirement: AttestRequirement::presence(Presence::Prompt),
43            },
44        ],
45        AttestRequirement::presence(Presence::Prompt),
46    )
47}
48
49/// The crew authority decision under an established human presence.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum CrewAuthz {
52    /// The established presence satisfies the policy — run it.
53    Allow,
54    /// Hold: needs a human gesture of at least this strength (the §7.5 `attest`).
55    /// The overseer surfaces it; post-BOOT it drives the discharge ceremony.
56    NeedsAttest(Presence),
57}
58
59/// Decide whether `op` (over `resource`) may run given the presence the session
60/// has already established. `Prompt` = the operator's `/team` enable; `Passkey` =
61/// a hardware gesture. Pure — the caller acts on `NeedsAttest`.
62#[must_use]
63pub fn crew_authz(
64    policy: &StepUpPolicy,
65    op: &str,
66    resource: &str,
67    established: Presence,
68) -> CrewAuthz {
69    let req = policy.required_for(&CallRequest::new(op, serde_json::Value::Null, resource));
70    if req.demands_gesture() && established < req.presence {
71        CrewAuthz::NeedsAttest(req.presence)
72    } else {
73        CrewAuthz::Allow
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn compose_roster_never_needs_a_gesture() {
83        let p = crew_step_up_policy();
84        // Even with NO established presence, proposing a roster is allowed (no effects).
85        assert_eq!(
86            crew_authz(&p, "compose_roster", "", Presence::None),
87            CrewAuthz::Allow
88        );
89    }
90
91    #[test]
92    fn crew_dispatch_needs_at_least_prompt() {
93        let p = crew_step_up_policy();
94        // No human presence -> hold for an attest.
95        assert_eq!(
96            crew_authz(&p, "crew", "add a flag", Presence::None),
97            CrewAuthz::NeedsAttest(Presence::Prompt)
98        );
99        // The operator's /team enable (Prompt) satisfies it.
100        assert_eq!(
101            crew_authz(&p, "crew", "add a flag", Presence::Prompt),
102            CrewAuthz::Allow
103        );
104        // A stronger gesture also satisfies it (presence is ordered).
105        assert_eq!(
106            crew_authz(&p, "crew", "add a flag", Presence::Passkey),
107            CrewAuthz::Allow
108        );
109    }
110
111    #[test]
112    fn team_and_unknown_ops_default_to_attest() {
113        let p = crew_step_up_policy();
114        assert_eq!(
115            crew_authz(&p, "team", "build X", Presence::None),
116            CrewAuthz::NeedsAttest(Presence::Prompt)
117        );
118        // An op no rule matches falls to the policy default (gate it).
119        assert_eq!(
120            crew_authz(&p, "mystery_op", "", Presence::None),
121            CrewAuthz::NeedsAttest(Presence::Prompt)
122        );
123    }
124
125    #[test]
126    fn a_passkey_required_action_is_not_satisfied_by_prompt() {
127        // Forward-looking: an amplifying crew that demands a hardware gesture is NOT
128        // satisfied by the soft /team enable — this is the seam BOOT's verifier fills.
129        let p = StepUpPolicy::new(
130            vec![Rule {
131                selector: "crew:secrets/*".to_string(),
132                requirement: AttestRequirement::presence(Presence::Passkey),
133            }],
134            AttestRequirement::NONE,
135        );
136        assert_eq!(
137            crew_authz(&p, "crew", "secrets/rotate", Presence::Prompt),
138            CrewAuthz::NeedsAttest(Presence::Passkey)
139        );
140        assert_eq!(
141            crew_authz(&p, "crew", "secrets/rotate", Presence::Passkey),
142            CrewAuthz::Allow
143        );
144    }
145}