core/docs/audit/ADR-0180-t1-t4-findings.md
Shay cecf7b82cc
test(adr-0180): T-1..T-4 Delta-CRDT pre-refactor obligations (Python, green) (#463)
ADR-0180 §1.5.4 + CLAUDE.md work-sequencing item 5 require these four
properties green on main before any core-rs/src/vault.rs change. They are
also the foundation ADR-0181 PR-5 (audio Delta-CRDT wiring) rides on.

  T-1  set-equality of vault writes under shuffled ingest (+ idempotent
       re-ingest at the content-addressed layer)
  T-2  trace-hash invariance to vault order, + recall result-set invariance
       to insertion order (the genuinely-failable half)
  T-3  versor_apply non-commutativity (negative guard)
  T-4  ProjectionHead.project purity across calls and threads

Findings (docs/audit/ADR-0180-t1-t4-findings.md):
- compute_trace_hash folds only vault_hits (a count), NOT vault contents, so
  ADR-0180 §1.5.3's "re-sort vault state in content-addressed order" is
  currently vacuous at the trace-hash layer; the live order-invariance
  obligation is at recall() (result-set + count). Recommend amending §1.5.3.
- equal-score recall ties are index-sensitive; the Merge Kernel needs a
  content-addressed tiebreak (mirrors ADR-0181 §2.2 merge key). Recommend
  amending §2.2.
- append is genuinely semilattice-eligible; versor_apply is non-commutative.

7 passed; smoke suite green. No runtime/core mutation — tests + audit only.
2026-05-29 10:40:52 -07:00

4.6 KiB

ADR-0180 §1.5.4 — T-1…T-4 Foundation: Findings

Date: 2026-05-29 Branch: adr-0180-crdt-t1-t4-foundation Tests: tests/test_adr_0180_crdt_foundation.py Scope: the Python-side pre-refactor obligations that must be green on main before any core-rs/src/vault.rs change (CLAUDE.md work-sequencing item 5; ADR-0180 §1.5.4). Also the foundation ADR-0181 PR-5 (audio Delta-CRDT wiring) rides on.

Status

All four obligations are now covered and green (7 tests, all passing). Below are the substantive findings discovered while grounding them against the actual substrate, not the abstract baseline ADR-0180 §1.5 assumed.

Finding 1 (load-bearing) — compute_trace_hash does not fold vault contents

ADR-0180 §1.5.3 point 2 worried that "the trace-hash reduction must consume vault state in a content-addressed order … not in wall-clock arrival order" and that §4.3 "cannot hold under [a time-driven flush] policy unless the hashing step re-sorts."

Actual code (core/cognition/trace.py:27): compute_trace_hash folds vault_hits — an int count — plus a serialized prefix of upstream turn fields. It does not fold vault contents at all.

Consequence: the §1.5.3 "re-sort vault state in content-addressed order" obligation is currently vacuous at the trace-hash layer — there is nothing order-sensitive about vault contents in the payload to re-sort, because contents are not in the payload. The CRDT merge may reorder the vault deque freely and compute_trace_hash is unaffected, provided the recalled result count (vault_hits) is order-invariant.

This shifts where the real obligation lives:

  • It is not at compute_trace_hash today (T-2a confirms count-stability).
  • It is at recall() — the count and content of recall results must be order-invariant under a reordered deque. T-2b (recall_result_set_invariant _to_insertion_order) pins this for distinct-score entries and is the genuinely-failable half of T-2.

The §1.5.3 re-sort obligation becomes live only if/when vault contents (not just a count) enter the trace-hash payload. Recommendation: ADR-0180 §1.5.3 should be amended to say the content-addressed-sort requirement applies to the recall result set and to any future contents-bearing hash, not to today's count-based compute_trace_hash.

Finding 2 (edge) — equal-score recall ties are index-sensitive

vault_recall breaks score ties by ascending index (vault/store.py). Index is assigned by storage order, so two entries with exactly equal CGA inner scores can surface in an order that depends on insertion order. For distinct scores (the common case, and what T-2b asserts) recall is order-invariant.

Consequence for the CRDT merge: the merge must content-address tie-scored entries (e.g. sort by versor bytes) before assigning deque indices, or the sub-50ms reorder window (ADR-0180 §3.2) could change which of two equal-score entries a recall returns. This is exactly the role ADR-0181 §2.2's (canonical, ir, projection) merge key plays for audio — it gives a total, content-addressed order independent of arrival. Recommendation: ADR-0180's Merge Kernel spec (§2.2) should adopt a content-addressed tiebreak for the general path, mirroring ADR-0181 §2.2.

Finding 3 (confirmed) — append is genuinely semilattice-eligible

T-1 confirms VaultStore.store is set-equal under any write permutation (append never dedups/drops/order-mutates), and re-ingest is set-stable at the content-addressed layer. This validates ADR-0180 §1.5.2 row 5 ("vault/store write … commutative, associative, idempotent — semilattice-eligible") against the real implementation. Note the deque itself appends duplicates (length grows on re-ingest); idempotence holds at the content-addressed layer the CRDT merge dedups on, not at the raw deque.

Finding 4 (confirmed) — versor_apply is non-commutative; barrier is justified

T-3 confirms the sandwich V·F·rev(V) does not commute. This is the load under ADR-0181 §2.1: in-chunk audio composition is a serialization barrier because this product is order-sensitive, and only the order-invariant AudioCompilationUnit crosses into the sharded merge layer. If a refactor ever makes versor_apply commutative, T-3 fails loudly before the substrate can wrongly shard it.

Net

  • T-1, T-3, T-4 hold against the real code as ADR-0180 §1.5.4 assumed.
  • T-2 holds, but the obligation moved: the order-invariance that matters today lives at recall() (result-set + count), not at compute_trace_hash (which is count-only). Two small amendments to ADR-0180 §1.5.3 and §2.2 are recommended above. Neither blocks the substrate; both sharpen it.