feat(sidecar): protocol 14 — the Backpacks forward (PLAN_REDESIGNS §11.3) #26

Merged
whitlocktech merged 1 commits from feat/rust-backpacks into edge 2026-10-12 14:41:47 -05:00
2 changed files with 26 additions and 0 deletions

View File

@@ -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;

View File

@@ -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<AppState>, Json(body): Json<Value>) -
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<AppState>, Json(body): Json<Value>) -> 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<AppState>, Json(body): Json<Value>) -> 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]