Phase 4b follow-on: pin --ttl RPC + CLI
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 10s
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 10s
Closes Phase 4b by exposing the TTL sidecar written by Phase 4b primitives on the wire and via `claw-cargo pin`. Wire additions: * Method::SetTagExpiry (0x1a) — payload `key_len:u16 || key || expires_at:u64 (LE)`. Reply single-byte OK. `expires_at == 0` clears the sidecar. * Method::GetTagExpiry (0x1b) — payload raw key bytes. Reply 8 bytes (u64 LE) on hit; NotFound when no sidecar is present. Both accept writes even when the stamped tag itself is absent, matching `TagStore::set_stamped_expiry` semantics — the sidecar takes effect the moment the tag lands. CLI: * `claw-cargo pin --ttl <duration>` — humantime-style duration (`30d`, `1h30m`, `2w`, ...). Applied to both the primary tag and its `.fingerprint` companion so eviction treats them as one lifetime. `--ttl 0` / `clear` / `none` clears an existing sidecar without touching the value. Tests: encode/decode roundtrip + malformed-input rejection for `encode_expiry_record`, method-byte stability, NotConfigured without a tag store, end-to-end set/get/overwrite/clear over QUIC, and a real-pin flow that publishes a stamped tag then attaches TTL. Duration parser is unit-tested for single/compound forms, case-insensitive units, bad input, and clock alignment. No new deps — the humantime-style parser is 60 lines in-tree. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
13fecd798e
commit
4630925040
@@ -598,6 +598,65 @@ pub async fn call_get_tag_versioned(
|
||||
))
|
||||
}
|
||||
|
||||
// ── Phase 4b follow-on: TTL client helpers ───────────────────────────
|
||||
|
||||
/// Phase 4b follow-on (2026-07-13): attach a TTL sidecar to a stamped
|
||||
/// tag on a peer. `expires_at_unix == 0` clears any prior sidecar.
|
||||
///
|
||||
/// The peer accepts writes even when the stamped tag isn't present
|
||||
/// yet — the sidecar sticks around and takes effect once the tag
|
||||
/// lands (`TagStore::set_stamped_expiry` semantics).
|
||||
pub async fn call_set_tag_expiry(
|
||||
conn: &Connection,
|
||||
key: &str,
|
||||
expires_at_unix: u64,
|
||||
) -> Result<()> {
|
||||
let payload = crate::cluster::tags::encode_expiry_record(key, expires_at_unix);
|
||||
let reply = rpc_call(conn, Method::SetTagExpiry, &payload).await?;
|
||||
if reply.len() != 1 {
|
||||
bail!(
|
||||
"expected single-byte SetTagExpiry reply, got {} bytes",
|
||||
reply.len()
|
||||
);
|
||||
}
|
||||
match reply[0] {
|
||||
STREAM_STATUS_OK => Ok(()),
|
||||
code => match decode_error(code) {
|
||||
Some(err) => bail!("peer rejected SetTagExpiry: {}", err.describe()),
|
||||
None => bail!(
|
||||
"peer replied with unknown byte 0x{:02x} for SetTagExpiry",
|
||||
code
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 4b follow-on: fetch the TTL sidecar for a stamped tag.
|
||||
/// Returns `Ok(None)` when no sidecar is present (never expires or
|
||||
/// no such tag).
|
||||
pub async fn call_get_tag_expiry(
|
||||
conn: &Connection,
|
||||
key: &str,
|
||||
) -> Result<Option<u64>> {
|
||||
let reply = rpc_call(conn, Method::GetTagExpiry, 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 => {}
|
||||
}
|
||||
}
|
||||
if reply.len() != 8 {
|
||||
bail!(
|
||||
"expected 8-byte GetTagExpiry reply, got {} bytes",
|
||||
reply.len()
|
||||
);
|
||||
}
|
||||
Ok(Some(u64::from_le_bytes(
|
||||
reply.as_slice().try_into().expect("checked length"),
|
||||
)))
|
||||
}
|
||||
|
||||
// ── Phase 5g: cache metrics client helper ────────────────────────────
|
||||
|
||||
/// Fetch the peer's current cache-metrics snapshot.
|
||||
|
||||
Reference in New Issue
Block a user