Phase 4 (0.6.3-beta): posting-key / network-key split (plumbing)
Decouple the signing identity from the network identity. This phase
ships the plumbing only — every device still has exactly one posting
identity, copied from the network key on first 0.6.3 launch so all
existing signed content keeps verifying. Phase 5 builds the
multi-persona UX on top.
Types:
- New PostingIdentity struct: { node_id, secret_seed, display_name,
created_at }
Storage:
- New posting_identities(node_id, secret_seed, display_name,
created_at) table
- Methods: upsert / get / list / delete posting identities;
get/set default posting id (stored in settings)
- seed_posting_identity_from_network: idempotent migration inserts
the network key as the single posting identity and sets it default
on first 0.6.3 launch
Node:
- default_posting_id + default_posting_secret fields populated on
startup via the migration
- All content signing / encryption / key wrapping now uses
default_posting_secret; the old Node.secret_seed field is gone
(iroh holds the network secret internally)
- author field on all locally-created content is now
default_posting_id (equal to node_id for upgraders until Phase 5
introduces separate personas)
- Auto-follow-self covers both network_id and default_posting_id
(same in 0.6.3, may diverge in 0.6.4+)
Export/import:
- Bundle now includes posting_identities.json in
IdentityOnly / PostsWithIdentity / Everything scopes
- restore_posting_identities(zip, storage) reads and upserts on
import
Smoke-tested:
- Fresh 0.6.3 install: posting_identities seeded from network key;
default set; new post's author = default_posting_id = network_id
- Two-node pull sync: B pulls A's post, signature verifies across
the wire
- 111 core tests pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
975e7b9bfe
commit
ce4b989b17
5 changed files with 287 additions and 50 deletions
|
|
@ -15,7 +15,40 @@ use crate::blob::BlobStore;
|
|||
use crate::content::compute_post_id;
|
||||
use crate::export::{ExportManifest, ExportedPost};
|
||||
use crate::storage::StoragePool;
|
||||
use crate::types::{Attachment, NodeId, Post, PostVisibility};
|
||||
use crate::types::{Attachment, NodeId, Post, PostVisibility, PostingIdentity};
|
||||
|
||||
/// Extract posting_identities.json from an export ZIP and upsert each entry
|
||||
/// into storage. Called during import so multi-persona users restore all
|
||||
/// their posting keys. Idempotent — INSERT OR IGNORE on conflict. No-op if
|
||||
/// the file is missing (pre-0.6.3 bundle).
|
||||
pub async fn restore_posting_identities(
|
||||
zip_path: &Path,
|
||||
storage: &StoragePool,
|
||||
) -> anyhow::Result<usize> {
|
||||
let zip_path = zip_path.to_path_buf();
|
||||
let identities: Vec<PostingIdentity> = tokio::task::spawn_blocking(move || -> anyhow::Result<Vec<PostingIdentity>> {
|
||||
let file = std::fs::File::open(&zip_path)?;
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
let buf = {
|
||||
let mut entry = match archive.by_name("itsgoin-export/posting_identities.json") {
|
||||
Ok(e) => e,
|
||||
Err(_) => return Ok(Vec::new()),
|
||||
};
|
||||
let mut s = String::new();
|
||||
entry.read_to_string(&mut s)?;
|
||||
s
|
||||
};
|
||||
Ok(serde_json::from_str(&buf).unwrap_or_default())
|
||||
}).await??;
|
||||
|
||||
let s = storage.get().await;
|
||||
let mut restored = 0usize;
|
||||
for id in &identities {
|
||||
s.upsert_posting_identity(id)?;
|
||||
restored += 1;
|
||||
}
|
||||
Ok(restored)
|
||||
}
|
||||
|
||||
/// What to do with the imported data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue