Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
7fe0493
feat(desktop): add workspace-scoped agent definition store scope model
Aug 3, 2026
108a31e
feat(desktop): add scoped _at() storage APIs and retarget event-sync …
Aug 3, 2026
80a6877
feat(desktop): wire fail-closed scope seam + import_identity scope se…
Aug 3, 2026
6cb0a4a
feat(desktop): scope-stable restore, shutdown, and runtime list
Aug 3, 2026
e6aa269
feat(desktop): scope initialization state machine with canonical clai…
Aug 3, 2026
b795ed6
feat(desktop): implement ordered per-scope migration pipeline and sco…
Aug 3, 2026
00a3381
feat(desktop): implement Phase 3 runtime ownership + Mesh scope rules
Aug 3, 2026
91859ea
feat(desktop): plumb WorkspaceApplyResult through frontend boundary (…
Aug 3, 2026
6eec536
fix(desktop): update runtime test helper to pass scope_id to starting()
Aug 3, 2026
8a35f5e
test(desktop): Phase 4 drain journal unit tests and execute_drain_jou…
Aug 3, 2026
e1234a2
test(desktop): Phase 4 scope lifecycle, crash-boundary, and Mesh rela…
Aug 3, 2026
370de88
chore(desktop): trim file-size ratchet violations to pass desktop-check
Aug 3, 2026
27370bc
fix(desktop): resolve clippy dead_code and lint warnings from push gate
Aug 3, 2026
3325363
fix(desktop): hold transition lock continuously from drain through co…
Aug 3, 2026
78b4e84
fix(desktop): pass-2 corrections C1-C5 (generation checks, fail-close…
Aug 3, 2026
18c4be3
fix(desktop): pass-2 correction C6 — derive RetentionScope and Mesh r…
Aug 3, 2026
372b0fa
fix(desktop): pass-2 correction C7 — degraded results observable + Ap…
Aug 3, 2026
d4391c9
fix(desktop): restore commit_active_scope as test-only helper
Aug 3, 2026
3ea3d70
fix(desktop): use null-coalescing fallback in AppShell composite reco…
Aug 3, 2026
d8ee408
fix(desktop): suppress dead_code clippy warnings on boot-migration shims
Aug 3, 2026
d26c15a
test(desktop): add two-workspace relay partition e2e probe
Aug 3, 2026
b0cc726
fix(desktop): pass-3 corrections C1–C7 (workspace-scoped agent store)
Aug 3, 2026
db225c6
fix(desktop): repair clippy and doc-comment issues from pass-3 correc…
Aug 3, 2026
ad3230b
fix(desktop): resume-pass corrections — Option A Mesh, versioned read…
Aug 3, 2026
f1fb83c
fix(desktop): resolve file-size gate violations (import.rs, scope_ini…
Aug 3, 2026
40607d4
fix(desktop): pass-1 review corrections (round 2) — compensation, Mes…
Aug 4, 2026
6dc4e29
Merge origin/main into duncan/workspace-scoped-agent-store
Aug 4, 2026
ff16a80
fix(desktop): pass-2 review corrections (round 3) — captured respawn,…
Aug 4, 2026
e73458e
fix(desktop): resolve clippy empty_line_after_doc_comments in file-si…
Aug 4, 2026
3957d73
fix(desktop): area-1 compensation lock order — adapter acquires store…
Aug 4, 2026
1ec287f
fix(desktop): area-2+5 captured restart context, epoch_for, teams param
Aug 4, 2026
1dc537d
fix(desktop): area-3+4 snapshot import seams, transition helpers
Aug 4, 2026
fcfd41d
test(desktop): area-4 serialization direction tests for workspace tra…
Aug 4, 2026
1a1571b
fix(desktop): resolve clippy warnings introduced in Phase 3 seam commits
Aug 4, 2026
d065860
fix(desktop): split Phase 3 test files to satisfy 1000-line size ratchet
Aug 4, 2026
4634fe1
chore(desktop): merge origin/main into workspace-scoped-agent-store
Aug 4, 2026
09207bc
fix(desktop): skip OS keychain in test builds to prevent headless hangs
Aug 5, 2026
50f615e
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 5, 2026
8b046bc
fix(desktop): wire production callers through shared mesh preflight h…
Aug 5, 2026
c29cd65
fix(desktop): extract mesh readiness helpers to satisfy file-size rat…
Aug 5, 2026
48015b8
fix(desktop): resolve P3 compliance gaps in Areas 3 and 4
Aug 5, 2026
a1228b3
fix(desktop): replace map_or(false,...) with is_some_and per clippy
Aug 5, 2026
91aea2d
fix(desktop): resolve P4 pass-1 review defects in workspace-scoped ag…
Aug 5, 2026
45f199b
fix(desktop): resolve P4 pass-2 test-vs-claim defects in workspace-sc…
Aug 5, 2026
7c455d9
fix(desktop): serialize generation-sensitive epoch tests with SCOPE_G…
Aug 5, 2026
27ea607
fix(desktop): suppress await_holding_lock clippy lint on epoch tests
Aug 5, 2026
6739f15
test(desktop): resolve P4 pass-3 test-vs-claim defects in workspace-s…
Aug 5, 2026
d71cd62
fix(desktop): resolve authorized-pass round-4 test-vs-claim residuals
Aug 5, 2026
b450d46
test(managed-agents): structural guard evidence for concurrency lock …
Aug 5, 2026
e4abdf9
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 5, 2026
816222e
fix(desktop): remove unused SCOPE_GENERATION_TEST_LOCK import in test…
Aug 5, 2026
c40a889
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 6, 2026
9bac71b
fix(workspace): represent applied-but-blocked as a truthful third state
Aug 6, 2026
2f53939
feat(desktop): add cross-workspace agent-library compatibility seam (…
Aug 11, 2026
1ac3f84
style(desktop): rustfmt runtime_commands_tests
Aug 11, 2026
887793c
feat(desktop): add cross-workspace agent-library data model (Phase 1)
Aug 11, 2026
de1dbdd
fix(desktop): close cross-workspace library review findings F1-F5
Aug 11, 2026
d5f777e
fix(desktop): close round-2 residuals in workspace-scoped agent library
Aug 13, 2026
49c0275
feat(desktop): add relation resolver + inbound-30177 canonical-linkag…
Aug 13, 2026
1d74aa5
fix(desktop): make inbound 30177 frozen-linkage convergence observabl…
Aug 13, 2026
ba052bc
test(desktop): pin inbound 30177 frozen-linkage convergence at the co…
Aug 13, 2026
58ead52
fix(agents): bind session-config frames to the emitting runtime (P23-C1)
Aug 17, 2026
3f933e1
feat(agents): add pure identity-binding helpers for the agent library
Aug 17, 2026
0e77706
fix(desktop): require scope match in runtime lifecycle capability
Aug 17, 2026
072b08e
refactor(desktop): split observer lifecycle command to sibling module
Aug 17, 2026
0ec342f
feat(desktop): add commit-time binding mint + orphan journal helpers
Aug 17, 2026
b41de58
feat(desktop): add crash-safe mint-order orchestrator (P5-I1)
Aug 17, 2026
1e369e9
feat(managed-agents): add §2.5 recovery reap for unreferenced orphans
Aug 17, 2026
383db8a
feat(managed-agents): add §3.5 retirement finalizer marker observer
Aug 17, 2026
159898d
fix(desktop): verify minted keys via durable OS read-back not cache
Aug 17, 2026
59800a0
feat(desktop): gate owner-identity egress behind witnessed leases (WS…
Aug 18, 2026
085029d
docs(egress): correct closed-world sink enumeration in owner_identity…
Aug 18, 2026
05d7eca
feat(egress): durable owner-identity capabilities + huddle session (C2a)
Aug 18, 2026
02e238b
feat(egress): thread durable bearer capability through Blossom media …
Aug 18, 2026
fb488a3
fix(egress): stamp durable capabilities from the issuing lease (C2 fi…
Aug 18, 2026
5b14931
feat(egress): owner-identity artifact capabilities + boundary stamp (C3)
Aug 18, 2026
017c4ae
Merge origin/main into duncan/workspace-scoped-agent-store
Aug 18, 2026
bddd216
feat(owner-egress): stamp P33 identity-export producers (C4)
Aug 18, 2026
1b3e348
fix(e2e): stamp-wrap owner-identity artifact bridge mocks
Aug 18, 2026
822c750
chore(deps): update h2 to fix RUSTSEC-2026-0258
Aug 18, 2026
9d2882b
fix(desktop): route phone recovery through identity-transition coordi…
Aug 18, 2026
b0da3e8
refactor(desktop): extract identity-transition coordinator to its own…
Aug 18, 2026
5b01079
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 18, 2026
be2911f
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 18, 2026
875c8f3
feat(desktop): classify imported-identity persistence into three dura…
Aug 18, 2026
1a3713c
feat(desktop): add durable identity-transition intent journal (P28-C1)
Aug 18, 2026
092acde
refactor(desktop): take workspace_transition unconditionally in ident…
Aug 18, 2026
09bc664
feat(desktop): honor pending transition journal at startup (P28-C1)
Aug 18, 2026
998f1ef
feat(desktop): wire owner-identity egress barrier into identity impor…
Aug 18, 2026
b917540
fix(desktop): gate identity transition at both supersession boundarie…
Aug 18, 2026
df51dce
refactor(desktop): make identity-transition coordinator runtime-generic
Aug 19, 2026
65eda54
test(desktop): add §7 sink, latch, and early-gate coordinator fixture…
Aug 19, 2026
5766132
refactor(desktop): inject identity-persist seams into transition coor…
Aug 19, 2026
dd555f7
test(desktop): drive §7 no-scope and activation-race commit schedules…
Aug 19, 2026
eeadfac
refactor(desktop): retire C5 dead-code allows to test-only or deletion
Aug 19, 2026
eeca9e7
Merge origin/main (934f3325c) into workspace-scoped-agent-store
Aug 19, 2026
557c056
test(desktop): serialize archive tests against the egress registry
Aug 19, 2026
249c494
fix(identity): admit artifacts before reading owner keys
Aug 19, 2026
5d4f2c0
fix(identity): order backup locking before egress admission
Aug 19, 2026
6f35ad7
test(desktop): close owner artifact scan universe
Aug 19, 2026
08c2047
Merge remote-tracking branch 'origin/main' into duncan/workspace-scop…
Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,7 @@ async fn tokio_main() -> Result<()> {
memory_enabled: config.memory_enabled,
harness_name: crate::config::normalize_agent_command_identity(&config.agent_command),
relay_url: config.relay_url.clone(),
start_nonce: runtime_start_nonce.clone(),
});

if !config.memory_enabled {
Expand Down
14 changes: 12 additions & 2 deletions crates/buzz-acp/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,13 @@ pub struct PromptContext {
/// the desktop keys per (agent, relay) pair, e.g. `session_config_captured`,
/// mirroring the `managed_agent_runtime_lifecycle` frames.
pub relay_url: String,
/// Unpredictable identity for this exact harness generation
/// (`BUZZ_MANAGED_AGENT_START_NONCE`). Rides in `session_config_captured`
/// alongside `relay_url` so the desktop can bind the frame to the exact
/// tracked runtime that emitted it, the same generation check the lifecycle
/// frames already carry. Empty when the harness was launched outside a
/// managed-agent runtime (no nonce in env) — such frames the desktop drops.
pub start_nonce: String,
}

impl AgentPool {
Expand Down Expand Up @@ -1246,9 +1253,11 @@ async fn create_session_and_apply_model(
// than falling back to the pre-switch `resp.raw.models`.
"models": effort_snapshot.get("models").cloned().unwrap_or(serde_json::Value::Null),
"modelOverridden": agent.model_overridden && switch_succeeded,
// Pair identity for the desktop session-config cache, which is
// keyed by (agent, relay) like the lifecycle frames.
// Pair identity for the desktop session-config cache, which binds
// the frame to the exact tracked runtime by (agent, relay, nonce)
// like the lifecycle frames. A frame with no nonce is dropped.
"relayUrl": ctx.relay_url,
"startNonce": ctx.start_nonce,
}),
);

Expand Down Expand Up @@ -7959,6 +7968,7 @@ printf '%s\n' '{{"jsonrpc":"2.0","id":0,"result":{{"stopReason":"end_turn"}}}}'"
memory_enabled: false,
harness_name: "goose".to_string(),
relay_url: "ws://127.0.0.1:3000".to_string(),
start_nonce: "test-nonce".to_string(),
}
}

Expand Down
190 changes: 190 additions & 0 deletions crates/buzz-test-client/tests/e2e_managed_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,193 @@ async fn test_managed_agent_tombstone_deletes_coordinate() {

client.disconnect().await.expect("disconnect");
}

/// NIP-33 author-coordinate isolation probe (relay-level, two keypairs on one relay).
///
/// This test verifies relay-level NIP-33 author scoping. It does NOT cover
/// desktop workspace activation, `apply_workspace`, the scoped file store,
/// inbound event routing, or runtime fan-out — those are verified by desktop
/// unit tests and the live two-workspace probe run after Thufir's clear.
///
/// Two distinct owner keypairs share one relay. The test verifies:
///
/// 1. Owner A's events are author-scoped: a subscription filtered by
/// `author: owner_a` returns only owner_a's events, not owner_b's.
/// 2. Symmetrically, owner B's subscription returns only owner_b's events.
/// 3. NIP-33 coordinates are scoped by `(kind, author, d-tag)`. A subscription
/// for `(kind=30177, author=owner_b, d=shared_d_tag)` returns B's event —
/// not A's — confirming the (kind, author, d-tag) tuple is unique per owner.
///
/// The filesystem isolation proof (different `(relay_url, owner_pubkey)` pairs
/// always produce distinct scope_id directories) is covered separately by the
/// scope_id unit tests.
#[tokio::test]
#[ignore]
async fn test_two_workspace_relay_partition() {
let url = relay_url();

// Workspace A and workspace B: two distinct owner keypairs (same relay)
let owner_a_keys = Keys::generate();
let owner_b_keys = Keys::generate();

// Use the same d-tag value (simulating same agent slug) in both workspaces.
// Relay NIP-33 addressing is (kind, author, d-tag) — so same d-tag but
// different authors are distinct coordinates that cannot collide.
let shared_d_tag = "workspace-leak-probe-agent";

// Publish agent definition as owner A
let mut client_a = BuzzTestClient::connect(&url, &owner_a_keys)
.await
.expect("owner_a connect");
let content_a = agent_projection_content("WorkspaceA-ExclusiveAgent");
let event_a = EventBuilder::new(Kind::Custom(AGENT_KIND), content_a.clone())
.tag(Tag::identifier(shared_d_tag))
.sign_with_keys(&owner_a_keys)
.expect("owner_a sign");
let ok_a = client_a
.send_event(event_a)
.await
.expect("send owner_a event");
assert!(
ok_a.accepted,
"relay rejected owner_a's event: {}",
ok_a.message
);

// Publish agent definition as owner B (same relay, different owner)
let mut client_b = BuzzTestClient::connect(&url, &owner_b_keys)
.await
.expect("owner_b connect");
let content_b = agent_projection_content("WorkspaceB-ExclusiveAgent");
let event_b = EventBuilder::new(Kind::Custom(AGENT_KIND), content_b.clone())
.tag(Tag::identifier(shared_d_tag))
.sign_with_keys(&owner_b_keys)
.expect("owner_b sign");
let ok_b = client_b
.send_event(event_b)
.await
.expect("send owner_b event");
assert!(
ok_b.accepted,
"relay rejected owner_b's event: {}",
ok_b.message
);

// ── Direction 1: owner_a's author-scoped subscription ──────────────────
// Owner A subscribes to their own agent coordinate.
// Must see exactly their definition, not owner_b's.
let sid_a = sub_id("probe-workspace-a");
let filter_a = Filter::new()
.kind(Kind::Custom(AGENT_KIND))
.author(owner_a_keys.public_key())
.custom_tags(SingleLetterTag::lowercase(Alphabet::D), [shared_d_tag]);
client_a
.subscribe(&sid_a, vec![filter_a])
.await
.expect("owner_a subscribe");
let events_a = client_a
.collect_until_eose(&sid_a, Duration::from_secs(5))
.await
.expect("owner_a collect");

assert_eq!(
events_a.len(),
1,
"owner_a's NIP-33 subscription must return exactly 1 event (their own), got {}",
events_a.len()
);
assert!(
events_a[0].content.contains("WorkspaceA-ExclusiveAgent"),
"owner_a's event must contain workspace-A content, got: {}",
events_a[0].content
);
assert_eq!(
events_a[0].pubkey,
owner_a_keys.public_key(),
"owner_a's subscription must not return events from owner_b"
);
assert!(
!events_a[0].content.contains("WorkspaceB-ExclusiveAgent"),
"workspace A's subscription must NOT return workspace B's content"
);

// ── Direction 2: owner_b's author-scoped subscription ──────────────────
// Symmetric: owner B must see only their definition.
let sid_b = sub_id("probe-workspace-b");
let filter_b = Filter::new()
.kind(Kind::Custom(AGENT_KIND))
.author(owner_b_keys.public_key())
.custom_tags(SingleLetterTag::lowercase(Alphabet::D), [shared_d_tag]);
client_b
.subscribe(&sid_b, vec![filter_b])
.await
.expect("owner_b subscribe");
let events_b = client_b
.collect_until_eose(&sid_b, Duration::from_secs(5))
.await
.expect("owner_b collect");

assert_eq!(
events_b.len(),
1,
"owner_b's NIP-33 subscription must return exactly 1 event (their own), got {}",
events_b.len()
);
assert!(
events_b[0].content.contains("WorkspaceB-ExclusiveAgent"),
"owner_b's event must contain workspace-B content, got: {}",
events_b[0].content
);
assert_eq!(
events_b[0].pubkey,
owner_b_keys.public_key(),
"owner_b's subscription must not return events from owner_a"
);
assert!(
!events_b[0].content.contains("WorkspaceA-ExclusiveAgent"),
"workspace B's subscription must NOT return workspace A's content"
);

// ── Direction 3: NIP-33 coordinate ownership — B's coord returns B's event ──
// Owner A subscribes to the same d-tag but filtered by owner_b's pubkey.
// This proves that NIP-33 coordinates are scoped by (kind, author, d-tag):
// A's coordinate and B's coordinate are distinct even though they share
// the same d-tag value, because they are authored by different pubkeys.
// The query returns B's event — not A's — confirming per-author isolation.
let sid_cross = sub_id("probe-cross-scope");
let filter_cross = Filter::new()
.kind(Kind::Custom(AGENT_KIND))
.author(owner_b_keys.public_key()) // owner_b's pubkey
.custom_tags(SingleLetterTag::lowercase(Alphabet::D), [shared_d_tag]);
client_a
.subscribe(&sid_cross, vec![filter_cross])
.await
.expect("cross-scope subscribe");
let events_cross = client_a
.collect_until_eose(&sid_cross, Duration::from_secs(5))
.await
.expect("cross-scope collect");

// The cross-scope query must return B's event (by B's pubkey), not A's.
// This confirms NIP-33 coordinates are scoped by (kind, author, d-tag).
assert_eq!(
events_cross.len(),
1,
"cross-scope query must return exactly 1 event (B's own), got {}",
events_cross.len()
);
assert_eq!(
events_cross[0].pubkey,
owner_b_keys.public_key(),
"cross-scope query must return B's event, not A's"
);
assert!(
!events_cross[0]
.content
.contains("WorkspaceA-ExclusiveAgent"),
"cross-scope query must NOT return workspace A's definitions"
);

client_a.disconnect().await.expect("owner_a disconnect");
client_b.disconnect().await.expect("owner_b disconnect");
}
1 change: 1 addition & 0 deletions desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ strip-ansi-escapes = "0.2"
tracing = "0.1"

[dev-dependencies]
tauri = { version = "2", features = ["test"] }
tauri-utils = "2"
# `test-util` enables tokio's paused-clock (`start_paused`) so the relay
# admission gate tests can assert exact wait durations without real sleeps.
Expand Down
Loading
Loading