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
//! VerifierRules from SPEC.md.
use crate::base::canonical::{map_as_pairs, strict_map, strict_set};
use crate::base::hash::Hash256;
use crate::base::schema::*;
use crate::record::author::ActorId;
use crate::record::evidence::Evidence;
use crate::record::kind::{AuthorType, Kind, SummaryType};
use crate::record::record::SpaceId;
use crate::record::sign::PublicKeyBytes;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
/// Static rule configuration for the Verifier.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VerifierRules {
/// The single trust domain this verifier accepts; records in any other
/// space are rejected.
pub space: SpaceId,
/// Frozen schema id → expected Kind. Unknown schemas reject with
/// `UnknownSchema`, mismatched kinds with `KindSchemaMismatch`.
#[serde(with = "map_as_pairs")]
pub kind_schema_map: BTreeMap<Hash256, Kind>,
/// Kinds whose author must carry a signature (`SignatureMissing`
/// otherwise). Empty by default. Must not contain `Kind::Verdict`:
/// verdicts are materialized unsigned by the commit protocol and take
/// their own verification path.
#[serde(with = "strict_set")]
pub signature_required_kinds: BTreeSet<Kind>,
/// Actor id → Ed25519 public keys that actor may sign with. A listed
/// actor's records **always require a signature** (`SignatureMissing`
/// otherwise - an actor id is just a string anyone can write, so an
/// unsigned record claiming a pinned identity would bypass exactly
/// the authentication pinning provides), and the signature must
/// verify against one of the pinned keys (`SignatureInvalid`
/// otherwise) - this is what binds a key to an identity. Unlisted
/// actors may sign with any key; the signature still must verify.
/// Empty by default.
#[serde(with = "strict_author_keys")]
pub author_keys: BTreeMap<ActorId, BTreeSet<PublicKeyBytes>>,
/// Actor id → the single author role it holds. The envelope's declared
/// `author.type` is adversary-controlled, so the kind-to-role table
/// alone cannot stop a governed agent from *claiming* `User` on an
/// Approval; this map binds identities to roles. A registered actor's
/// records must declare exactly the registered role, and for **every
/// kind except `Verdict`** the author MUST be registered here
/// (unregistered authors reject with `AuthorRoleInvalid`). Note that
/// registration binds the id to a role, not the record to a person:
/// an actor id is only *authenticated* when the actor also has pinned
/// keys in `author_keys`, which makes its signature mandatory.
#[serde(with = "strict_map")]
pub author_roles: BTreeMap<ActorId, AuthorType>,
/// Actors allowed to retract records they did not author (an
/// administrative override; e.g. the human principal). A Retraction is
/// otherwise valid only when its author is the target's author -
/// disagreement with *someone else's* record is contrary evidence or a
/// Refusal, not a Retraction. Empty by default.
#[serde(with = "strict_set")]
pub admin_retraction_actors: BTreeSet<ActorId>,
/// Summary types a Summary record may use; others reject with
/// `InvalidPayload`.
#[serde(with = "strict_set")]
pub allowed_summary_types: BTreeSet<SummaryType>,
/// Minimum evidence strength per kind: a record whose *derived*
/// evidence is weaker than the threshold configured for its kind is
/// rejected with `EvidenceBelowThreshold`. Empty by default (no
/// thresholds). E.g. `{Kind::Summary: Evidence::Verified}` enforces
/// "summaries may only rest on deterministic or verified inputs".
#[serde(with = "strict_map")]
pub evidence_thresholds: BTreeMap<Kind, Evidence>,
/// Maximum number of records `build_context` selects per thread.
pub max_context_records: u32,
}
mod strict_author_keys {
use super::{ActorId, BTreeMap, BTreeSet, PublicKeyBytes};
use serde::de::{Error as _, MapAccess, Visitor};
use serde::{Deserializer, Serialize, Serializer};
use std::fmt;
pub fn serialize<S>(
map: &BTreeMap<ActorId, BTreeSet<PublicKeyBytes>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
map.serialize(serializer)
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<BTreeMap<ActorId, BTreeSet<PublicKeyBytes>>, D::Error>
where
D: Deserializer<'de>,
{
struct AuthorKeysVisitor;
impl<'de> Visitor<'de> for AuthorKeysVisitor {
type Value = BTreeMap<ActorId, BTreeSet<PublicKeyBytes>>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an actor-key object with unique actors and keys")
}
fn visit_map<A>(self, mut entries: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = BTreeMap::new();
while let Some((actor, keys)) =
entries.next_entry::<ActorId, Vec<PublicKeyBytes>>()?
{
let mut unique = BTreeSet::new();
for key in keys {
if !unique.insert(key) {
return Err(A::Error::custom("duplicate key for author"));
}
}
if map.insert(actor, unique).is_some() {
return Err(A::Error::custom("duplicate author in author_keys"));
}
}
Ok(map)
}
}
deserializer.deserialize_map(AuthorKeysVisitor)
}
}
impl VerifierRules {
/// Build the default verifier rules: the frozen schema map, no signature
/// requirements, all summary types allowed.
pub fn new(space: SpaceId, max_context_records: u32) -> Self {
Self {
space,
kind_schema_map: frozen_kind_schema_map(),
signature_required_kinds: BTreeSet::new(),
author_keys: BTreeMap::new(),
author_roles: BTreeMap::new(),
admin_retraction_actors: BTreeSet::new(),
allowed_summary_types: all_summary_types(),
evidence_thresholds: BTreeMap::new(),
max_context_records,
}
}
/// Register an actor's role (see `author_roles`). Returns self for
/// chaining during rules construction.
pub fn with_author_role(mut self, actor: impl Into<ActorId>, role: AuthorType) -> Self {
self.author_roles.insert(actor.into(), role);
self
}
}
/// Build the frozen kind-schema map.
pub fn frozen_kind_schema_map() -> BTreeMap<Hash256, Kind> {
let mut map = BTreeMap::new();
map.insert(schema_id(SCHEMA_REQUEST), Kind::Request);
map.insert(schema_id(SCHEMA_ACTION), Kind::Action);
map.insert(schema_id(SCHEMA_RESPONSE), Kind::Response);
map.insert(schema_id(SCHEMA_RESULT), Kind::Result);
map.insert(schema_id(SCHEMA_RESULT_EXTERNAL), Kind::Result);
map.insert(schema_id(SCHEMA_RESULT_EFFECT_CONFIRMATION), Kind::Result);
map.insert(schema_id(SCHEMA_CAPABILITY), Kind::Capability);
map.insert(schema_id(SCHEMA_APPROVAL), Kind::Approval);
map.insert(schema_id(SCHEMA_SUMMARY), Kind::Summary);
map.insert(schema_id(SCHEMA_REFUSAL), Kind::Refusal);
map.insert(schema_id(SCHEMA_USAGE), Kind::Usage);
map.insert(schema_id(SCHEMA_VERDICT), Kind::Verdict);
map.insert(schema_id(SCHEMA_PLAN), Kind::Plan);
map.insert(schema_id(SCHEMA_RETRACTION), Kind::Retraction);
map
}
/// All summary types the default rules allow.
pub fn all_summary_types() -> BTreeSet<SummaryType> {
let mut set = BTreeSet::new();
set.insert(SummaryType::Procedure);
set.insert(SummaryType::Pattern);
set.insert(SummaryType::Lesson);
set.insert(SummaryType::StateSnapshot);
set
}