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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
use super::*;
pub(super) fn check_kind_specific(
record: &Record,
prior: &Prior<'_>,
rules: &VerifierRules,
state: &State,
) -> Option<ReasonCode> {
match record.kind {
Kind::Request => check_request(record, prior, state),
Kind::Action => check_action(record, prior, state),
Kind::Result => check_result(record, prior, rules, state),
Kind::Refusal => check_refusal(record, prior, state),
Kind::Usage => check_usage(record, prior, state),
Kind::Summary => check_summary(record, rules),
Kind::Response => check_response(record, prior, state),
Kind::Approval => check_approval(record),
Kind::Plan => check_plan(record, prior, state),
Kind::Retraction => check_retraction(record, prior, rules, state),
// No kind-specific rules beyond the structural and replacement
// checks above.
Kind::Capability => None,
// Verdicts take the check_verdict_record path before this match.
Kind::Verdict => None,
}
}
/// Request checks - delegated child requests must link to parent via `Cause` refs.
pub(super) fn check_request(
record: &Record,
prior: &Prior<'_>,
state: &State,
) -> Option<ReasonCode> {
let data: RequestData = dec!(&record.data, RequestData);
let cause_targets: Vec<RecordId> = record
.refs
.iter()
.filter(|r| r.type_ == RefType::Cause)
.map(|r| r.target)
.collect();
for target_id in &cause_targets {
// Cause targets are refs, so the generic ref checks already
// resolved them; a miss here is a rejection, never a pass.
let Some(parent) = prior.find(*target_id) else {
return Some(ReasonCode::RefUnresolved);
};
if parent.kind != Kind::Request {
return Some(ReasonCode::InvalidPayload);
}
if parent.thread != record.thread {
return Some(ReasonCode::InvalidPayload);
}
if !state.accepted_records.contains(target_id) {
return Some(ReasonCode::RequestMissing);
}
}
// Parentage is unambiguous: the declared parent and the Cause edges
// must state the same delegation graph. No parent means zero Request
// Cause refs; a declared parent means exactly one Cause ref, naming
// exactly that parent - no undeclared or surplus parents.
match data.parent_request_id {
None => {
if !cause_targets.is_empty() {
return Some(ReasonCode::InvalidPayload);
}
}
Some(pid) => {
if cause_targets.len() != 1 || cause_targets[0] != pid {
return Some(ReasonCode::InvalidPayload);
}
}
}
None
}
/// Action checks.
pub(super) fn check_action(
record: &Record,
prior: &Prior<'_>,
state: &State,
) -> Option<ReasonCode> {
let data: ActionData = dec!(&record.data, ActionData);
// request_id must resolve to accepted active request in same thread/space
if !state.active_requests.contains(&data.request_id) {
return Some(ReasonCode::RequestMissing);
}
let Some(request) = prior.find(data.request_id) else {
return Some(ReasonCode::RequestMissing);
};
if request.thread != record.thread || request.space != record.space {
return Some(ReasonCode::RequestMissing);
}
// The action must operate in the request's scope: a capability held
// for some other scope must not let a provider serve a scope-A
// request with scope-B actions.
let request_data: RequestData = dec!(&request.data, RequestData);
if data.scope != request_data.scope {
return Some(ReasonCode::InvalidPayload);
}
// Requires effective capability
let cap_record_id = state.active_capabilities.get(&(
record.author.id.clone(),
data.action_class.clone(),
data.scope,
));
match cap_record_id {
None => return Some(ReasonCode::CapabilityMissing),
Some(&cap_id) => {
let Some(cap_record) = prior.find(cap_id) else {
return Some(ReasonCode::CapabilityMissing);
};
// Retracted or tainted authority never authorizes: its content
// was asserted wrong (or rests on something that was), so
// governance and epistemic state must agree.
if state.retracted_records.contains(&cap_id) || state.tainted_records.contains(&cap_id)
{
return Some(ReasonCode::CapabilityMissing);
}
let cap_data: CapabilityData = dec!(&cap_record.data, CapabilityData);
// Check expiry
if let Some(expiry) = cap_data.expiry {
if record.time >= expiry {
return Some(ReasonCode::CapabilityMissing);
}
}
// Resolve the full authority chain first (so a missing or
// expired approval reports its own precise reason), then
// require the audit graph to show that authority: `Require`
// refs naming the exact capability - and approval, for Ask
// mode - are mandatory, so a later retraction of the authority
// taints the action through the dependence index.
let approval_id = match cap_data.mode {
// Deny rejects
CapabilityMode::Deny => return Some(ReasonCode::CapabilityDenied),
// Auto allows without approval
CapabilityMode::Auto => None,
// Ask requires approval
CapabilityMode::Ask => {
match check_approval_for_action(record, &data, prior, state) {
Err(reason) => return Some(reason),
Ok(approval_id) => Some(approval_id),
}
}
};
if !has_require_ref(record, cap_id) {
return Some(ReasonCode::AuthorityRefMissing);
}
if let Some(approval_id) = approval_id {
if !has_require_ref(record, approval_id) {
return Some(ReasonCode::AuthorityRefMissing);
}
}
}
}
None
}
/// Whether the record carries a `Require` ref to `target`.
pub(super) fn has_require_ref(record: &Record, target: RecordId) -> bool {
record
.refs
.iter()
.any(|r| r.type_ == RefType::Require && r.target == target)
}
/// Check approval resolution for Ask mode actions. Ok carries the id of
/// the approval that resolved (the caller requires a `Require` ref naming
/// exactly it); Err is the rejection reason. Retracted or tainted
/// approvals never authorize - they are skipped as if absent.
pub(super) fn check_approval_for_action(
record: &Record,
data: &ActionData,
prior: &Prior<'_>,
state: &State,
) -> Result<RecordId, ReasonCode> {
// The exact-approval target binds the acting author together with the
// action content: SHA-256(canonical((action_author_id, ActionData))).
// Hashing ActionData alone would let a different Provider (or the
// same one, repeatedly) reuse an approval granted for one specific
// actor's one specific action.
let action_hash = match sha256_canonical(&(&record.author.id, data)) {
Ok(h) => h,
Err(_) => return Err(ReasonCode::InvalidPayload),
};
let usable = |approval_id: RecordId| -> bool {
state.accepted_records.contains(&approval_id)
&& !state.retracted_records.contains(&approval_id)
&& !state.tainted_records.contains(&approval_id)
};
// Priority 1: Exact match (single-use: an accepted action consumes
// it, removing it from `valid_approvals` - see the incremental state
// apply).
if let Some(&approval_id) = state.valid_approvals.get(&action_hash) {
if let Some(approval_record) = prior.find(approval_id) {
if usable(approval_id) {
let approval_data: ApprovalData =
match decode::<ApprovalData>(&approval_record.data) {
Ok(v) => v,
Err(_) => return Err(ReasonCode::InvalidPayload),
};
// The approval's declared subject actor and scope must
// match the action (the hash already binds both; a
// mismatched declaration is malformed and unusable -
// an approval must not visibly claim one scope while
// authorizing another through its hash).
if approval_data.actor_id.as_deref() == Some(record.author.id.as_str())
&& approval_data.scope == data.scope
{
match approval_data.expiry {
// Expired exact approval falls through to class match.
Some(expiry) if record.time >= expiry => {}
// Valid exact approval.
_ => return Ok(approval_id),
}
}
}
}
}
// Priority 2: Class match with specific actor
let class_key = (
data.action_class.clone(),
data.scope,
Some(record.author.id.clone()),
);
if let Some(&approval_id) = state.class_approvals.get(&class_key) {
if let Some(approval_record) = prior.find(approval_id) {
if usable(approval_id) {
let approval_data: ApprovalData =
match decode::<ApprovalData>(&approval_record.data) {
Ok(v) => v,
Err(_) => return Err(ReasonCode::InvalidPayload),
};
if let Some(expiry) = approval_data.expiry {
if record.time >= expiry {
return Err(ReasonCode::ApprovalExpired);
}
}
return Ok(approval_id); // Valid class approval
}
}
}
// Priority 3: Class wildcard match
let wildcard_key = (data.action_class.clone(), data.scope, None);
if let Some(&approval_id) = state.class_approvals.get(&wildcard_key) {
if let Some(approval_record) = prior.find(approval_id) {
if usable(approval_id) {
let approval_data: ApprovalData =
match decode::<ApprovalData>(&approval_record.data) {
Ok(v) => v,
Err(_) => return Err(ReasonCode::InvalidPayload),
};
if let Some(expiry) = approval_data.expiry {
if record.time >= expiry {
return Err(ReasonCode::ApprovalExpired);
}
}
return Ok(approval_id); // Valid class wildcard approval
}
}
}
Err(ReasonCode::ApprovalMissing)
}
// Result checks.