From 3222267dfae4c77fdbcd13e6bcb5b65d1ad11980 Mon Sep 17 00:00:00 2001 From: Yash Datta Date: Mon, 27 Jul 2026 09:10:43 +0800 Subject: [PATCH] feat: carry blackboard reads/writes in the protocol crate (lockstep) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lockstep half of the daemon's goal-blackboard slice 1. CONTRIBUTING requires this crate to move with the daemon's packages/protocol. - `reads` / `writes` on CollaborationRole: the artifact kinds a role may read and write on the goal blackboard (`spec`, `research`, `adr`, `task-list`, `diff`, `findings`, or `extra/`). `None` means "use the daemon's default profile for this role name", which is NOT the same as an empty list ("read/write nothing") — the daemon distinguishes them, so these stay Option> rather than defaulting to an empty vec here. Collapsing the two would silently strip every default profile. Kept as plain strings, not an enum: the artifact vocabulary has a fixed core plus an `extra/` escape hatch, so an enum here would reject a valid kind a newer daemon accepts. The wire_format review role now carries populated lists rather than None, so the recursive camelCase walker actually visits them and a future rename of either field fails the wire test. cargo build + clippy + test green: 354 tests; clippy warnings unchanged (7 before, 7 after on codeoid-protocol, verified by stashing). --- crates/codeoid-protocol/src/session.rs | 15 +++++++++++++++ crates/codeoid-protocol/tests/wire_format.rs | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/crates/codeoid-protocol/src/session.rs b/crates/codeoid-protocol/src/session.rs index 1384456..6a46180 100644 --- a/crates/codeoid-protocol/src/session.rs +++ b/crates/codeoid-protocol/src/session.rs @@ -162,6 +162,21 @@ pub struct CollaborationRole { /// to. Write authority is opt-in per role. #[serde(default, skip_serializing_if = "Option::is_none")] pub write: Option, + /// Goal-blackboard artifact kinds this role may READ — `spec`, `research`, + /// `adr`, `task-list`, `diff`, `findings`, or `extra/`. + /// + /// `None` = the daemon's default profile for this role name; a role with no + /// profile and no declaration reads nothing. This is what makes reviewer + /// independence structural: `review` reads `diff`+`spec` and NOT + /// `research` or its peers' `findings`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reads: Option>, + /// Artifact kinds this role may WRITE. `None` = the default profile for + /// this role name. A role writing a multi-writer kind (`findings`) writes + /// into its own slot, chosen daemon-side, so one reviewer can never + /// overwrite another's. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub writes: Option>, } /// Set on a role-CHILD of a collaborative session: which collaboration it diff --git a/crates/codeoid-protocol/tests/wire_format.rs b/crates/codeoid-protocol/tests/wire_format.rs index e698fd6..699a40d 100644 --- a/crates/codeoid-protocol/tests/wire_format.rs +++ b/crates/codeoid-protocol/tests/wire_format.rs @@ -128,6 +128,8 @@ fn sample_session_info() -> SessionInfo { count: None, purpose: None, write: None, + reads: None, + writes: None, }, codeoid_protocol::CollaborationRole { name: "review".into(), @@ -136,6 +138,8 @@ fn sample_session_info() -> SessionInfo { count: Some(3), purpose: Some("independent critique".into()), write: Some(false), + reads: Some(vec!["spec".into(), "diff".into()]), + writes: Some(vec!["findings".into()]), }, ], }),