From a95ecb8e25f1652b36eaa338beba8cc464bd2119 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 12 Oct 2026 14:35:54 -0500 Subject: [PATCH] =?UTF-8?q?feat(sidecar):=20protocol=2014=20=E2=80=94=20th?= =?UTF-8?q?e=20Backpacks=20forward=20(PLAN=5FREDESIGNS=20=C2=A711.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /backpacks/read, a thin forward like /kits/uses: the body is stamped `backpacks.read` whatever it names, so the route can only ever read. Nothing is kept here. The bigger backpack an event lends is a permission in an ordinary perm.sync (D337) and needs nothing new. Still protocol 14 (D332); one test. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- sidecar/src/main.rs | 4 ++++ sidecar/src/web.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/sidecar/src/main.rs b/sidecar/src/main.rs index 39107cf..0b4ee34 100644 --- a/sidecar/src/main.rs +++ b/sidecar/src/main.rs @@ -188,6 +188,10 @@ use tracing_subscriber::EnvFilter; /// and `POST /economics/adjust` (staff). The plugin remembers a payment's key; this process keeps /// nothing. The tally's `balance` and the `economics.wiped` event ride the feed like any frame. /// +/// Backpacks (§20.4, the module's PLAN_REDESIGNS.md §11.3) adds one: `POST /backpacks/read`, +/// one player's backpack, read live. The plugin never writes a backpack, and a bigger one an event +/// lends is a `backpacks.size.N` permission in an ordinary `perm.sync`, so it needs nothing 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, §20 the step editor; diff --git a/sidecar/src/web.rs b/sidecar/src/web.rs index 51a76ec..ad0872b 100644 --- a/sidecar/src/web.rs +++ b/sidecar/src/web.rs @@ -139,6 +139,8 @@ pub async fn serve(addr: &str, state: AppState) -> anyhow::Result<()> { .route("/economics/pay", post(economics_pay)) .route("/economics/balances", post(economics_balances)) .route("/economics/adjust", post(economics_adjust)) + // Protocol 14, Backpacks (§20.4): one player's backpack, read live and never kept here. + .route("/backpacks/read", post(backpacks_read)) .route("/chat", post(chat)) // Protocol 11 (§30): the map. The picture passes through in slices and is never kept // here, and positions are asked for while somebody is looking and never filed (D111). The @@ -613,6 +615,13 @@ async fn economics_adjust(State(st): State, Json(body): Json) - forward_object(&st, body, "economics.adjust", "a balance adjustment").await } +/// One player's backpack (protocol 14). The body is `{steamId}`; the answer is +/// `backpacks.contents` (`exists`, and with a backpack its `capacity`, `pageSize` and `items`), or +/// `backpacks.error` with a reason. The plugin only reads, and nothing is kept here. +async fn backpacks_read(State(st): State, Json(body): Json) -> Response { + forward_object(&st, body, "backpacks.read", "a backpack read").await +} + /// Say one line in the server's chat. A repeated key is answered `ok` by the plugin and not said /// again, so a retried step never repeats itself. async fn chat(State(st): State, Json(body): Json) -> Response { @@ -1248,6 +1257,19 @@ mod tests { assert!(stamp(json!([1]), "economics.pay", "r-34").is_none()); } + /// Protocol 14, Backpacks: a read is stamped `backpacks.read` whatever its body names, so the + /// route can only ever read. + #[test] + fn protocol_14_backpacks_read_is_stamped() { + let body = json!({ "cmd": "backpacks.write", "steamId": "76561198000000001" }); + let stamped = stamp(body, "backpacks.read", "r-40").expect("an object is stamped"); + + assert_eq!(stamped["cmd"], "backpacks.read"); + assert_eq!(stamped["reqId"], "r-40"); + assert_eq!(stamped["steamId"], "76561198000000001"); + assert!(stamp(json!("76561198000000001"), "backpacks.read", "r-41").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] -- 2.49.1