diff --git a/sidecar/src/main.rs b/sidecar/src/main.rs index 226badc..5f554e9 100644 --- a/sidecar/src/main.rs +++ b/sidecar/src/main.rs @@ -171,6 +171,10 @@ use tracing_subscriber::EnvFilter; /// whatever its kind — so what changes here is what no longer has to fit: the reload window that /// had to sit inside [`rpc::REPLY_TIMEOUT`] is gone. /// +/// The permission manager's redesign (the module's PLAN_REDESIGNS.md §1, D160) adds one route, +/// `POST /permissions/inventory`: the plugin's whole store, in pages it sizes to the game link's +/// line cap. Forwarded like every other object; the pages are never kept here. +/// /// `docs/rust-link/PROTOCOL.md` is the specification — §8 the read path, §9 identity, §10 the /// mirror, §11 configuration, §12 clans, §13 the raid frame, §14 the leases, §15 the world verbs, /// §16 the rewards, §17 the map, §18 the optional mods, §19 the walk's fixes; this constant is one diff --git a/sidecar/src/web.rs b/sidecar/src/web.rs index b542d1e..9eba2d9 100644 --- a/sidecar/src/web.rs +++ b/sidecar/src/web.rs @@ -95,6 +95,10 @@ pub async fn serve(addr: &str, state: AppState) -> anyhow::Result<()> { // plugin will do with any of it. It puts `cmd` and `reqId` on the object and forwards it, // exactly as it forwards a link code. .route("/permissions/sync", post(perm_sync)) + // Protocol 13 (D160): the whole store, owners included, in pages. The body is + // `{snapshotId?, page?}` and is forwarded as it is; paging is the plugin's and the + // website's business, never this process's, which keeps no copy of any page. + .route("/permissions/inventory", post(perm_inventory)) // Protocol 5 (R18): the plugin's own view of the game host's configuration tree. All // three are correlated round trips and all three fail when the game is down, because // "what is on that host's disk" has no stale answer worth giving — and, unlike a board, @@ -435,6 +439,13 @@ async fn perm_sync(State(st): State, Json(body): Json) -> Respo respond(st.rpc.call(&st.game, command, &req_id).await) } +/// One page of the plugin's permission inventory (protocol 13). Page 0 without a `snapshotId` +/// starts a fresh read; any other page names the snapshot page 0 answered with. A `perm.error` +/// (`busy`, `stale`, `too-large`) is an answer, and arrives as a `200` like the sync's. +async fn perm_inventory(State(st): State, Json(body): Json) -> Response { + forward_object(&st, body, "perm.inventory", "an inventory request").await +} + #[derive(Debug, Deserialize)] struct ConfigPathQuery { path: String, @@ -1081,6 +1092,20 @@ mod tests { assert!(stamp(json!(["9"]), "tally.close", "r-4").is_none()); } + /// Protocol 13: an inventory page request keeps its paging fields and cannot become a sync. + #[test] + fn an_inventory_request_cannot_choose_its_own_command() { + let body = + json!({ "cmd": "perm.sync", "reqId": "theirs", "snapshotId": "abc123", "page": 2 }); + let stamped = stamp(body, "perm.inventory", "r-7").expect("an object is stamped"); + + assert_eq!(stamped["cmd"], "perm.inventory"); + assert_eq!(stamped["reqId"], "r-7"); + assert_eq!(stamped["snapshotId"], "abc123"); + assert_eq!(stamped["page"], 2); + assert!(stamp(json!([0]), "perm.inventory", "r-8").is_none()); + } + /// Protocol 12: a title set is forwarded whole. The markup inside each `text` is the module's /// and the plugin's business, so it must arrive byte for byte as the website composed it. #[test]