Data Model¶
This document describes grite's data model, including event schema, ID types, and encoding.
Event Schema¶
All state changes are events. Issues are projections of the event stream.
Event Structure¶
pub struct Event {
pub event_id: EventId, // [u8; 32] - content-addressed
pub issue_id: IssueId, // [u8; 16] - random
pub actor: ActorId, // [u8; 16] - random
pub ts_unix_ms: u64,
pub parent: Option<EventId>,
pub kind: EventKind,
pub sig: Option<Vec<u8>>, // Ed25519 signature (optional)
}
Event Kinds¶
pub enum IssueState {
Open,
Closed,
}
pub enum DependencyType {
Blocks, // "this issue blocks target"
DependsOn, // "this issue depends on target"
RelatedTo, // symmetric, no cycle constraint
}
pub struct SymbolInfo {
pub name: String,
pub kind: String, // "function", "struct", "trait", "class", etc.
pub line_start: u32,
pub line_end: u32,
}
pub enum EventKind {
IssueCreated { title: String, body: String, labels: Vec<String> },
IssueUpdated { title: Option<String>, body: Option<String> },
CommentAdded { body: String },
LabelAdded { label: String },
LabelRemoved { label: String },
StateChanged { state: IssueState },
LinkAdded { url: String, note: Option<String> },
AssigneeAdded { user: String },
AssigneeRemoved { user: String },
AttachmentAdded { name: String, sha256: [u8; 32], mime: String },
DependencyAdded { target: IssueId, dep_type: DependencyType },
DependencyRemoved { target: IssueId, dep_type: DependencyType },
ContextUpdated { path: String, language: String, symbols: Vec<SymbolInfo>, summary: String, content_hash: [u8; 32] },
ProjectContextUpdated { key: String, value: String },
}
Event Kind Details¶
| Kind | Fields | Purpose |
|---|---|---|
IssueCreated |
title, body, labels | Create new issue |
IssueUpdated |
title?, body? | Update title/body |
CommentAdded |
body | Add comment |
LabelAdded |
label | Add label |
LabelRemoved |
label | Remove label |
StateChanged |
state | Open/close issue |
LinkAdded |
url, note? | Attach URL |
AssigneeAdded |
user | Assign user |
AssigneeRemoved |
user | Unassign user |
AttachmentAdded |
name, sha256, mime | Attach file metadata |
DependencyAdded |
target, dep_type | Add dependency edge |
DependencyRemoved |
target, dep_type | Remove dependency edge |
ContextUpdated |
path, language, symbols, summary, content_hash | Update file context |
ProjectContextUpdated |
key, value | Update project context entry |
Note
AttachmentAdded carries only metadata and a content hash. Binary storage is out of scope for the WAL.
Note
ContextUpdated and ProjectContextUpdated events use derived IssueIds:
- File context:
IssueId = blake2b("grite:context:file:" + path)[..16] - Project context:
IssueId = [0xFF; 16](sentinel)
This allows context events to flow through the standard WAL and sync for free.
ID Types¶
Overview¶
| Type | Rust Type | Size | Format | Generation |
|---|---|---|---|---|
ActorId |
[u8; 16] |
128-bit | Random | rand::thread_rng().gen() |
IssueId |
[u8; 16] |
128-bit | Random | rand::thread_rng().gen() |
EventId |
[u8; 32] |
256-bit | Hash | BLAKE2b-256 of event content |
Why Byte Arrays?¶
- Compact storage: 16 bytes vs 32 chars (hex string), 50% smaller
- Type safety:
[u8; 16]vs[u8; 32]catches ID mixups at compile time - Hash-native: EventId directly holds BLAKE2b-256 output
- Crypto-friendly: Works directly with signing/hashing operations
- No allocations: Fixed-size arrays on stack
Display Format¶
All IDs are displayed as lowercase hex strings:
ActorId: 64d15a2c383e2161772f9cea23e87222 (32 hex chars)
IssueId: 8057324b1e03afd613d4b428fdee657a (32 hex chars)
EventId: a1b2c3d4... (64 hex chars)
ActorId¶
- Generated once per device/agent during
grite init - Stored in
.git/grite/actors/<actor_id>/config.toml - Identifies the source of events
- Multiple actors can exist per repository
IssueId¶
- Generated when creating a new issue
- Random to avoid coordination between actors
- Appears in all events for that issue
EventId¶
- Content-addressed: computed from event content
- Ensures event integrity (any change produces different ID)
- Used for deduplication during sync
Canonical Encoding¶
Goal¶
Stable, cross-language hashing regardless of platform or serializer.
Approach¶
- Hash: BLAKE2b-256 (
[u8; 32]) - Preimage: Canonical CBOR encoding of a fixed-order array
Hashing is independent of storage. WAL chunks use portable CBOR encoding, while sled uses rkyv for compact on-disk values. The event_id is always computed from the canonical CBOR preimage.
Hash Input¶
The hash input is the following array (no maps):
[
1, // schema version
issue_id, // 16-byte bstr
actor, // 16-byte bstr
ts_unix_ms, // u64
parent, // null or 32-byte bstr
kind_tag, // u32
kind_payload // array (see below)
]
Kind Tags and Payloads¶
| Tag | Kind | Payload |
|---|---|---|
| 1 | IssueCreated | [title, body, labels] |
| 2 | IssueUpdated | [title_opt, body_opt] |
| 3 | CommentAdded | [body] |
| 4 | LabelAdded | [label] |
| 5 | LabelRemoved | [label] |
| 6 | StateChanged | [state] |
| 7 | LinkAdded | [url, note_opt] |
| 8 | AssigneeAdded | [user] |
| 9 | AssigneeRemoved | [user] |
| 10 | AttachmentAdded | [name, sha256, mime] |
| 11 | DependencyAdded | [target_bytes, dep_type_str] |
| 12 | DependencyRemoved | [target_bytes, dep_type_str] |
| 13 | ContextUpdated | [path, language, sorted_symbols_array, summary, content_hash_bytes] |
| 14 | ProjectContextUpdated | [key, value] |
IssueState Encoding¶
IssueState values are encoded as lowercase strings:
"open""closed"
Canonicalization Rules¶
- CBOR is encoded using canonical rules (RFC 8949)
- Arrays are encoded in order
- Strings are UTF-8 as provided
- For hashing only,
labelsinIssueCreatedare sorted lexicographically sigis not included in the hash; it signs theevent_id
Signing and Verification¶
Overview¶
Signatures are optional. If present, sig is a detached Ed25519 signature over the raw 32-byte event_id.
Key Generation¶
Creates:
- Private key:
.git/grite/actors/<actor_id>/keys/signing.key - Public key: stored in actor
config.toml
Verification Flow¶
- Compute
event_idfrom canonical CBOR preimage - Verify
sigagainstevent_idusing actor's public key
Verification Policy¶
Configurable in repo config:
| Policy | Behavior |
|---|---|
off |
No verification |
warn |
Log warning on invalid signatures |
reject |
Reject events with invalid signatures |
Issue Projection¶
IssueProjection is computed by folding events for an issue. See CRDT Merging for merge strategy details.
pub struct IssueProjection {
pub issue_id: IssueId,
pub title: String,
pub body: String,
pub state: IssueState,
pub labels: BTreeSet<String>,
pub assignees: BTreeSet<String>,
pub dependencies: BTreeSet<Dependency>,
pub comments: Vec<Comment>,
pub links: Vec<Link>,
pub attachments: Vec<Attachment>,
pub created_ts: u64,
pub updated_ts: u64,
pub version: Version,
}
pub struct Dependency {
pub target: IssueId,
pub dep_type: DependencyType,
}
Context Projections¶
Context events are projected separately from issues:
pub struct FileContext {
pub path: String,
pub language: String,
pub symbols: Vec<SymbolInfo>,
pub summary: String,
pub content_hash: [u8; 32],
pub version: Version, // LWW per file path
}
pub struct ProjectContextEntry {
pub value: String,
pub version: Version, // LWW per key
}
Next Steps¶
- CRDT Merging - How projections are computed
- Git WAL - How events are stored
- Storage Layout - File organization