liminal_server/services/site/
declared.rs1use std::sync::Arc;
13
14use super::effects::SiteEffects;
15use super::{BuilderGrants, SiteAct, SiteChange, SiteError};
16use crate::agents::open_session::{OPEN_GRANT, OPEN_KIND};
17use crate::auth_lys::session::LysSession;
18use crate::server::connection::declared_holder::{ANONYMOUS, PREFIX};
19use crate::services::lifecycle::Lifecycle;
20
21pub const NEEDS_A_NAME: &str = "construction_needs_a_name: an anonymous seat acts for no one; \
23 liminal seat init --open <server> --name <name>";
24
25#[must_use]
27pub fn builds(session: &dyn LysSession) -> bool {
28 match session.holder_kind() {
29 Some("agent") => true,
30 Some(OPEN_KIND) => session.holder() != ANONYMOUS,
31 _ => false,
32 }
33}
34
35#[must_use]
38pub fn answerable(session: &dyn LysSession) -> Option<String> {
39 match session.holder_kind() {
40 Some("person") => Some(session.holder().to_owned()),
41 Some(OPEN_KIND) if session.holder() != ANONYMOUS => {
42 Some(format!("{PREFIX}{}", session.holder()))
43 }
44 _ => None,
45 }
46}
47
48#[derive(Debug)]
51pub struct DeclaredActs;
52
53impl crate::server::connection::services::SiteDraftHolder for DeclaredActs {
54 fn hold(
55 &self,
56 lifecycle: &Lifecycle,
57 session: &dyn LysSession,
58 operation: &str,
59 target: &str,
60 change: SiteChange,
61 at_ms: u64,
62 ) -> Result<serde_json::Value, SiteError> {
63 if session.holder_kind() != Some(OPEN_KIND) {
64 return Err(SiteError::Invalid("construction holder kind"));
65 }
66 let holder = answerable(session).ok_or_else(|| SiteError::Refused(NEEDS_A_NAME.into()))?;
67 let act = SiteAct {
68 target: target.into(),
69 holder: holder.clone(),
70 responsible: holder,
71 change,
72 };
73 lifecycle.apply_declared(&act, operation, at_ms)
74 }
75}
76
77pub struct OpenSiteEffects {
80 agents: Arc<crate::agents::runtime::Agents>,
81}
82
83impl OpenSiteEffects {
84 #[must_use]
86 pub const fn new(agents: Arc<crate::agents::runtime::Agents>) -> Self {
87 Self { agents }
88 }
89}
90
91impl SiteEffects for OpenSiteEffects {
92 fn agent_owner(&self, agent: &str) -> Result<String, SiteError> {
93 let record = self.agents.registry().record(agent).map_err(|_| {
94 SiteError::Refused(format!(
95 "builder {agent} has no station on this exchange; its seat listens to its \
96 inbox once to place it"
97 ))
98 })?;
99 if !record.active() {
100 return Err(SiteError::Refused(format!("agent {agent} is retired")));
101 }
102 Ok(record.owner)
103 }
104
105 fn bind(
106 &self,
107 _draft: &str,
108 _site: &str,
109 agent: &str,
110 _at_ms: u64,
111 ) -> Result<BuilderGrants, SiteError> {
112 self.agent_owner(agent)?;
113 Ok(BuilderGrants {
114 plan: OPEN_GRANT.into(),
115 serve: OPEN_GRANT.into(),
116 })
117 }
118
119 fn revoke(&self, _draft: &str, _label: &str, _grant: &str) -> Result<(), SiteError> {
120 Ok(())
121 }
122
123 fn open(&self) -> bool {
124 true
125 }
126}
127
128#[cfg(test)]
129#[path = "declared_tests.rs"]
130mod tests;