mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
desktop: build private agent tombstones
Add a pure owner-signed deleted aggregate builder that advances verified relay authority evidence without carrying active projections, with codec round-trip and invalid-input coverage. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -157,6 +157,42 @@ pub struct PromotionEvidence {
|
||||
pub definition_revision: u64,
|
||||
}
|
||||
|
||||
/// Build the next minimal deleted aggregate from verified relay-head evidence.
|
||||
///
|
||||
/// A tombstone carries no public projections or private active body. It advances
|
||||
/// the same CAS chain by exactly one and is signed by the owner, so deletion can
|
||||
/// use the aggregate route rather than legacy kind:5 mutation.
|
||||
#[allow(dead_code)] // Consumed by the aggregate deletion driver in the next slice.
|
||||
pub fn build_tombstone_event(
|
||||
owner_keys: &Keys,
|
||||
agent_pubkey: &str,
|
||||
current_generation: u64,
|
||||
current_private_event_id: &str,
|
||||
timestamp: &str,
|
||||
created_at: u64,
|
||||
) -> Result<Event, MigrationError> {
|
||||
let agent = parse_pubkey("agent_pubkey", agent_pubkey)?;
|
||||
let previous = nostr::EventId::from_hex(current_private_event_id)
|
||||
.map_err(|e| MigrationError::InvalidInput(format!("private event id: {e}")))?;
|
||||
let generation = current_generation
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| MigrationError::InvalidInput("aggregate generation overflow".into()))?;
|
||||
let payload = Payload {
|
||||
format: pma::FORMAT.to_string(),
|
||||
version: pma::VERSION,
|
||||
agent_pubkey: agent.to_hex(),
|
||||
owner_pubkey: owner_keys.public_key().to_hex(),
|
||||
generation,
|
||||
previous_event_id: Some(previous.to_hex()),
|
||||
state: State::Deleted,
|
||||
updated_at: timestamp.to_string(),
|
||||
active: None,
|
||||
deleted_at: Some(timestamp.to_string()),
|
||||
extensions: Default::default(),
|
||||
};
|
||||
pma::build_event(owner_keys, &payload, created_at).map_err(MigrationError::from)
|
||||
}
|
||||
|
||||
/// Build a signed, inert kind:30179 migration candidate for one agent.
|
||||
///
|
||||
/// Assembles the encrypted aggregate payload from the record + keys + exact
|
||||
|
||||
@@ -447,6 +447,67 @@ fn verify_rejects_wrong_definition_revision() {
|
||||
assert!(matches!(err, MigrationError::VerificationFailed(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tombstone_advances_verified_head_without_active_payload() {
|
||||
let owner = Keys::generate();
|
||||
let agent = Keys::generate();
|
||||
let previous = EventBuilder::new(Kind::TextNote, "previous")
|
||||
.sign_with_keys(&owner)
|
||||
.unwrap();
|
||||
let timestamp = "2026-08-05T18:00:00Z";
|
||||
|
||||
let event = build_tombstone_event(
|
||||
&owner,
|
||||
&agent.public_key().to_hex(),
|
||||
7,
|
||||
&previous.id.to_hex(),
|
||||
timestamp,
|
||||
CREATED_AT,
|
||||
)
|
||||
.unwrap();
|
||||
let (envelope, payload) =
|
||||
buzz_core_pkg::private_managed_agent::validate_and_decrypt(&event, &owner).unwrap();
|
||||
assert_eq!(envelope.generation, 8);
|
||||
assert_eq!(
|
||||
envelope.previous_event_id.map(|id| id.to_hex()),
|
||||
Some(previous.id.to_hex())
|
||||
);
|
||||
assert_eq!(
|
||||
payload.state,
|
||||
buzz_core_pkg::private_managed_agent::State::Deleted
|
||||
);
|
||||
assert!(payload.active.is_none());
|
||||
assert_eq!(payload.deleted_at.as_deref(), Some(timestamp));
|
||||
assert_eq!(payload.updated_at, timestamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tombstone_rejects_invalid_coordinate_or_generation_overflow() {
|
||||
let owner = Keys::generate();
|
||||
assert!(matches!(
|
||||
build_tombstone_event(
|
||||
&owner,
|
||||
"invalid",
|
||||
1,
|
||||
&"11".repeat(32),
|
||||
"2026-08-05T18:00:00Z",
|
||||
CREATED_AT
|
||||
),
|
||||
Err(MigrationError::InvalidInput(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
build_tombstone_event(
|
||||
&owner,
|
||||
&Keys::generate().public_key().to_hex(),
|
||||
u64::MAX,
|
||||
&"11".repeat(32),
|
||||
"2026-08-05T18:00:00Z",
|
||||
CREATED_AT,
|
||||
),
|
||||
Err(MigrationError::InvalidInput(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_rejects_stale_generation_head() {
|
||||
let fx = Fixture::new();
|
||||
|
||||
Reference in New Issue
Block a user