ABI Conformance
AuthoredMap, AuthoredVector, and the full CRDT collection coverage gate
What changed
The AbiState struct inside the abi_conformance app gained two new fields:
authored_counters: AuthoredMap<String, LwwRegister<u32>>authored_log: AuthoredVector<LwwRegister<UserId32>>
The golden file abi.expected.json was updated to include the corresponding ABI entries for both fields. With this change the conformance app now emits all 11 CrdtCollectionType variants, closing the last gap in the cross-repo coverage gate.
Why these two types were missing
The cross-repo coverage gate requires every schema-declared CrdtType to be exercised by at least one corpus ABI. Before this change, authored_map and authored_vector were never emitted by any conformance corpus, so they had to be listed in an explicit allowlist exemption inside the gate script.
- Allowlist exemptions are fragile — a new variant added to the schema would silently pass the gate unless the exemption list was manually updated.
- Coverage should be structural — the conformance app is the designated place to lock every marker; the exemption mechanism should remain empty in steady state.
- Adding the two fields removes both exemptions and makes the gate self-maintaining: any future
CrdtCollectionTypethat lacks a matching conformance field will now fail the gate rather than slip through silently.
The full set of 11 CrdtCollectionType variants
After this change the conformance app covers every declared variant. The table below lists them all for reference:
lww_registercountersetmapvectorpriority_queuequeuetextgraphauthored_map— newly coveredauthored_vector— newly covered
AbiState struct — before and after
Before
#[app::state]
pub struct AbiState {
// … existing fields covering variants 1–9 …
}
After
#[app::state]
pub struct AbiState {
// … existing fields covering variants 1–9 …
/// Covers CrdtCollectionType::authored_map
authored_counters: AuthoredMap<String, LwwRegister<u32>>,
/// Covers CrdtCollectionType::authored_vector
authored_log: AuthoredVector<LwwRegister<UserId32>>,
}
Both fields follow the same nesting pattern already used by map and vector: an outer collection type wrapping an inner LwwRegister value.
Golden file update — abi.expected.json
The expected ABI snapshot must be regenerated whenever AbiState changes. Two new entries are appended to the fields array:
// authored_counters entry
{
"name": "authored_counters",
"type": {
"collection": "authored_map",
"key": { "primitive": "string" },
"value": {
"collection": "lww_register",
"inner": { "primitive": "u32" }
}
}
},
// authored_log entry
{
"name": "authored_log",
"type": {
"collection": "authored_vector",
"inner": {
"collection": "lww_register",
"inner": { "primitive": "user_id32" }
}
}
}
Run cargo test -p abi_conformance -- --nocapture to regenerate the snapshot and confirm it matches the golden file.
How the coverage gate works
The gate is a CI step that compares the set of CrdtCollectionType variants declared in the schema crate against the set of variants actually emitted by every ABI file in the corpus directory.
- Schema source — the
CrdtCollectionTypeenum incrates/primitives/src/crdt.rs(or equivalent). - Corpus — every
abi.jsonandabi.expected.jsonfile discovered underapps/. - Pass condition — the union of all collection markers found in the corpus equals the full variant set; the allowlist of exemptions must be empty.
- Fail condition — any variant present in the schema but absent from every corpus ABI causes the gate to fail with a diff listing the missing markers.
AuthoredMap and AuthoredVector semantics
AuthoredMap
An AuthoredMap<K, V> is a key-value collection where every entry carries the UserId of the peer that last wrote it. Conflict resolution is author-aware: writes from the designated author of a key take precedence over writes from other peers, falling back to LWW semantics when authorship is equal.
AuthoredVector
An AuthoredVector<V> is an append-only sequence where each element is stamped with the inserting peer's identity. Order is deterministic across replicas: elements are sorted first by logical timestamp, then by author id to break ties.
Both types are generic over their value type, which may itself be a CRDT (as in the conformance app where the value is LwwRegister<_>), enabling arbitrarily nested CRDT compositions.
Adding a new CrdtCollectionType in the future
The conformance app is now the canonical place to lock every CRDT collection marker. The required steps when introducing a new variant are:
- Add the variant to the
CrdtCollectionTypeenum in the schema crate. - Implement the corresponding collection type in the CRDT runtime crate.
- Add a field of that type to
AbiStateinapps/abi_conformance/src/lib.rs. - Regenerate
abi.expected.jsonby running the conformance snapshot test. - Verify the coverage gate passes with an empty allowlist — do not add an exemption.
Never add a new variant to the schema without a matching conformance field. A CI failure from the gate is the intended signal that step 3 was skipped.
File locations
apps/abi_conformance/src/lib.rs—AbiStatestruct definition; add new fields here.apps/abi_conformance/abi.expected.json— golden ABI snapshot; regenerate after struct changes.crates/primitives/src/crdt.rs— authoritativeCrdtCollectionTypeenum.scripts/check_crdt_coverage.sh(or equivalent CI step) — the coverage gate; allowlist must stay empty.
Relationship to other conformance corpus entries
The conformance app is intentionally not a realistic application. Its sole purpose is to ensure every ABI-visible type appears at least once in a well-formed, compilable Rust struct so that:
- The ABI serializer is exercised for every variant on every CI run.
- The golden snapshot acts as a regression test for ABI format changes.
- Cross-language SDK generators can use the corpus as a test fixture without needing a real application.
Other apps in the repository are free to use only the CRDT types they actually need; they are not required to cover the full variant set.