From 1db3b8743a08347ffd7abc86aeb32546e4272e72 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 13 Sep 2026 06:24:45 +0100 Subject: [PATCH] feat(spec): add optional valid-until to capability-request Lets a requester bound how long its own ask is worth granting -- a receiver refuses a request whose deadline has already passed outright rather than presenting a stale ask to a human for approval, and a future relay/facilitator forwarding the request can drop it once expired rather than keep holding or forwarding it. Reuses the same message/token-lifetime concept the plan's own section E already names, rather than inventing a second TTL field. No new frame or verb, no conformance vector regen needed: capability-request's existing open extension tail already made this an additive field, matching every other domain's own use of the identical pattern. --- spec/management.cddl | 9 ++++++++- spec/protocol.cddl | 9 ++++++++- ts/packages/core/src/generated/protocol.ts | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/management.cddl b/spec/management.cddl index 1adb089..edb373f 100644 --- a/spec/management.cddl +++ b/spec/management.cddl @@ -32,8 +32,15 @@ manage-response-frame = { type: "manage-response", request-id: uint, outcome: ma ; (freshly minted token on the same response) rather than inventing a new ; response frame; a denial is an ordinary manage-error, the same convention ; room.join's own denial already uses. +; valid-until (wire-mesh#82) lets a requester bound how long its own ask is +; worth granting -- a receiver refuses a request whose deadline has already +; passed outright rather than presenting a stale ask to a human for +; approval, and a future relay/facilitator forwarding the request can drop +; it once expired rather than keep holding or forwarding it. Reuses the +; same message/token-lifetime concept the plan's own section E already +; names as its own open item, rather than inventing a second TTL field. $manage-command-params /= capability-request -capability-request = { verb: "capability.request", capability: tstr, * tstr => any } +capability-request = { verb: "capability.request", capability: tstr, ? valid-until: uint, * tstr => any } capability-grant-ok = { result: "ok", "granted-token": capability-token, * tstr => any } ; Revocation — new, not present in Cascade's frozen set. Gossiped revocation diff --git a/spec/protocol.cddl b/spec/protocol.cddl index ca4899f..bb8f837 100644 --- a/spec/protocol.cddl +++ b/spec/protocol.cddl @@ -389,8 +389,15 @@ manage-response-frame = { type: "manage-response", request-id: uint, outcome: ma ; (freshly minted token on the same response) rather than inventing a new ; response frame; a denial is an ordinary manage-error, the same convention ; room.join's own denial already uses. +; valid-until (wire-mesh#82) lets a requester bound how long its own ask is +; worth granting -- a receiver refuses a request whose deadline has already +; passed outright rather than presenting a stale ask to a human for +; approval, and a future relay/facilitator forwarding the request can drop +; it once expired rather than keep holding or forwarding it. Reuses the +; same message/token-lifetime concept the plan's own section E already +; names as its own open item, rather than inventing a second TTL field. $manage-command-params /= capability-request -capability-request = { verb: "capability.request", capability: tstr, * tstr => any } +capability-request = { verb: "capability.request", capability: tstr, ? valid-until: uint, * tstr => any } capability-grant-ok = { result: "ok", "granted-token": capability-token, * tstr => any } ; Revocation — new, not present in Cascade's frozen set. Gossiped revocation diff --git a/ts/packages/core/src/generated/protocol.ts b/ts/packages/core/src/generated/protocol.ts index d3953f9..b15a0ea 100644 --- a/ts/packages/core/src/generated/protocol.ts +++ b/ts/packages/core/src/generated/protocol.ts @@ -136,6 +136,7 @@ export const manageResponseFrameSchema = z.lazy(() => z.object({ export const capabilityRequestSchema = z.lazy(() => z.object({ "verb": z.literal("capability.request"), "capability": z.string(), + "valid-until": z.number().int().nonnegative().optional(), }).catchall(z.unknown())); export const capabilityGrantOkSchema = z.lazy(() => z.object({ "result": z.literal("ok"),