mirror of
https://github.com/block/buzz.git
synced 2026-11-06 09:21:45 +00:00
fix(managed-agents): boot reconcile survives pin-less records
Boot reconcile minted zero 30179s on a store with any record whose
legacy relay_url pin is "". Two defects compounded:
- buzz-core validate_identity_and_config rejected an empty relay_url.
The per-record pin has been ignored at read time since #2122 (the
agent resolves to the active workspace relay), so "" is a legal
record and must encode. Keep only the 4096 upper bound.
- reconcile_agents_in_dir_with used `?` on each record, so the first
build failure aborted the loop and every record after it in file
order was never visited. Log the failing record and keep walking;
each record is its own transaction, so partial progress is safe.
Tests: buzz-core `empty_relay_url_is_accepted`; desktop
`empty_relay_url_pin_does_not_abort_boot_reconcile` writes a pin-less
record followed by a normal one and asserts both get 30177 and a
decryptable 30179. The desktop test fails on f7c247756 with
"invalid relay_url length" and passes with this change.
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -417,7 +417,9 @@ fn validate_identity_and_config(
|
|||||||
if let Some(auth_tag) = &identity.auth_tag {
|
if let Some(auth_tag) = &identity.auth_tag {
|
||||||
validate_auth_tag(auth_tag, &owner.to_hex(), agent)?;
|
validate_auth_tag(auth_tag, &owner.to_hex(), agent)?;
|
||||||
}
|
}
|
||||||
if config.relay_url.is_empty() || config.relay_url.len() > 4096 {
|
// Empty is legal: a pin-less agent resolves to the active workspace relay
|
||||||
|
// at read time, so only the upper bound is enforced here.
|
||||||
|
if config.relay_url.len() > 4096 {
|
||||||
return Err(Error::InvalidPayload("invalid relay_url length".into()));
|
return Err(Error::InvalidPayload("invalid relay_url length".into()));
|
||||||
}
|
}
|
||||||
if config.name.is_empty() || config.name.len() > 4096 {
|
if config.name.is_empty() || config.name.len() > 4096 {
|
||||||
@@ -764,6 +766,16 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pin-less agent (empty `relay_url`, legal since the per-record pin
|
||||||
|
/// became read-time-ignored) must still produce a valid payload.
|
||||||
|
#[test]
|
||||||
|
fn empty_relay_url_is_accepted() {
|
||||||
|
let owner = Keys::generate();
|
||||||
|
let mut candidate = payload(&owner, &Keys::generate());
|
||||||
|
candidate.config.relay_url.clear();
|
||||||
|
validate_payload(&candidate).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_owner_attestation_passes_and_binds_agent() {
|
fn valid_owner_attestation_passes_and_binds_agent() {
|
||||||
let owner = Keys::generate();
|
let owner = Keys::generate();
|
||||||
|
|||||||
@@ -144,8 +144,12 @@ fn reconcile_agents_in_dir_with(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if retain_agent_record_at_boot(&conn, keys, record)? {
|
// One unpublishable record must not strand every record after it in
|
||||||
reconciled += 1;
|
// file order: log it and keep walking.
|
||||||
|
match retain_agent_record_at_boot(&conn, keys, record) {
|
||||||
|
Ok(true) => reconciled += 1,
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(e) => eprintln!("buzz-desktop: agent-event-reconcile: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,51 @@ fn keyring_resident_nsec_is_hydrated_for_private_config() {
|
|||||||
assert_eq!(payload.identity.private_key_nsec, nsec);
|
assert_eq!(payload.identity.private_key_nsec, nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A record whose legacy `relay_url` pin is empty (legal since #2122 — the
|
||||||
|
/// pin is ignored at read time) must not stop the boot reconcile: the records
|
||||||
|
/// after it in file order still get their first 30179.
|
||||||
|
#[test]
|
||||||
|
fn empty_relay_url_pin_does_not_abort_boot_reconcile() {
|
||||||
|
use nostr::ToBech32;
|
||||||
|
|
||||||
|
let owner_keys = nostr::Keys::generate();
|
||||||
|
let pinless_keys = nostr::Keys::generate();
|
||||||
|
let later_keys = nostr::Keys::generate();
|
||||||
|
let mut pinless = sample_record(&pinless_keys.public_key().to_hex(), "pinless");
|
||||||
|
pinless.relay_url.clear();
|
||||||
|
pinless.private_key_nsec = pinless_keys.secret_key().to_bech32().unwrap();
|
||||||
|
let mut later = sample_record(&later_keys.public_key().to_hex(), "later");
|
||||||
|
later.private_key_nsec = later_keys.secret_key().to_bech32().unwrap();
|
||||||
|
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
write_store(&dir, &[pinless, later]);
|
||||||
|
let db_path = dir.path().join("retention.db");
|
||||||
|
reconcile_agents_in_dir_with(
|
||||||
|
dir.path(),
|
||||||
|
&owner_keys,
|
||||||
|
&db_path,
|
||||||
|
None::<&crate::secret_store::SecretStore>,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let conn = open_retention_db(&db_path).unwrap();
|
||||||
|
let owner = owner_keys.public_key().to_hex();
|
||||||
|
for keys in [&pinless_keys, &later_keys] {
|
||||||
|
let agent = keys.public_key().to_hex();
|
||||||
|
assert!(
|
||||||
|
get_retained_event(&conn, KIND_MANAGED_AGENT, &owner, &agent)
|
||||||
|
.unwrap()
|
||||||
|
.is_some(),
|
||||||
|
"30177 for {agent}"
|
||||||
|
);
|
||||||
|
let row = get_retained_event(&conn, KIND_PRIVATE_MANAGED_AGENT, &owner, &agent)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap_or_else(|| panic!("first 30179 for {agent}"));
|
||||||
|
let event = nostr::Event::from_json(&row.raw_event).unwrap();
|
||||||
|
private_managed_agent::validate_and_decrypt(&event, &owner_keys).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fresh_record_is_retained_pending() {
|
fn fresh_record_is_retained_pending() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user