1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! Persisted construction shapes. Free text has no Debug representation.
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
/// Required positive operator bounds; reaching a bound refuses before mutation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteLimits {
/// Limits active and retired construction records without evicting history.
pub sites: NonZeroUsize,
/// Limits subject schemas validated in one admission operation.
pub subjects: NonZeroUsize,
/// Limits each schema's compilation input before allocating a validator.
pub schema_bytes: NonZeroUsize,
/// Limits purpose, title, note and routing metadata individually.
pub text_bytes: NonZeroUsize,
/// Limits steps retained in one complete plan replacement.
pub steps: NonZeroUsize,
/// Limits historical transitions; a full history refuses new writes.
pub transitions: NonZeroUsize,
/// Limits prepared and completed drafts without evicting execution receipts.
pub drafts: NonZeroUsize,
/// Bounds the distinct authenticated people recorded on a draft approval.
pub approvers: NonZeroUsize,
/// Limits the whole atomic snapshot, including histories and prepared words.
pub snapshot_bytes: NonZeroUsize,
/// Limits one immutable request before parsing or holding a draft.
pub request_bytes: NonZeroUsize,
}
/// Operator bounds and delegation sources for the construction registry.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConstructionConfig {
/// Positive construction byte and count limits, all explicitly configured.
pub limits: SiteLimits,
/// The two source grants held by this machine; no source is inferred.
pub grants: super::effects::GrantSources,
}
/// Exactly what the site promises on one served subject.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Contract {
/// Required input schema, exactly as supplied.
#[serde(default)]
pub input_schema_json: String,
/// Required output schema, exactly as supplied.
#[serde(default)]
pub output_schema_json: String,
}
/// Immutable contracts approved before binding any builder.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteSpec {
/// One service name segment.
pub name: String,
/// One sentence describing its work.
pub purpose: String,
/// Exact subject roots it will serve.
pub subjects: Vec<String>,
/// One validated pair for every subject.
#[serde(default)]
pub contracts: BTreeMap<String, Contract>,
}
/// The exact Lys grant ids, retained for deterministic revocation.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuilderGrants {
/// The builder's plan-only grant on this service.
pub plan: String,
/// The builder's serve-only grant on this service.
pub serve: String,
}
/// The connector's authenticated approval, stored without free-text words.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Approval {
/// Exact Lys draft.
pub draft: String,
/// Digest of the immutable approved words.
pub digest: String,
/// The actual distinct approving people.
pub approved_by: Vec<String>,
/// The server's execution clock, in epoch milliseconds.
pub executed_at_ms: u64,
/// The durable execution outcome on this exact target transition.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub receipt: Option<lys_pass::drafts::Execution>,
}
impl From<crate::auth_lys::live::drafts::ApprovalTransition> for Approval {
fn from(value: crate::auth_lys::live::drafts::ApprovalTransition) -> Self {
Self {
draft: value.draft,
digest: value.digest,
approved_by: value.approved_by,
executed_at_ms: value.executed_at_ms,
receipt: None,
}
}
}
/// Deliberate construction events; no payload becomes executable code.
#[derive(Clone, Serialize, Deserialize)]
#[serde(
tag = "act",
content = "detail",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum SiteChange {
/// A contracts-first site, initially without a builder or template.
CreateSite(SiteSpec),
/// The named active Lys agent becomes its sole builder.
BindBuilder { agent: String },
/// Revokes both recorded builder grants.
UnbindBuilder,
/// Records the program exactly approved by the person.
CreateTemplate(super::super::templates::TemplateProposal),
/// Updates an existing template without changing read-only configuration.
UpdateTemplate(super::super::templates::TemplateProposal),
/// Retires a live template without removing its history.
RetireTemplate,
/// Removes the trial serve grant and leaves the site asleep.
Complete,
}
impl SiteChange {
/// The schema action used to hold this act.
#[must_use]
pub const fn action(&self) -> &'static str {
match self {
Self::CreateSite(_) => "create_site",
Self::BindBuilder { .. } => "bind_builder",
Self::UnbindBuilder => "unbind_builder",
Self::CreateTemplate(_) => "create",
Self::UpdateTemplate(_) => "update",
Self::RetireTemplate => "retire",
Self::Complete => "complete",
}
}
/// The resource kind named in the immutable draft.
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::CreateSite(_) => "liminal.network",
Self::CreateTemplate(_) | Self::UpdateTemplate(_) | Self::RetireTemplate => {
"liminal.template"
}
_ => "liminal.service",
}
}
}
/// A prepared act whose actor is derived only from an admitted pass.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteAct {
/// The placed service id or template name, never a filesystem path.
pub target: String,
/// Original verified requester.
pub holder: String,
/// Responsible person from the same verified claims.
pub responsible: String,
/// The immutable approved event.
pub change: SiteChange,
}
impl SiteAct {
/// Binds immutable words to the signed requester before the draft is held.
///
/// # Errors
/// Unsupported holder kind or missing responsible person.
pub fn prepare(
target: String,
change: SiteChange,
pass: &lys_pass::VerifiedPass,
) -> Result<Self, super::SiteError> {
let holder = &pass.claims().holder;
let responsible = match holder.kind.as_str() {
"person" => holder.id.clone(),
"agent" => holder
.responsible
.clone()
.ok_or(super::SiteError::Invalid("responsible person"))?,
_ => return Err(super::SiteError::Invalid("construction holder kind")),
};
if responsible.is_empty() {
return Err(super::SiteError::Invalid("responsible person"));
}
Ok(Self {
target,
holder: holder.id.clone(),
responsible,
change,
})
}
}
impl SiteAct {
/// Creation asks the network before an unplaced site can have inherited rights.
#[must_use]
pub fn permission_id<'a>(&'a self, network: &'a str) -> &'a str {
if matches!(self.change, SiteChange::CreateSite(_)) {
network
} else {
&self.target
}
}
}