Phase 3c + 3e: stamped tags + namespaced ref keys — closes Phase 3
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 20s
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 20s
Ships the last two pieces from the arch doc's Phase 3 scope for the
cargo-cache use case:
## 3c: Stamped tags (CRDT-merge on PutTag)
Mirror of Phase 3a/3b for TagStore. Two concurrent `claw-cargo pin`
calls on the same tag now race deterministically instead of silently
clobbering.
* `StampedTagValue` — same 48-byte (value, clock, node) tuple as
StampedRef.
* `TagStore::put_stamped(key, StampedTagValue) -> TagPutOutcome`
and `TagStore::get_stamped(key)` — data lives under `tags-v2/`
(separate from `tags/` for cutover safety).
* New wire methods `PutTagVersioned = 0x18` +
`GetTagVersioned = 0x19`.
* `call_put_tag_versioned` / `call_get_tag_versioned` client
helpers.
* `claw-cargo pin` now writes stamped tags. Concurrent pin gets
AlreadyExists and moves on (blob content is content-addressed so
both winners agree on the payload).
## 3e: Namespaced ref keys
Opt-in `--namespace <slug>` on peer-facing subcommands. When set,
the ref key becomes `blake3("clawstor.ns.v1" || namespace || fp)`
so two runners on different namespaces (`clawverse/main` vs
`clawverse/pr-42`) don't collide on the same fingerprint. Empty
namespace = pre-3e behavior, so this is 100% backward compat.
* `refs::namespaced_ref_key(namespace, fingerprint) -> RefKey`
primitive.
* `PeerArgs::namespace: Option<String>` CLI flag flows through to
`cmd_status`, `cmd_prefetch`, `cmd_build`.
* `peer_lookup` now takes a `RefKey` directly (was `&Fingerprint`)
so the namespace resolution stays in the caller — the daemon
never sees "namespace" as a concept.
## 3d: Deferred
Full vector clocks per namespace are noted in the arch doc as a
Phase-3 goal; scalar wall-clock (clock + node stamp) is sufficient
for the cargo-cache use case (single-key LWW merge). NTP-synced
runners see monotonic ordering; skewed runners lose an ordering
but the CRDT semantics still guarantee no data corruption. Full VC
is deferred to a future phase.
+9 tests, 280 total (baseline +8: 7 unit + 1 e2e over real QUIC).
This commit is contained in:
@@ -546,6 +546,58 @@ async fn call_get_ref_versioned_inner(
|
||||
))
|
||||
}
|
||||
|
||||
// ── Phase 3c: stamped-tag client helpers ─────────────────────────────
|
||||
|
||||
/// Phase 3c (2026-07-13): submit a stamped (CRDT-merge) PutTag.
|
||||
///
|
||||
/// Returns `Ok(true)` when the peer merged the write, `Ok(false)`
|
||||
/// when the peer rejected it because an equal-or-newer version
|
||||
/// already exists (`AlreadyExists`). Any other reply is an error.
|
||||
pub async fn call_put_tag_versioned(
|
||||
conn: &Connection,
|
||||
key: &str,
|
||||
incoming: &crate::cluster::tags::StampedTagValue,
|
||||
) -> Result<bool> {
|
||||
let payload = crate::cluster::tags::encode_stamped_record(key, incoming);
|
||||
let reply = rpc_call(conn, Method::PutTagVersioned, &payload).await?;
|
||||
if reply.len() != 1 {
|
||||
bail!(
|
||||
"expected single-byte PutTagVersioned reply, got {} bytes",
|
||||
reply.len()
|
||||
);
|
||||
}
|
||||
match reply[0] {
|
||||
STREAM_STATUS_OK => Ok(true),
|
||||
code => match decode_error(code) {
|
||||
Some(ErrorCode::AlreadyExists) => Ok(false),
|
||||
Some(err) => bail!("peer rejected PutTagVersioned: {}", err.describe()),
|
||||
None => bail!(
|
||||
"peer replied with unknown byte 0x{:02x} for PutTagVersioned",
|
||||
code
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 3c: fetch a stamped tag value.
|
||||
pub async fn call_get_tag_versioned(
|
||||
conn: &Connection,
|
||||
key: &str,
|
||||
) -> Result<Option<crate::cluster::tags::StampedTagValue>> {
|
||||
let reply = rpc_call(conn, Method::GetTagVersioned, key.as_bytes()).await?;
|
||||
if reply.len() == 1 {
|
||||
match decode_error(reply[0]) {
|
||||
Some(ErrorCode::NotFound) => return Ok(None),
|
||||
Some(err) => bail!("peer replied with error: {}", err.describe()),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Ok(Some(
|
||||
crate::cluster::tags::StampedTagValue::from_bytes(&reply)
|
||||
.context("decoding stamped tag reply")?,
|
||||
))
|
||||
}
|
||||
|
||||
// ── Phase 5g: cache metrics client helper ────────────────────────────
|
||||
|
||||
/// Fetch the peer's current cache-metrics snapshot.
|
||||
|
||||
Reference in New Issue
Block a user