wire-mesh-core 3.8.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/adapters/byte-stream-connection.d.cts +2 -2
  2. package/dist/adapters/byte-stream-connection.d.mts +2 -2
  3. package/dist/adapters/frame-codec.d.cts +1 -1
  4. package/dist/adapters/frame-codec.d.mts +1 -1
  5. package/dist/adapters/node-identity.d.cts +2 -2
  6. package/dist/adapters/node-identity.d.mts +2 -2
  7. package/dist/adapters/tcp-transport.d.cts +1 -1
  8. package/dist/adapters/tcp-transport.d.mts +1 -1
  9. package/dist/adapters/threshold-identity.d.cts +2 -2
  10. package/dist/adapters/threshold-identity.d.mts +2 -2
  11. package/dist/adapters/threshold-wasm.d.cts +1 -1
  12. package/dist/adapters/threshold-wasm.d.mts +1 -1
  13. package/dist/adapters/tls-transport.d.cts +1 -1
  14. package/dist/adapters/tls-transport.d.mts +1 -1
  15. package/dist/domain/advert-extension-policy.d.cts +1 -1
  16. package/dist/domain/advert-extension-policy.d.mts +1 -1
  17. package/dist/domain/bulk.d.cts +2 -2
  18. package/dist/domain/bulk.d.mts +2 -2
  19. package/dist/domain/capability-grant.cjs +4 -4
  20. package/dist/domain/capability-grant.d.cts +3 -3
  21. package/dist/domain/capability-grant.d.mts +3 -3
  22. package/dist/domain/capability-grant.mjs +2 -2
  23. package/dist/domain/capability-request.cjs +18 -6
  24. package/dist/domain/capability-request.d.cts +10 -3
  25. package/dist/domain/capability-request.d.mts +10 -3
  26. package/dist/domain/capability-request.mjs +15 -4
  27. package/dist/domain/coordinator-election.d.cts +1 -1
  28. package/dist/domain/coordinator-election.d.mts +1 -1
  29. package/dist/domain/data-sync.d.cts +2 -2
  30. package/dist/domain/data-sync.d.mts +2 -2
  31. package/dist/domain/device-id.d.cts +1 -1
  32. package/dist/domain/device-id.d.mts +1 -1
  33. package/dist/domain/direct-manage-request.d.cts +2 -2
  34. package/dist/domain/direct-manage-request.d.mts +2 -2
  35. package/dist/domain/gossip-expansion.d.cts +1 -1
  36. package/dist/domain/gossip-expansion.d.mts +1 -1
  37. package/dist/domain/grant-candidates.cjs +2 -2
  38. package/dist/domain/grant-candidates.d.cts +1 -1
  39. package/dist/domain/grant-candidates.d.mts +1 -1
  40. package/dist/domain/grant-candidates.mjs +1 -1
  41. package/dist/domain/handshake.d.cts +1 -1
  42. package/dist/domain/handshake.d.mts +1 -1
  43. package/dist/domain/hub-mailbox.d.cts +1 -1
  44. package/dist/domain/hub-mailbox.d.mts +1 -1
  45. package/dist/domain/mesh-session.cjs +2 -2
  46. package/dist/domain/mesh-session.d.cts +3 -3
  47. package/dist/domain/mesh-session.d.mts +3 -3
  48. package/dist/domain/mesh-session.mjs +1 -1
  49. package/dist/domain/notice-board.d.cts +3 -3
  50. package/dist/domain/notice-board.d.mts +3 -3
  51. package/dist/domain/path-trace.d.cts +1 -1
  52. package/dist/domain/path-trace.d.mts +1 -1
  53. package/dist/domain/peer-advert.cjs +2 -2
  54. package/dist/domain/peer-advert.d.cts +2 -2
  55. package/dist/domain/peer-advert.d.mts +2 -2
  56. package/dist/domain/peer-advert.mjs +1 -1
  57. package/dist/domain/relay-advert.d.cts +1 -1
  58. package/dist/domain/relay-advert.d.mts +1 -1
  59. package/dist/domain/relay-hub.cjs +72 -15
  60. package/dist/domain/relay-hub.d.cts +6 -2
  61. package/dist/domain/relay-hub.d.mts +6 -2
  62. package/dist/domain/relay-hub.mjs +72 -15
  63. package/dist/domain/relay-use-gate.d.cts +2 -2
  64. package/dist/domain/relay-use-gate.d.mts +2 -2
  65. package/dist/domain/revocation-view.cjs +2 -2
  66. package/dist/domain/revocation-view.d.cts +2 -2
  67. package/dist/domain/revocation-view.d.mts +2 -2
  68. package/dist/domain/revocation-view.mjs +1 -1
  69. package/dist/domain/room-rekey.d.cts +3 -3
  70. package/dist/domain/room-rekey.d.mts +3 -3
  71. package/dist/domain/room-token-verification.cjs +2 -2
  72. package/dist/domain/room-token-verification.d.cts +1 -1
  73. package/dist/domain/room-token-verification.d.mts +1 -1
  74. package/dist/domain/room-token-verification.mjs +1 -1
  75. package/dist/domain/room.d.cts +4 -4
  76. package/dist/domain/room.d.mts +4 -4
  77. package/dist/domain/shard-manifest.d.cts +1 -1
  78. package/dist/domain/shard-manifest.d.mts +1 -1
  79. package/dist/domain/threshold-subject.cjs +2 -2
  80. package/dist/domain/threshold-subject.mjs +1 -1
  81. package/dist/domain/token-predicates.cjs +184 -0
  82. package/dist/domain/token-predicates.d.cts +2 -0
  83. package/dist/domain/token-predicates.d.mts +2 -0
  84. package/dist/domain/token-predicates.mjs +173 -0
  85. package/dist/{token-scope-CxHXTT3u.cjs → domain/token-scope.cjs} +3 -12
  86. package/dist/domain/token-scope.d.cts +11 -0
  87. package/dist/domain/token-scope.d.mts +11 -0
  88. package/dist/{token-scope-Z4bmci4M.mjs → domain/token-scope.mjs} +1 -1
  89. package/dist/domain/tokens.cjs +466 -9
  90. package/dist/domain/tokens.d.cts +131 -2
  91. package/dist/domain/tokens.d.mts +131 -2
  92. package/dist/domain/tokens.mjs +458 -1
  93. package/dist/domain/topology-snapshot.d.cts +1 -1
  94. package/dist/domain/topology-snapshot.d.mts +1 -1
  95. package/dist/domain/topology.d.cts +1 -1
  96. package/dist/domain/topology.d.mts +1 -1
  97. package/dist/domain/webrtc-signaling.cjs +2 -2
  98. package/dist/domain/webrtc-signaling.d.cts +2 -2
  99. package/dist/domain/webrtc-signaling.d.mts +2 -2
  100. package/dist/domain/webrtc-signaling.mjs +1 -1
  101. package/dist/generated/protocol.cjs +4 -2
  102. package/dist/generated/protocol.d.cts +1 -1
  103. package/dist/generated/protocol.d.mts +1 -1
  104. package/dist/generated/protocol.mjs +4 -2
  105. package/dist/{identity-DJbr96J3.d.cts → identity-B6nhlSOT.d.cts} +1 -1
  106. package/dist/{identity-ViYdZrIU.d.mts → identity-EV-HHv_D.d.mts} +1 -1
  107. package/dist/ports/identity.d.cts +1 -1
  108. package/dist/ports/identity.d.mts +1 -1
  109. package/dist/ports/transport.d.cts +1 -1
  110. package/dist/ports/transport.d.mts +1 -1
  111. package/dist/{protocol-BgR1e_NI.d.cts → protocol-DzmpxxUr.d.cts} +6 -0
  112. package/dist/{protocol-BgR1e_NI.d.mts → protocol-DzmpxxUr.d.mts} +6 -0
  113. package/dist/{room-token-verification-7zD4ib-A.d.mts → room-token-verification-Dh7SGJfK.d.cts} +2 -2
  114. package/dist/{room-token-verification-hFtBDZPO.d.cts → room-token-verification-Hb1LkpFh.d.mts} +2 -2
  115. package/dist/token-predicates-D4NNPLnp.d.cts +72 -0
  116. package/dist/token-predicates-DqpG8VZZ.d.mts +72 -0
  117. package/dist/{transport-Dv_LTCsF.d.mts → transport-bD3p_TO_.d.mts} +1 -1
  118. package/dist/{transport-DP3hdehk.d.cts → transport-gDbSHO-r.d.cts} +1 -1
  119. package/package.json +9 -1
  120. package/dist/tokens-BMWCnQEF.d.cts +0 -158
  121. package/dist/tokens-D8JlTJNA.mjs +0 -617
  122. package/dist/tokens-DRkm_QOR.d.mts +0 -158
  123. package/dist/tokens-tPJQ7Vzt.cjs +0 -664
@@ -1,2 +1,131 @@
1
- import { _ as sig1ToBeSigned, a as MintVerdict, c as RevocationEntryVerdictReason, d as VerifyCapabilityTokenOptions, f as VerifyRevocationEntryOptions, g as mintRevocationEntry, h as mintCapabilityToken, i as MintRevocationEntryOptions, l as TokenVerdict, m as canGrantVia, n as MintCapabilityTokenOptions, o as RevocationCheck, p as canGrant, r as MintRefusalReason, s as RevocationEntryVerdict, t as MANAGE_GRANT_CAPABILITY, u as TokenVerdictReason, v as verifyCapabilityToken, y as verifyRevocationEntry } from "../tokens-DRkm_QOR.mjs";
2
- export { MANAGE_GRANT_CAPABILITY, MintCapabilityTokenOptions, MintRefusalReason, MintRevocationEntryOptions, MintVerdict, RevocationCheck, RevocationEntryVerdict, RevocationEntryVerdictReason, TokenVerdict, TokenVerdictReason, VerifyCapabilityTokenOptions, VerifyRevocationEntryOptions, canGrant, canGrantVia, mintCapabilityToken, mintRevocationEntry, sig1ToBeSigned, verifyCapabilityToken, verifyRevocationEntry };
1
+ import { B as IdentityKey, D as DeviceId, bt as RevocationEntry, en as TokenClaims, p as CapabilityToken, yt as RevocationClaims } from "../protocol-DzmpxxUr.mjs";
2
+ import { t as IdentityPort } from "../identity-EV-HHv_D.mjs";
3
+ import { t as Clock } from "../clock-DiSx-WKM.mjs";
4
+ import { l as GrantSubject, m as TokenDelegateHandler, r as ConditionsContext, u as NarrowingCandidate } from "../token-predicates-DqpG8VZZ.mjs";
5
+ import { PredicateNode } from "trilean";
6
+ //#region src/domain/tokens.d.ts
7
+ /** The capability verb of a grant-capability (wire-mesh#323): authority to mint other capability tokens, the same register manage:revoke already established for authority over tokens. */
8
+ export declare const MANAGE_GRANT_CAPABILITY = "manage:grant";
9
+ /**
10
+ * The revocation view a verifier consults. Returns every recorded, already-signature-verified revocation-claims for tokenId, across every issuer that has ever submitted one -- unfiltered by the store itself. The actual verifier obligation (an entry counts against a token when its own issuer matches the token's own issuer, OR its optional `authorization` grants delegated revoke authority -- management.cddl) is checked by the caller (verifyTokenChain), not here, since that check needs the target token's own scope plus identity/clock to verify a nested authorization token, none of which a store constructed ahead of time has access to. Implementations ingest gossiped revocation-announce frames via verifyRevocationEntry (which enforces each entry's own signature and self-certification) and key the resulting claims by token-id alone -- a token-id can legitimately carry multiple recorded entries from different issuers.
11
+ */
12
+ export interface RevocationCheck {
13
+ entriesFor: (tokenId: Uint8Array) => Promise<readonly RevocationClaims[]>;
14
+ }
15
+ export type TokenVerdictReason = "malformed" | "bad_signature" | "wrong_issuer" | "bearer_mismatch" | "expired" | "not_yet_valid" | "content_expired" | "revoked" | "delegation_exceeds_parent" | "parent_invalid" |
16
+ /** The authorised-by link's own obligations failed: the cited grant-capability did not decode or fully verify, its bearer is not this token's issuer, its capability is not manage:grant, its grants-capability does not match, the scope or expiry widened, the depth bound was exceeded, or its conditions (evaluated against THIS token's claims) did not hold. Collapsed to one reason the same way every narrowing failure already collapses to delegation_exceeds_parent. */
17
+ "authorisation_invalid" |
18
+ /** The parent/authorised-by walk exceeded MAX_CHAIN_DEPTH. */
19
+ "chain_too_deep" |
20
+ /** The walk revisited a payload already on the current path: a cycle two colluding issuers constructed. */
21
+ "chain_cycle" |
22
+ /** claims.conditions is present but its bstr fails CBOR decode, or decodes to something that is not a JSON array of valid trilean PredicateNodes -- fail-closed per CONVENTIONS.md's verifier-obligations glossary, the same treatment "malformed" already gives an undecodable payload. */
23
+ "conditions_invalid" |
24
+ /** claims.conditions decoded and validated, but at least one entry did not evaluate to a definite `true` (indeterminate or false) -- the issuer's own additional restriction was not met. */
25
+ "conditions_not_satisfied";
26
+ export type TokenVerdict = {
27
+ ok: true;
28
+ claims: TokenClaims;
29
+ /** The device-id at the root of this token's delegation chain: its own issuer when it carries no parent, otherwise the root of its parent's chain. Lets a caller (e.g. core/room's obligation that a chain must terminate at the path's own owner, or the verifier itself for a DM) check the chain's root with one equality comparison instead of re-walking the parent chain a second time. */
30
+ rootIssuer: DeviceId;
31
+ /** The root ancestor's own self-certifying issuer-key -- already decoded during the walk (every TokenClaims carries its own issuer-key), just threaded up rather than re-derived. Lets a caller compute an ECDH shared secret against the chain's root (e.g. room.rekey's own sender, for a named room whose token chain terminates at the owner) without a separate, out-of-band way to learn that issuer's public key. */
32
+ rootIssuerKey: IdentityKey;
33
+ /** How many delegation hops this token is from its own root -- 0 for a root grant. Costs nothing extra once rootIssuer is being tracked, and makes the delegation bound observable for diagnostics. */
34
+ depth: number;
35
+ } | {
36
+ ok: false;
37
+ reason: TokenVerdictReason;
38
+ };
39
+ export interface VerifyCapabilityTokenOptions {
40
+ identity: IdentityPort;
41
+ clock: Clock;
42
+ revocation: RevocationCheck;
43
+ /** When given, the token must bear this device -- the caller presenting a token to authorise itself, not someone else. */
44
+ expectedBearer?: DeviceId;
45
+ /** Registers domain-specific delegate systems a presented token's own `conditions` entries may name, beyond the five mandatory narrowing ops (which are never reachable from `conditions` -- see evaluateConditions's own doc comment). None are registered by wire-mesh-core itself; a domain (a message TTL, #84's future revoke-authorization check) supplies its own here. A `conditions` entry naming a system absent from this map is indeterminate, and therefore fails the whole token -- fail-closed, not a silent no-op. */
46
+ extraPredicateResolvers?: Readonly<Record<string, TokenDelegateHandler<ConditionsContext>>>;
47
+ }
48
+ /** RFC 9052 §4.4 Sig_structure for a COSE_Sign1 with no external AAD: ["Signature1", protected, external_aad, payload]. Exported (not just used internally by verifyTokenChain) so any other self-certifying COSE_Sign1 construction in this codebase -- e.g. threshold-subject.ts's own to-be-signed bytes for a FROST-signed capability-token/revocation-entry/handle-record/room-notice -- reuses this single construction rather than a second hand-rolled one. */
49
+ export declare function sig1ToBeSigned(protectedHeader: Uint8Array, payload: Uint8Array): Uint8Array;
50
+ /**
51
+ * Verifies one capability token per tokens.cddl's own documented rules: the token is a well-formed COSE_Sign1 whose signature actually verifies against its own embedded issuer-key, that issuer-key is self-certifying (sha256(issuer-key.public-key) equals the claimed issuer device-id -- no shared secret needed to check this), the token is currently valid (not expired, not before not-before, not revoked by its own issuer), and -- recursively -- any parent delegation narrows rather than widens across all three axes of authority: the parent's bearer must be this token's issuer (the delegation chain is unbroken), this token's expiry must not exceed its parent's, and this token's scope must narrow its parent's (same kind; equal-or-descendant path when the parent carries one) with an identical capability verb (the capability-verb grammar has no sub-verb relation, so a different verb is a different authority, not a narrower one). Undecodable payload bytes return "malformed" and undecodable parent bytes return "parent_invalid" -- hostile input produces a verdict, never a throw.
52
+ */
53
+ export declare function verifyCapabilityToken(token: CapabilityToken, options: VerifyCapabilityTokenOptions): Promise<TokenVerdict>;
54
+ export type RevocationEntryVerdictReason = "malformed" | "bad_signature" | "wrong_issuer";
55
+ export type RevocationEntryVerdict = {
56
+ ok: true;
57
+ claims: RevocationClaims;
58
+ } | {
59
+ ok: false;
60
+ reason: RevocationEntryVerdictReason;
61
+ };
62
+ export interface VerifyRevocationEntryOptions {
63
+ /** Crypto primitives only -- any IdentityPort instance can verify any entry, since everything needed to check one travels inside the entry itself. */
64
+ identity: IdentityPort;
65
+ }
66
+ /**
67
+ * Verifies one gossiped revocation-entry (management.cddl): a well-formed COSE_Sign1 whose signature verifies against its own embedded issuer-key, where that issuer-key is self-certifying (sha256(issuer-key.public-key) equals the claimed issuer device-id). A verifier that ingests a revocation-announce frame runs each entry through this before recording it in its revocation view; entries failing here are dropped, not stored. The issuer-match against a specific token's own issuer (only a token's own issuer may revoke it) is deliberately NOT checked here -- it happens at lookup time in RevocationCheck, against whichever token is being verified.
68
+ */
69
+ export declare function verifyRevocationEntry(entry: RevocationEntry, options: VerifyRevocationEntryOptions): Promise<RevocationEntryVerdict>;
70
+ export type MintRefusalReason = "already_expired" | "parent_malformed" | "parent_bearer_mismatch" | "expires_exceeds_parent" | "scope_does_not_narrow" | "capability_mismatch" | "delegation_exceeds_parent" |
71
+ /** parent and authorised-by are mutually exclusive link kinds; a manage:grant capability must be minted through authorised-by only. */
72
+ "links_exclusive" |
73
+ /** The cited grant-capability did not decode, or its own conditions bytes did not. */
74
+ "authorisation_malformed" | "authorisation_parent_forbidden" | "authorisation_bearer_mismatch" | "authorisation_capability_mismatch" | "authorisation_scope_does_not_narrow" | "authorisation_exceeds_authoriser" | "authorisation_delegation_exceeds" |
75
+ /** The authoriser's conditions, evaluated against the candidate being minted, did not all hold (wire-mesh#323 subject mode). */
76
+ "authorisation_conditions_not_satisfied";
77
+ export type MintVerdict = {
78
+ ok: true;
79
+ token: CapabilityToken;
80
+ } | {
81
+ ok: false;
82
+ reason: MintRefusalReason;
83
+ };
84
+ /**
85
+ * A pure query: could deviceId, presenting heldToken as its own delegation authority," successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
86
+ *
87
+ * Deliberately narrower than a full mint attempt in one respect: this checks only the narrowing rules tokens.cddl's own delegation obligations require (bearer match, expiry, scope, capability, delegations-remaining), the same scope mintCapabilityToken itself checks a *parent* against -- it does not verify heldToken's own signature or revocation status, exactly as mintCapabilityToken never re-verifies its own parent's signature either. A caller that also needs heldToken's cryptographic validity confirmed calls verifyCapabilityToken separately.
88
+ *
89
+ * Async as of the predicate-list evaluator (issue #85): checkNarrowing now routes through trilean's own evaluatePredicate, which is asynchronous throughout -- there is no synchronous path through a real evaluator call, so this is a genuine, deliberate breaking change to what was previously a synchronous pure function. No existing consumer of wire-mesh-core calls canGrant (confirmed against agent-comms, the only other repo depending on this package), so there is nothing to migrate.
90
+ */
91
+ export declare function canGrant(heldToken: CapabilityToken, deviceId: DeviceId, candidate: Readonly<NarrowingCandidate>, now: number): Promise<boolean>;
92
+ /** A pure query: could deviceId, presenting heldGrantCapability as its minting authority, mint a token matching candidate right now (wire-mesh#323) -- canGrant's authorised-by analogue, with the same deliberately-narrower-than-verify caveat canGrant's own doc comment states: the held token's signature and revocation status are not checked here; a caller needing those confirmed calls verifyCapabilityToken separately. The candidate carries a bearer because the authoriser's subject-mode conditions read it (the no-self-grant bar). */
93
+ export declare function canGrantVia(heldGrantCapability: CapabilityToken, deviceId: DeviceId, candidate: Readonly<GrantSubject>, now: number): Promise<boolean>;
94
+ export interface MintCapabilityTokenOptions {
95
+ /** The issuer -- signs the token, and supplies the self-certifying issuer/issuer-key claims. */
96
+ identity: IdentityPort;
97
+ clock: Clock;
98
+ tokenId: Uint8Array<ArrayBuffer>;
99
+ bearer: DeviceId;
100
+ capability: TokenClaims["capability"];
101
+ scope: TokenClaims["scope"];
102
+ expires: number;
103
+ notBefore?: number;
104
+ /** How many further delegation hops the *resulting* token itself permits below it -- unrelated to, and never a bound on, whether `identity` may mint further tokens of its own at the root level for other bearers. Those are two different facts: a token minted with `delegationsRemaining: 0` genuinely cannot itself be re-delegated (correct -- e.g. a room owner's own self-signed root grant, which should never be handed onward), but that same `0` says nothing about the issuer's own separate, ordinary authority to mint additional independent root-level grants (naming no `parent` at all) for other bearers. Root-level minting for a second bearer is never blocked by any existing token's own `delegationsRemaining`, because it uses no `parent` in the first place -- there is no narrowing check to run. Confirmed live in agent-comms' own room-membership implementation (`ExaDev/agent-comms` PR #72): each member's own join/invite grant is minted as its own independent, parent-less, root-level token precisely because the room owner's `delegationsRemaining: 0` self-grant cannot parent anything -- correct by this same reasoning, not a workaround. */
105
+ delegationsRemaining?: number;
106
+ /** The issuer's own token, when this is a delegation rather than a root grant. Its claims are checked against every narrowing rule below -- mint refuses rather than producing a token verifyCapabilityToken would reject anyway. Omit entirely for a root-level grant (including a second, independent root-level grant for a different bearer under the same capability/scope this issuer already grants elsewhere) -- there is no bound on how many such root grants an issuer may mint, since none of them narrows any other. */
107
+ parent?: CapabilityToken;
108
+ /** Additional, issuer-chosen restrictions beyond the five mandatory narrowing checks (issue #85) -- CBOR-encoded into claims.conditions verbatim, evaluated by every verifier via evaluateConditions. Strictly additive: has no bearing on narrowing, which mint enforces separately above regardless of what's given here. Not re-validated against trilean's own schema before encoding -- the TS type already guarantees a well-formed PredicateNode[] at this call site, unlike the bytes a verifier decodes from an untrusted wire token, which always are. */
109
+ conditions?: PredicateNode[];
110
+ /** The grant-capability authorising this mint (wire-mesh#323): the resulting token carries it as authorised-by, and every link obligation (bearer match, manage:grant verb, grants-capability match, scope and expiry narrowing, depth consumption, and the authoriser's subject-mode conditions against this very candidate) is enforced here, at issuance, rather than left for the far end as a bare authorisation_invalid. Mutually exclusive with parent. */
111
+ authorisedBy?: CapabilityToken;
112
+ /** On a manage:grant token: the single verb it authorises minting. Absent means any verb within scope. */
113
+ grantsCapability?: TokenClaims["capability"];
114
+ /** On a manage:request token (wire-mesh#324): the single verb it authorises requesting. Absent means any verb within scope. */
115
+ requestsCapability?: TokenClaims["capability"];
116
+ }
117
+ /**
118
+ * Mints one capability token: builds token-claims from the given fields, signs it as a COSE_Sign1 under `identity`'s own key, with a protected header matching what the frozen conformance vectors actually encode (`{1: alg, 4: issuer device-id}`, not the empty header a token merely needs to verify against itself).
119
+ *
120
+ * When `parent` is given, every one of `tokens.cddl`'s own narrowing obligations is enforced here, at issuance, rather than left for the far end to discover minutes or hours later as a bare `delegation_exceeds_parent` from `verifyCapabilityToken` -- the same "fail loudly, fail early" reasoning that governs every other boundary in this codebase. An issuer minting an invalid delegation is a bug in the caller; this function refuses rather than producing a token indistinguishable from a valid one until someone else verifies it.
121
+ */
122
+ export declare function mintCapabilityToken(options: MintCapabilityTokenOptions): Promise<MintVerdict>;
123
+ export interface MintRevocationEntryOptions {
124
+ /** The token's own issuer -- only a token's own issuer may revoke it (management.cddl), so this must be the same identity that minted the token being revoked. */
125
+ identity: IdentityPort;
126
+ tokenId: Uint8Array<ArrayBuffer>;
127
+ revokedAt: number;
128
+ }
129
+ /** Mints one revocation-entry (management.cddl): a COSE_Sign1 over revocation-claims, signed the same way mintCapabilityToken signs a token. No narrowing chain to check -- a revocation entry has no parent and cannot fail to be issuable the way a delegated token can, so this returns the entry directly rather than a verdict. */
130
+ export declare function mintRevocationEntry(options: MintRevocationEntryOptions): Promise<RevocationEntry>;
131
+ //#endregion
@@ -1,2 +1,459 @@
1
- import { a as mintRevocationEntry, c as verifyRevocationEntry, i as mintCapabilityToken, n as canGrant, o as sig1ToBeSigned, r as canGrantVia, s as verifyCapabilityToken, t as MANAGE_GRANT_CAPABILITY } from "../tokens-D8JlTJNA.mjs";
1
+ import { capabilityTokenSchema, revocationClaimsSchema, tokenClaimsSchema } from "../generated/protocol.mjs";
2
+ import { bytesToHex } from "./device-id.mjs";
3
+ import { bytesEqual, scopeNarrows } from "./token-scope.mjs";
4
+ import { conditionsListSchema, evaluateConditions, evaluateNarrowing } from "./token-predicates.mjs";
5
+ import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
6
+ //#region src/domain/tokens.ts
7
+ /** The capability verb of a grant-capability (wire-mesh#323): authority to mint other capability tokens, the same register manage:revoke already established for authority over tokens. */
8
+ const MANAGE_GRANT_CAPABILITY = "manage:grant";
9
+ /** Both link kinds walk at most this many hops before the verifier gives up, mirroring Rust's own MAX_CHAIN_DEPTH. Two colluding issuers can construct a mutually-bearing parent cycle that would otherwise recurse forever, and the authorised-by link makes the attack cheaper to mount, so TS gains the bound rather than relying on JS recursion limits. */
10
+ const MAX_CHAIN_DEPTH = 64;
11
+ /** RFC 9052 §4.4 Sig_structure for a COSE_Sign1 with no external AAD: ["Signature1", protected, external_aad, payload]. Exported (not just used internally by verifyTokenChain) so any other self-certifying COSE_Sign1 construction in this codebase -- e.g. threshold-subject.ts's own to-be-signed bytes for a FROST-signed capability-token/revocation-entry/handle-record/room-notice -- reuses this single construction rather than a second hand-rolled one. */
12
+ function sig1ToBeSigned(protectedHeader, payload) {
13
+ return encode([
14
+ "Signature1",
15
+ protectedHeader,
16
+ /* @__PURE__ */ new Uint8Array(0),
17
+ payload
18
+ ], cdeEncodeOptions);
19
+ }
20
+ /** Normalises cbor2's encode() (and any other Uint8Array<ArrayBufferLike>-typed construction) to a fresh, non-shared, whole-buffer Uint8Array<ArrayBuffer> -- what the generated schemas' concrete-typed fields require. */
21
+ function buf(bytes) {
22
+ return Uint8Array.from(bytes);
23
+ }
24
+ function encodeBuf(value) {
25
+ return buf(encode(value, cdeEncodeOptions));
26
+ }
27
+ /** The COSE protected header every capability-token/revocation-entry envelope in this codebase actually signs over: label 1 (alg) and label 4 (kid, the issuer's own device-id) -- matching the frozen conformance vectors, not the empty header a token merely needs to verify against itself. */
28
+ function protectedHeaderFor(identity) {
29
+ return encodeBuf({
30
+ 1: identity.identityKey.alg,
31
+ 4: identity.deviceId
32
+ });
33
+ }
34
+ /** Decodes and validates a parent token's own claims from its raw CapabilityToken tuple -- the same decode `verifyTokenChain` performs on `claims.parent`, extracted here so mint can check narrowing against a parent's real claims without duplicating the CBOR/schema plumbing. Returns undefined for anything that doesn't parse; the caller turns that into its own refusal reason since "malformed" means something different at mint time than at verify time. */
35
+ function decodeTokenClaims(token) {
36
+ const [, , payload] = token;
37
+ if (payload === null) return void 0;
38
+ let decoded;
39
+ try {
40
+ decoded = decode(payload, cdeDecodeOptions);
41
+ } catch {
42
+ return;
43
+ }
44
+ const result = tokenClaimsSchema.safeParse(decoded);
45
+ return result.success ? result.data : void 0;
46
+ }
47
+ /**
48
+ * Verifies one capability token per tokens.cddl's own documented rules: the token is a well-formed COSE_Sign1 whose signature actually verifies against its own embedded issuer-key, that issuer-key is self-certifying (sha256(issuer-key.public-key) equals the claimed issuer device-id -- no shared secret needed to check this), the token is currently valid (not expired, not before not-before, not revoked by its own issuer), and -- recursively -- any parent delegation narrows rather than widens across all three axes of authority: the parent's bearer must be this token's issuer (the delegation chain is unbroken), this token's expiry must not exceed its parent's, and this token's scope must narrow its parent's (same kind; equal-or-descendant path when the parent carries one) with an identical capability verb (the capability-verb grammar has no sub-verb relation, so a different verb is a different authority, not a narrower one). Undecodable payload bytes return "malformed" and undecodable parent bytes return "parent_invalid" -- hostile input produces a verdict, never a throw.
49
+ */
50
+ async function verifyCapabilityToken(token, options) {
51
+ const verdict = await verifyTokenChain(token, {
52
+ identity: options.identity,
53
+ clock: options.clock,
54
+ revocation: options.revocation,
55
+ ...options.extraPredicateResolvers !== void 0 ? { extraPredicateResolvers: options.extraPredicateResolvers } : {}
56
+ });
57
+ if (!verdict.ok) return verdict;
58
+ if (options.expectedBearer !== void 0 && !bytesEqual(verdict.claims.bearer, options.expectedBearer)) return {
59
+ ok: false,
60
+ reason: "bearer_mismatch"
61
+ };
62
+ return verdict;
63
+ }
64
+ /**
65
+ * Does one recorded revocation-claims entry actually revoke targetClaims, per management.cddl's own additive obligation? Valid when EITHER the entry's own issuer equals the target token's own issuer (the original, unconditional rule -- only a token's own issuer may revoke it), OR the entry carries an `authorization` that independently verifies as an ordinary capability-token -- with `expectedBearer` set to THIS entry's own `issuer`, proving the authorization was actually granted to the party submitting this revocation, not merely referenced from someone else's -- whose own `capability` is `manage:revoke` (spec/registry/core-capabilities.md) and whose own `scope` narrows targetClaims' scope. An authorization that fails any part of this (wrong capability, scope doesn't narrow, fails ordinary verification -- expired, revoked, bad signature, bearer mismatch) makes the entry no more valid than if `authorization` were absent; it never falls back to weakening the issuer-match rule.
66
+ */
67
+ async function revocationEntryGrantsRevoke(entry, targetClaims, options) {
68
+ if (bytesEqual(entry.issuer, targetClaims.issuer)) return true;
69
+ if (entry.authorization === void 0) return false;
70
+ let decodedAuthorization;
71
+ try {
72
+ decodedAuthorization = decode(entry.authorization, cdeDecodeOptions);
73
+ } catch {
74
+ return false;
75
+ }
76
+ const authorizationResult = capabilityTokenSchema.safeParse(decodedAuthorization);
77
+ if (!authorizationResult.success) return false;
78
+ const authorizationVerdict = await verifyCapabilityToken(authorizationResult.data, {
79
+ ...options,
80
+ expectedBearer: entry.issuer
81
+ });
82
+ return authorizationVerdict.ok && authorizationVerdict.claims.capability === "manage:revoke" && scopeNarrows(authorizationVerdict.claims.scope, targetClaims.scope);
83
+ }
84
+ async function verifyTokenChain(token, options, subjectClaims, depth = 0, seen = /* @__PURE__ */ new Set()) {
85
+ const [protectedHeader, , payload, signature] = token;
86
+ if (payload === null) return {
87
+ ok: false,
88
+ reason: "malformed"
89
+ };
90
+ if (depth >= MAX_CHAIN_DEPTH) return {
91
+ ok: false,
92
+ reason: "chain_too_deep"
93
+ };
94
+ const payloadHex = bytesToHex(payload);
95
+ if (seen.has(payloadHex)) return {
96
+ ok: false,
97
+ reason: "chain_cycle"
98
+ };
99
+ const deeperSeen = new Set(seen).add(payloadHex);
100
+ let decodedClaims;
101
+ try {
102
+ decodedClaims = decode(payload, cdeDecodeOptions);
103
+ } catch {
104
+ return {
105
+ ok: false,
106
+ reason: "malformed"
107
+ };
108
+ }
109
+ const claimsResult = tokenClaimsSchema.safeParse(decodedClaims);
110
+ if (!claimsResult.success) return {
111
+ ok: false,
112
+ reason: "malformed"
113
+ };
114
+ const claims = claimsResult.data;
115
+ if (!await options.identity.verify(claims["issuer-key"], sig1ToBeSigned(protectedHeader, payload), signature)) return {
116
+ ok: false,
117
+ reason: "bad_signature"
118
+ };
119
+ const derivedIssuerId = await options.identity.deriveDeviceId(claims["issuer-key"]["public-key"]);
120
+ if (!bytesEqual(derivedIssuerId, claims.issuer)) return {
121
+ ok: false,
122
+ reason: "wrong_issuer"
123
+ };
124
+ const now = options.clock.now();
125
+ if (claims.expires <= now) return {
126
+ ok: false,
127
+ reason: "expired"
128
+ };
129
+ if (claims["not-before"] !== void 0 && claims["not-before"] > now) return {
130
+ ok: false,
131
+ reason: "not_yet_valid"
132
+ };
133
+ const validUntil = claims["valid-until"];
134
+ if (validUntil !== void 0 && validUntil <= now) return {
135
+ ok: false,
136
+ reason: "content_expired"
137
+ };
138
+ const revocationEntries = await options.revocation.entriesFor(claims["token-id"]);
139
+ for (const entry of revocationEntries) if (await revocationEntryGrantsRevoke(entry, claims, options)) return {
140
+ ok: false,
141
+ reason: "revoked"
142
+ };
143
+ if (claims.conditions !== void 0) {
144
+ let decodedConditions;
145
+ try {
146
+ decodedConditions = decode(claims.conditions, cdeDecodeOptions);
147
+ } catch {
148
+ return {
149
+ ok: false,
150
+ reason: "conditions_invalid"
151
+ };
152
+ }
153
+ const conditionsResult = conditionsListSchema.safeParse(decodedConditions);
154
+ if (!conditionsResult.success) return {
155
+ ok: false,
156
+ reason: "conditions_invalid"
157
+ };
158
+ if (!(await evaluateConditions(conditionsResult.data, claims, options.clock, options.extraPredicateResolvers, subjectClaims)).ok) return {
159
+ ok: false,
160
+ reason: "conditions_not_satisfied"
161
+ };
162
+ }
163
+ const parentBytes = claims.parent;
164
+ const authorisationBytes = claims["authorised-by"];
165
+ if (claims.capability === "manage:grant" && parentBytes !== void 0) return {
166
+ ok: false,
167
+ reason: "authorisation_invalid"
168
+ };
169
+ if (parentBytes !== void 0 && authorisationBytes !== void 0) return {
170
+ ok: false,
171
+ reason: "malformed"
172
+ };
173
+ if (parentBytes !== void 0) {
174
+ let decodedParent;
175
+ try {
176
+ decodedParent = decode(parentBytes, cdeDecodeOptions);
177
+ } catch {
178
+ return {
179
+ ok: false,
180
+ reason: "parent_invalid"
181
+ };
182
+ }
183
+ const parentResult = capabilityTokenSchema.safeParse(decodedParent);
184
+ if (!parentResult.success) return {
185
+ ok: false,
186
+ reason: "parent_invalid"
187
+ };
188
+ const parentVerdict = await verifyTokenChain(parentResult.data, options, void 0, depth + 1, deeperSeen);
189
+ if (!parentVerdict.ok) {
190
+ if (parentVerdict.reason === "chain_too_deep" || parentVerdict.reason === "chain_cycle") return {
191
+ ok: false,
192
+ reason: parentVerdict.reason
193
+ };
194
+ return {
195
+ ok: false,
196
+ reason: "parent_invalid"
197
+ };
198
+ }
199
+ const candidate = {
200
+ capability: claims.capability,
201
+ scope: claims.scope,
202
+ expires: claims.expires,
203
+ ...claims["delegations-remaining"] !== void 0 ? { delegationsRemaining: claims["delegations-remaining"] } : {}
204
+ };
205
+ if (await evaluateNarrowing(parentVerdict.claims, claims.issuer, candidate) !== void 0) return {
206
+ ok: false,
207
+ reason: "delegation_exceeds_parent"
208
+ };
209
+ return {
210
+ ok: true,
211
+ claims,
212
+ rootIssuer: parentVerdict.rootIssuer,
213
+ rootIssuerKey: parentVerdict.rootIssuerKey,
214
+ depth: parentVerdict.depth + 1
215
+ };
216
+ }
217
+ if (authorisationBytes !== void 0) {
218
+ let decodedAuthoriser;
219
+ try {
220
+ decodedAuthoriser = decode(authorisationBytes, cdeDecodeOptions);
221
+ } catch {
222
+ return {
223
+ ok: false,
224
+ reason: "authorisation_invalid"
225
+ };
226
+ }
227
+ const authoriserResult = capabilityTokenSchema.safeParse(decodedAuthoriser);
228
+ if (!authoriserResult.success) return {
229
+ ok: false,
230
+ reason: "authorisation_invalid"
231
+ };
232
+ const authoriserVerdict = await verifyTokenChain(authoriserResult.data, options, claims, depth + 1, deeperSeen);
233
+ if (!authoriserVerdict.ok) {
234
+ if (authoriserVerdict.reason === "chain_too_deep" || authoriserVerdict.reason === "chain_cycle") return {
235
+ ok: false,
236
+ reason: authoriserVerdict.reason
237
+ };
238
+ return {
239
+ ok: false,
240
+ reason: "authorisation_invalid"
241
+ };
242
+ }
243
+ const authoriser = authoriserVerdict.claims;
244
+ const grantsCapability = authoriser["grants-capability"];
245
+ if (!(bytesEqual(authoriser.bearer, claims.issuer) && authoriser.capability === "manage:grant" && (grantsCapability === void 0 || grantsCapability === claims.capability) && scopeNarrows(authoriser.scope, claims.scope) && claims.expires <= authoriser.expires && (authoriser["delegations-remaining"] === void 0 || claims["delegations-remaining"] !== void 0 && claims["delegations-remaining"] < authoriser["delegations-remaining"]))) return {
246
+ ok: false,
247
+ reason: "authorisation_invalid"
248
+ };
249
+ return {
250
+ ok: true,
251
+ claims,
252
+ rootIssuer: authoriserVerdict.rootIssuer,
253
+ rootIssuerKey: authoriserVerdict.rootIssuerKey,
254
+ depth: authoriserVerdict.depth + 1
255
+ };
256
+ }
257
+ return {
258
+ ok: true,
259
+ claims,
260
+ rootIssuer: claims.issuer,
261
+ rootIssuerKey: claims["issuer-key"],
262
+ depth: 0
263
+ };
264
+ }
265
+ /**
266
+ * Verifies one gossiped revocation-entry (management.cddl): a well-formed COSE_Sign1 whose signature verifies against its own embedded issuer-key, where that issuer-key is self-certifying (sha256(issuer-key.public-key) equals the claimed issuer device-id). A verifier that ingests a revocation-announce frame runs each entry through this before recording it in its revocation view; entries failing here are dropped, not stored. The issuer-match against a specific token's own issuer (only a token's own issuer may revoke it) is deliberately NOT checked here -- it happens at lookup time in RevocationCheck, against whichever token is being verified.
267
+ */
268
+ async function verifyRevocationEntry(entry, options) {
269
+ const [protectedHeader, , payload, signature] = entry;
270
+ if (payload === null) return {
271
+ ok: false,
272
+ reason: "malformed"
273
+ };
274
+ let decodedClaims;
275
+ try {
276
+ decodedClaims = decode(payload, cdeDecodeOptions);
277
+ } catch {
278
+ return {
279
+ ok: false,
280
+ reason: "malformed"
281
+ };
282
+ }
283
+ const claimsResult = revocationClaimsSchema.safeParse(decodedClaims);
284
+ if (!claimsResult.success) return {
285
+ ok: false,
286
+ reason: "malformed"
287
+ };
288
+ const claims = claimsResult.data;
289
+ if (!await options.identity.verify(claims["issuer-key"], sig1ToBeSigned(protectedHeader, payload), signature)) return {
290
+ ok: false,
291
+ reason: "bad_signature"
292
+ };
293
+ const derivedIssuerId = await options.identity.deriveDeviceId(claims["issuer-key"]["public-key"]);
294
+ if (!bytesEqual(derivedIssuerId, claims.issuer)) return {
295
+ ok: false,
296
+ reason: "wrong_issuer"
297
+ };
298
+ return {
299
+ ok: true,
300
+ claims
301
+ };
302
+ }
303
+ /** Maps evaluateNarrowing's own NarrowingSystem (the first predicate op that did not hold) to mintCapabilityToken's five distinct refusal reasons -- verifyTokenChain collapses every narrowing failure to a single "delegation_exceeds_parent", but mint has always reported which specific rule was violated, since a caller building the candidate itself benefits from knowing exactly what to fix. */
304
+ const NARROWING_SYSTEM_TO_MINT_REFUSAL = {
305
+ "bearer-is": "parent_bearer_mismatch",
306
+ "expires-at": "expires_exceeds_parent",
307
+ "scope-narrows": "scope_does_not_narrow",
308
+ "capability-is": "capability_mismatch",
309
+ "depth-remaining": "delegation_exceeds_parent"
310
+ };
311
+ /** The narrowing arithmetic tokens.cddl's own delegation obligations require (bearer match, expiry within the parent's, scope narrows, same capability, delegations-remaining strictly less than the parent's) -- shared between mintCapabilityToken (which additionally builds and signs the resulting token) and canGrant (a pure query with no minting side effect at all), so the two can never silently drift into two different ideas of what "narrows" means. Delegates the actual check to evaluateNarrowing (token-predicates.ts), the same generic evaluator verifyTokenChain's own delegation-chain walk uses, so mint and verify share one implementation of each of the five checks rather than two hardcoded copies. Returns the specific refusal reason, or undefined when every rule is satisfied. */
312
+ async function checkNarrowing(parentClaims, granterDeviceId, candidate) {
313
+ const failedSystem = await evaluateNarrowing(parentClaims, granterDeviceId, candidate);
314
+ return failedSystem === void 0 ? void 0 : NARROWING_SYSTEM_TO_MINT_REFUSAL[failedSystem];
315
+ }
316
+ /** The authorised-by link's own arithmetic (wire-mesh#323), the analogue checkNarrowing already is for the parent link: everything tokens.cddl's authorised-by obligations require, checked against a not-yet-minted candidate, shared between mintCapabilityToken and canGrantVia so the two can never drift. The authoriser's conditions are evaluated in subject mode against the candidate, the same evaluation the verifier performs against the minted child. Returns the specific refusal reason, or undefined when the authorisation holds. */
317
+ async function checkAuthorisation(authorisingClaims, minterDeviceId, candidate) {
318
+ if (authorisingClaims.parent !== void 0) return "authorisation_parent_forbidden";
319
+ if (!bytesEqual(authorisingClaims.bearer, minterDeviceId)) return "authorisation_bearer_mismatch";
320
+ const grantsCapability = authorisingClaims["grants-capability"];
321
+ if (authorisingClaims.capability !== "manage:grant" || grantsCapability !== void 0 && grantsCapability !== candidate.capability) return "authorisation_capability_mismatch";
322
+ if (!scopeNarrows(authorisingClaims.scope, candidate.scope)) return "authorisation_scope_does_not_narrow";
323
+ if (candidate.expires > authorisingClaims.expires) return "authorisation_exceeds_authoriser";
324
+ const remaining = authorisingClaims["delegations-remaining"];
325
+ if (remaining !== void 0 && (candidate.delegationsRemaining === void 0 || candidate.delegationsRemaining >= remaining)) return "authorisation_delegation_exceeds";
326
+ if (authorisingClaims.conditions !== void 0) {
327
+ let decodedConditions;
328
+ try {
329
+ decodedConditions = decode(authorisingClaims.conditions, cdeDecodeOptions);
330
+ } catch {
331
+ return "authorisation_malformed";
332
+ }
333
+ const conditionsResult = conditionsListSchema.safeParse(decodedConditions);
334
+ if (!conditionsResult.success) return "authorisation_malformed";
335
+ if (!(await evaluateConditions(conditionsResult.data, authorisingClaims, { now: () => 0 }, {}, candidate)).ok) return "authorisation_conditions_not_satisfied";
336
+ }
337
+ }
338
+ /**
339
+ * A pure query: could deviceId, presenting heldToken as its own delegation authority," successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
340
+ *
341
+ * Deliberately narrower than a full mint attempt in one respect: this checks only the narrowing rules tokens.cddl's own delegation obligations require (bearer match, expiry, scope, capability, delegations-remaining), the same scope mintCapabilityToken itself checks a *parent* against -- it does not verify heldToken's own signature or revocation status, exactly as mintCapabilityToken never re-verifies its own parent's signature either. A caller that also needs heldToken's cryptographic validity confirmed calls verifyCapabilityToken separately.
342
+ *
343
+ * Async as of the predicate-list evaluator (issue #85): checkNarrowing now routes through trilean's own evaluatePredicate, which is asynchronous throughout -- there is no synchronous path through a real evaluator call, so this is a genuine, deliberate breaking change to what was previously a synchronous pure function. No existing consumer of wire-mesh-core calls canGrant (confirmed against agent-comms, the only other repo depending on this package), so there is nothing to migrate.
344
+ */
345
+ async function canGrant(heldToken, deviceId, candidate, now) {
346
+ if (candidate.expires <= now) return false;
347
+ const heldClaims = decodeTokenClaims(heldToken);
348
+ if (heldClaims === void 0) return false;
349
+ return await checkNarrowing(heldClaims, deviceId, candidate) === void 0;
350
+ }
351
+ /** A pure query: could deviceId, presenting heldGrantCapability as its minting authority, mint a token matching candidate right now (wire-mesh#323) -- canGrant's authorised-by analogue, with the same deliberately-narrower-than-verify caveat canGrant's own doc comment states: the held token's signature and revocation status are not checked here; a caller needing those confirmed calls verifyCapabilityToken separately. The candidate carries a bearer because the authoriser's subject-mode conditions read it (the no-self-grant bar). */
352
+ async function canGrantVia(heldGrantCapability, deviceId, candidate, now) {
353
+ if (candidate.expires <= now) return false;
354
+ const heldClaims = decodeTokenClaims(heldGrantCapability);
355
+ if (heldClaims === void 0) return false;
356
+ return await checkAuthorisation(heldClaims, deviceId, candidate) === void 0;
357
+ }
358
+ /**
359
+ * Mints one capability token: builds token-claims from the given fields, signs it as a COSE_Sign1 under `identity`'s own key, with a protected header matching what the frozen conformance vectors actually encode (`{1: alg, 4: issuer device-id}`, not the empty header a token merely needs to verify against itself).
360
+ *
361
+ * When `parent` is given, every one of `tokens.cddl`'s own narrowing obligations is enforced here, at issuance, rather than left for the far end to discover minutes or hours later as a bare `delegation_exceeds_parent` from `verifyCapabilityToken` -- the same "fail loudly, fail early" reasoning that governs every other boundary in this codebase. An issuer minting an invalid delegation is a bug in the caller; this function refuses rather than producing a token indistinguishable from a valid one until someone else verifies it.
362
+ */
363
+ async function mintCapabilityToken(options) {
364
+ if (options.expires <= options.clock.now()) return {
365
+ ok: false,
366
+ reason: "already_expired"
367
+ };
368
+ if (options.parent !== void 0 && options.authorisedBy !== void 0) return {
369
+ ok: false,
370
+ reason: "links_exclusive"
371
+ };
372
+ if (options.capability === "manage:grant" && options.parent !== void 0) return {
373
+ ok: false,
374
+ reason: "authorisation_parent_forbidden"
375
+ };
376
+ let parentBytes;
377
+ if (options.parent !== void 0) {
378
+ const parentClaims = decodeTokenClaims(options.parent);
379
+ if (parentClaims === void 0) return {
380
+ ok: false,
381
+ reason: "parent_malformed"
382
+ };
383
+ const refusal = await checkNarrowing(parentClaims, options.identity.deviceId, {
384
+ capability: options.capability,
385
+ scope: options.scope,
386
+ expires: options.expires,
387
+ ...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
388
+ });
389
+ if (refusal !== void 0) return {
390
+ ok: false,
391
+ reason: refusal
392
+ };
393
+ parentBytes = encodeBuf(options.parent);
394
+ }
395
+ let authorisationBytes;
396
+ if (options.authorisedBy !== void 0) {
397
+ const authorisingClaims = decodeTokenClaims(options.authorisedBy);
398
+ if (authorisingClaims === void 0) return {
399
+ ok: false,
400
+ reason: "authorisation_malformed"
401
+ };
402
+ const refusal = await checkAuthorisation(authorisingClaims, options.identity.deviceId, {
403
+ capability: options.capability,
404
+ scope: options.scope,
405
+ bearer: options.bearer,
406
+ expires: options.expires,
407
+ ...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
408
+ });
409
+ if (refusal !== void 0) return {
410
+ ok: false,
411
+ reason: refusal
412
+ };
413
+ authorisationBytes = encodeBuf(options.authorisedBy);
414
+ }
415
+ const payload = encodeBuf({
416
+ "token-id": options.tokenId,
417
+ issuer: options.identity.deviceId,
418
+ "issuer-key": options.identity.identityKey,
419
+ bearer: options.bearer,
420
+ capability: options.capability,
421
+ scope: options.scope,
422
+ expires: options.expires,
423
+ ...options.notBefore !== void 0 ? { "not-before": options.notBefore } : {},
424
+ ...parentBytes !== void 0 ? { parent: parentBytes } : {},
425
+ ...options.delegationsRemaining !== void 0 ? { "delegations-remaining": options.delegationsRemaining } : {},
426
+ ...options.conditions !== void 0 ? { conditions: encodeBuf(options.conditions) } : {},
427
+ ...authorisationBytes !== void 0 ? { "authorised-by": authorisationBytes } : {},
428
+ ...options.grantsCapability !== void 0 ? { "grants-capability": options.grantsCapability } : {},
429
+ ...options.requestsCapability !== void 0 ? { "requests-capability": options.requestsCapability } : {}
430
+ });
431
+ const protectedHeader = protectedHeaderFor(options.identity);
432
+ return {
433
+ ok: true,
434
+ token: [
435
+ protectedHeader,
436
+ {},
437
+ payload,
438
+ await options.identity.sign(sig1ToBeSigned(protectedHeader, payload))
439
+ ]
440
+ };
441
+ }
442
+ /** Mints one revocation-entry (management.cddl): a COSE_Sign1 over revocation-claims, signed the same way mintCapabilityToken signs a token. No narrowing chain to check -- a revocation entry has no parent and cannot fail to be issuable the way a delegated token can, so this returns the entry directly rather than a verdict. */
443
+ async function mintRevocationEntry(options) {
444
+ const payload = encodeBuf({
445
+ "token-id": options.tokenId,
446
+ issuer: options.identity.deviceId,
447
+ "issuer-key": options.identity.identityKey,
448
+ "revoked-at": options.revokedAt
449
+ });
450
+ const protectedHeader = protectedHeaderFor(options.identity);
451
+ return [
452
+ protectedHeader,
453
+ {},
454
+ payload,
455
+ await options.identity.sign(sig1ToBeSigned(protectedHeader, payload))
456
+ ];
457
+ }
458
+ //#endregion
2
459
  export { MANAGE_GRANT_CAPABILITY, canGrant, canGrantVia, mintCapabilityToken, mintRevocationEntry, sig1ToBeSigned, verifyCapabilityToken, verifyRevocationEntry };
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, nn as TopologyPeers } from "../protocol-BgR1e_NI.cjs";
1
+ import { D as DeviceId, nn as TopologyPeers } from "../protocol-DzmpxxUr.cjs";
2
2
  //#region src/domain/topology-snapshot.d.ts
3
3
  export interface TopologyPeersInputs {
4
4
  /** connection?.peerDeviceId -- the transport-authenticated direct peer, when the adapter gives one. Takes priority over firstAdvertisedPeer below. */
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, nn as TopologyPeers } from "../protocol-BgR1e_NI.mjs";
1
+ import { D as DeviceId, nn as TopologyPeers } from "../protocol-DzmpxxUr.mjs";
2
2
  //#region src/domain/topology-snapshot.d.ts
3
3
  export interface TopologyPeersInputs {
4
4
  /** connection?.peerDeviceId -- the transport-authenticated direct peer, when the adapter gives one. Takes priority over firstAdvertisedPeer below. */
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, V as ManageCommand, et as PeerAdvert, nn as TopologyPeers } from "../protocol-BgR1e_NI.cjs";
1
+ import { D as DeviceId, V as ManageCommand, et as PeerAdvert, nn as TopologyPeers } from "../protocol-DzmpxxUr.cjs";
2
2
  import { DirectoryEntry, IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
3
3
  //#region src/domain/topology.d.ts
4
4
  export declare const TOPOLOGY_GET_CAPABILITY = "topology:get";
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, V as ManageCommand, et as PeerAdvert, nn as TopologyPeers } from "../protocol-BgR1e_NI.mjs";
1
+ import { D as DeviceId, V as ManageCommand, et as PeerAdvert, nn as TopologyPeers } from "../protocol-DzmpxxUr.mjs";
2
2
  import { DirectoryEntry, IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
3
3
  //#region src/domain/topology.d.ts
4
4
  export declare const TOPOLOGY_GET_CAPABILITY = "topology:get";