feat: v0.8 Iteration A — ID-class fixes, manifest privacy, registry + greetings

A1 — ID-class bug family (v0.6.1 persona split left network-NodeId comparisons
where posting IDs live). ~14 sites fixed with multi-persona semantics ("author is
one of MY posting identities", not == default):
- revoke_post_access / revoke_circle_access (revocation was broken for every post)
- group key create/add-member/rotate (add-member distribution now works)
- circle profile set/delete/get, encrypted-attachment CEK unwrap
- receipt/comment slot authorship, replication cycle, blob eviction own-tier
- orphaned GroupKeyRequest/Response (0xA1/0xA2) deleted (no send side existed)

A2 — Manifest privacy: AuthorManifest.author_addresses removed from struct AND
signature digest; CdnManifest tree-era fields dropped. Startup migrations re-sign
own manifests, purge unverifiable foreign copies, strip persona fields from legacy
network-id profile rows. Posting identities are now location-anonymous.

A3 — Discovery & first contact:
- Open-slot comments: derivable slot V_x (blake3 from author + slot_binder_nonce)
  so strangers pass the CDN comment-verification gate; OpenSlotDecl in FoF gating
- Greetings: HPKE-style sealed GreetingBody (return_path + fresh reply_pubkey),
  throwaway outer ID, size-bucketed; messaging-first (Reply/Dismiss, no vouch)
- Registry: frozen canonical registry post + REGISTRY_POST_ID, signed registration
  comments (self-certifying deletes), newest-wins per persona, --publish-registry
- Comment TTL: expires_at_ms inside signed digest v2 (context bump), rand 30-365d
  ordinary / fixed 30d registrations, expiry sweep on the eviction cycle
- Single accept_incoming_comment() gate wired into all three ingest sites, closing
  a pre-existing hole where both pull paths stored comments with no verification
- UI: consent panel (visible/listed/greetings, active choice at first publish),
  registry search in Discover, sealed "Say hi" compose, greetings inbox

Security fixes from review: forged-tombstone injection, delete-by-sender-trust,
remote SetPolicy poisoning, stored XSS (escapeHtml quotes, 7 pre-existing sites),
greeting off-switch now revokes prior bios' slots.

No PoW (rejected: inverted cost). Session-relay gating untouched.
201 core tests pass; CLI + desktop build; 3-node integration green.

DEPLOY GATE: wire-incompatible with v0.7.3 — ALPN bump to itsgoin/4 lands in
Iteration B. Do not build/deploy/anchor-swap before that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGiPD2cF75mnvneSCjdDC5
This commit is contained in:
Scott Reimers 2026-07-30 08:28:26 -04:00
parent 66c9851061
commit 17dbf076cb
19 changed files with 5237 additions and 524 deletions

View file

@ -297,4 +297,40 @@ mod tests {
assert!(!applied2);
assert!(s2.get_group_key(&group_id).unwrap().is_none());
}
/// A1 (multi-persona): a distribution post addressed to our SECOND
/// persona must still be applied — the trial-unwrap iterates ALL held
/// posting identities, not just the default one.
#[test]
fn second_persona_member_applies_distribution() {
let s = temp_storage();
let (admin_sec, admin_id) = make_keypair(1);
let (default_sec, default_id) = make_keypair(2); // default persona (not a member)
let (second_sec, second_id) = make_keypair(3); // second persona (the member)
let group_id = [43u8; 32];
let record = GroupKeyRecord {
group_id,
circle_name: "second".to_string(),
epoch: 1,
group_public_key: [8u8; 32],
admin: admin_id,
created_at: 100,
canonical_root_post_id: None,
};
let group_seed = [11u8; 32];
// Addressed ONLY to the second persona.
let (_pid, post, visibility) = build_distribution_post(
&admin_id, &admin_sec, &record, &group_seed, &[second_id],
).unwrap();
let personas = vec![
mk_persona(default_sec, default_id),
mk_persona(second_sec, second_id),
];
let applied = try_apply_distribution_post(&s, &post, &visibility, &personas).unwrap();
assert!(applied, "second persona must unwrap the group seed");
assert_eq!(s.get_group_seed(&group_id, 1).unwrap().unwrap(), group_seed);
}
}