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
//! RBAC role definitions and sidecar I/O.
//!
//! [`RoleDef`] is the public unit of role configuration. Roles are declared in
//! [`Schema::roles`](crate::schema::Schema) and persisted as `roles.json` in
//! the database directory via [`GraphDb::apply_schema`].
//!
//! # Never-widen rule
//!
//! - Empty role (no keys, no labels) = empty mask = sees nothing.
//! - Unknown role on a request = `Err` (never silently grant full access).
//! - Corrupt `roles.json` at open = roles poisoned; [`GraphDb::mask_for_role`]
//! returns `Err` for any role name until the file is fixed and the DB
//! re-opened.
//!
//! # Persistence
//!
//! `roles.json` format: `{ "version": 1, "roles": [...] }` (no write scopes)
//! or `{ "version": 2, "roles": [...] }` (at least one role has a write scope).
//! Version 2 is written only when a write scope is present; version 1 is kept
//! for forward-compat honesty — a v0.2 server can load v1 safely and the
//! `write` field (absent from v1) is ignored by serde's `#[serde(default)]`
//! when a v2 sidecar is loaded by an older binary.
//! Files are written atomically (temp → fsync → rename → dir-sync); a no-change
//! re-apply leaves the file byte-identical.
use ;
/// Write permissions granted to a role.
///
/// All fields default to empty (absent from JSON = no write permission for that
/// operation). `write: None` on `RoleDef` is equivalent to all fields empty —
/// the role is read-only, identical to v0.2 behavior.
///
/// Subset rule (enforced at `apply_schema` time):
/// - `create_labels`, `update_labels`, and `delete_labels` must each be a
/// subset of the role's read `labels`.
/// - `create_edge_types` and `delete_edge_types` have no subset requirement
/// (edge types are not read-scoped).
/// A named RBAC role: resolves to a node-visibility mask at query time.
///
/// `keys` and `labels` both default to empty when absent from JSON, so a
/// schema snippet that names only labels is valid.
///
/// The resolved mask is the union of:
/// - all nodes whose key appears in `keys` (unknown keys silently ignored), and
/// - all nodes carrying any label in `labels` (resolved live against the current
/// graph — new nodes of an allowed label are immediately visible without
/// re-applying the schema).
///
/// An empty union (no keys, no matching label nodes) = empty mask = sees nothing.
///
/// `write: None` (or absent from JSON) = read-only role, v1 behavior, backward
/// compatible with any client that does not know about write scopes.
/// On-disk wrapper for `roles.json`. Version field allows future format bumps.
///
/// Version 1: no write scopes (all roles read-only, v0.2 compatible).
/// Version 2: at least one role carries a `write` field.
/// Version >2: unrecognised — roles state is poisoned on load.
pub