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 CrdtCollectionType that 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:

  1. lww_register
  2. counter
  3. set
  4. map
  5. vector
  6. priority_queue
  7. queue
  8. text
  9. graph
  10. authored_mapnewly covered
  11. authored_vectornewly 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 CrdtCollectionType enum in crates/primitives/src/crdt.rs (or equivalent).
  • Corpus — every abi.json and abi.expected.json file discovered under apps/.
  • 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:

  1. Add the variant to the CrdtCollectionType enum in the schema crate.
  2. Implement the corresponding collection type in the CRDT runtime crate.
  3. Add a field of that type to AbiState in apps/abi_conformance/src/lib.rs.
  4. Regenerate abi.expected.json by running the conformance snapshot test.
  5. 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.rsAbiState struct definition; add new fields here.
  • apps/abi_conformance/abi.expected.json — golden ABI snapshot; regenerate after struct changes.
  • crates/primitives/src/crdt.rs — authoritative CrdtCollectionType enum.
  • 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.