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.
- package/dist/adapters/byte-stream-connection.d.cts +2 -2
- package/dist/adapters/byte-stream-connection.d.mts +2 -2
- package/dist/adapters/frame-codec.d.cts +1 -1
- package/dist/adapters/frame-codec.d.mts +1 -1
- package/dist/adapters/node-identity.d.cts +2 -2
- package/dist/adapters/node-identity.d.mts +2 -2
- package/dist/adapters/tcp-transport.d.cts +1 -1
- package/dist/adapters/tcp-transport.d.mts +1 -1
- package/dist/adapters/threshold-identity.d.cts +2 -2
- package/dist/adapters/threshold-identity.d.mts +2 -2
- package/dist/adapters/threshold-wasm.d.cts +1 -1
- package/dist/adapters/threshold-wasm.d.mts +1 -1
- package/dist/adapters/tls-transport.d.cts +1 -1
- package/dist/adapters/tls-transport.d.mts +1 -1
- package/dist/domain/advert-extension-policy.d.cts +1 -1
- package/dist/domain/advert-extension-policy.d.mts +1 -1
- package/dist/domain/bulk.d.cts +2 -2
- package/dist/domain/bulk.d.mts +2 -2
- package/dist/domain/capability-grant.cjs +4 -4
- package/dist/domain/capability-grant.d.cts +3 -3
- package/dist/domain/capability-grant.d.mts +3 -3
- package/dist/domain/capability-grant.mjs +2 -2
- package/dist/domain/capability-request.cjs +18 -6
- package/dist/domain/capability-request.d.cts +10 -3
- package/dist/domain/capability-request.d.mts +10 -3
- package/dist/domain/capability-request.mjs +15 -4
- package/dist/domain/coordinator-election.d.cts +1 -1
- package/dist/domain/coordinator-election.d.mts +1 -1
- package/dist/domain/data-sync.d.cts +2 -2
- package/dist/domain/data-sync.d.mts +2 -2
- package/dist/domain/device-id.d.cts +1 -1
- package/dist/domain/device-id.d.mts +1 -1
- package/dist/domain/direct-manage-request.d.cts +2 -2
- package/dist/domain/direct-manage-request.d.mts +2 -2
- package/dist/domain/gossip-expansion.d.cts +1 -1
- package/dist/domain/gossip-expansion.d.mts +1 -1
- package/dist/domain/grant-candidates.cjs +2 -2
- package/dist/domain/grant-candidates.d.cts +1 -1
- package/dist/domain/grant-candidates.d.mts +1 -1
- package/dist/domain/grant-candidates.mjs +1 -1
- package/dist/domain/handshake.d.cts +1 -1
- package/dist/domain/handshake.d.mts +1 -1
- package/dist/domain/hub-mailbox.d.cts +1 -1
- package/dist/domain/hub-mailbox.d.mts +1 -1
- package/dist/domain/mesh-session.cjs +2 -2
- package/dist/domain/mesh-session.d.cts +3 -3
- package/dist/domain/mesh-session.d.mts +3 -3
- package/dist/domain/mesh-session.mjs +1 -1
- package/dist/domain/notice-board.d.cts +3 -3
- package/dist/domain/notice-board.d.mts +3 -3
- package/dist/domain/path-trace.d.cts +1 -1
- package/dist/domain/path-trace.d.mts +1 -1
- package/dist/domain/peer-advert.cjs +2 -2
- package/dist/domain/peer-advert.d.cts +2 -2
- package/dist/domain/peer-advert.d.mts +2 -2
- package/dist/domain/peer-advert.mjs +1 -1
- package/dist/domain/relay-advert.d.cts +1 -1
- package/dist/domain/relay-advert.d.mts +1 -1
- package/dist/domain/relay-hub.cjs +72 -15
- package/dist/domain/relay-hub.d.cts +6 -2
- package/dist/domain/relay-hub.d.mts +6 -2
- package/dist/domain/relay-hub.mjs +72 -15
- package/dist/domain/relay-use-gate.d.cts +2 -2
- package/dist/domain/relay-use-gate.d.mts +2 -2
- package/dist/domain/revocation-view.cjs +2 -2
- package/dist/domain/revocation-view.d.cts +2 -2
- package/dist/domain/revocation-view.d.mts +2 -2
- package/dist/domain/revocation-view.mjs +1 -1
- package/dist/domain/room-rekey.d.cts +3 -3
- package/dist/domain/room-rekey.d.mts +3 -3
- package/dist/domain/room-token-verification.cjs +2 -2
- package/dist/domain/room-token-verification.d.cts +1 -1
- package/dist/domain/room-token-verification.d.mts +1 -1
- package/dist/domain/room-token-verification.mjs +1 -1
- package/dist/domain/room.d.cts +4 -4
- package/dist/domain/room.d.mts +4 -4
- package/dist/domain/shard-manifest.d.cts +1 -1
- package/dist/domain/shard-manifest.d.mts +1 -1
- package/dist/domain/threshold-subject.cjs +2 -2
- package/dist/domain/threshold-subject.mjs +1 -1
- package/dist/domain/token-predicates.cjs +184 -0
- package/dist/domain/token-predicates.d.cts +2 -0
- package/dist/domain/token-predicates.d.mts +2 -0
- package/dist/domain/token-predicates.mjs +173 -0
- package/dist/{token-scope-CxHXTT3u.cjs → domain/token-scope.cjs} +3 -12
- package/dist/domain/token-scope.d.cts +11 -0
- package/dist/domain/token-scope.d.mts +11 -0
- package/dist/{token-scope-Z4bmci4M.mjs → domain/token-scope.mjs} +1 -1
- package/dist/domain/tokens.cjs +466 -9
- package/dist/domain/tokens.d.cts +131 -2
- package/dist/domain/tokens.d.mts +131 -2
- package/dist/domain/tokens.mjs +458 -1
- package/dist/domain/topology-snapshot.d.cts +1 -1
- package/dist/domain/topology-snapshot.d.mts +1 -1
- package/dist/domain/topology.d.cts +1 -1
- package/dist/domain/topology.d.mts +1 -1
- package/dist/domain/webrtc-signaling.cjs +2 -2
- package/dist/domain/webrtc-signaling.d.cts +2 -2
- package/dist/domain/webrtc-signaling.d.mts +2 -2
- package/dist/domain/webrtc-signaling.mjs +1 -1
- package/dist/generated/protocol.cjs +4 -2
- package/dist/generated/protocol.d.cts +1 -1
- package/dist/generated/protocol.d.mts +1 -1
- package/dist/generated/protocol.mjs +4 -2
- package/dist/{identity-DJbr96J3.d.cts → identity-B6nhlSOT.d.cts} +1 -1
- package/dist/{identity-ViYdZrIU.d.mts → identity-EV-HHv_D.d.mts} +1 -1
- package/dist/ports/identity.d.cts +1 -1
- package/dist/ports/identity.d.mts +1 -1
- package/dist/ports/transport.d.cts +1 -1
- package/dist/ports/transport.d.mts +1 -1
- package/dist/{protocol-BgR1e_NI.d.cts → protocol-DzmpxxUr.d.cts} +6 -0
- package/dist/{protocol-BgR1e_NI.d.mts → protocol-DzmpxxUr.d.mts} +6 -0
- package/dist/{room-token-verification-7zD4ib-A.d.mts → room-token-verification-Dh7SGJfK.d.cts} +2 -2
- package/dist/{room-token-verification-hFtBDZPO.d.cts → room-token-verification-Hb1LkpFh.d.mts} +2 -2
- package/dist/token-predicates-D4NNPLnp.d.cts +72 -0
- package/dist/token-predicates-DqpG8VZZ.d.mts +72 -0
- package/dist/{transport-Dv_LTCsF.d.mts → transport-bD3p_TO_.d.mts} +1 -1
- package/dist/{transport-DP3hdehk.d.cts → transport-gDbSHO-r.d.cts} +1 -1
- package/package.json +9 -1
- package/dist/tokens-BMWCnQEF.d.cts +0 -158
- package/dist/tokens-D8JlTJNA.mjs +0 -617
- package/dist/tokens-DRkm_QOR.d.mts +0 -158
- package/dist/tokens-tPJQ7Vzt.cjs +0 -664
package/dist/domain/room.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { At as RoomPath, Ot as RoomNotice, kt as RoomNoticeClaims, p as CapabilityToken, q as MessageRef } from "../protocol-
|
|
2
|
-
import { t as IdentityPort } from "../identity-
|
|
1
|
+
import { At as RoomPath, Ot as RoomNotice, kt as RoomNoticeClaims, p as CapabilityToken, q as MessageRef } from "../protocol-DzmpxxUr.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-EV-HHv_D.mjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { r as RoomTokenVerdictReason } from "../room-token-verification-
|
|
4
|
+
import { RevocationCheck } from "./tokens.mjs";
|
|
5
|
+
import { r as RoomTokenVerdictReason } from "../room-token-verification-Hb1LkpFh.mjs";
|
|
6
6
|
//#region src/domain/room.d.ts
|
|
7
7
|
export type RoomNoticeVerdictReason = "malformed" | "wrong_room" | "bad_signature" | "wrong_poster" | "content_expired" | "key_epoch_mismatch" | RoomTokenVerdictReason;
|
|
8
8
|
export type RoomNoticeVerdict = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId } from "../protocol-
|
|
1
|
+
import { D as DeviceId } from "../protocol-DzmpxxUr.cjs";
|
|
2
2
|
import { PresentedShard, ShardConfig } from "./erasure-coding.cjs";
|
|
3
3
|
//#region src/domain/shard-manifest.d.ts
|
|
4
4
|
/** The content-type a core/data entry carrying a shard manifest declares. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId } from "../protocol-
|
|
1
|
+
import { D as DeviceId } from "../protocol-DzmpxxUr.mjs";
|
|
2
2
|
import { PresentedShard, ShardConfig } from "./erasure-coding.mjs";
|
|
3
3
|
//#region src/domain/shard-manifest.d.ts
|
|
4
4
|
/** The content-type a core/data entry carrying a shard manifest declares. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_domain_tokens = require("./tokens.cjs");
|
|
3
3
|
//#region src/domain/threshold-subject.ts
|
|
4
4
|
/**
|
|
5
5
|
* `threshold-subject` -- what a signing session actually asks a group to sign, and the single most important design decision in the whole protocol: it carries the FULL decoded content, never a bare hash. A participant that commits to an opaque digest is a blind signer, and the entire security value of T-of-N is that each participant independently reviews and authorises the content -- returning a round-1 commitment IS that act of authorisation. This module builds exactly the bytes a participant signs its round-1 commitment over: the RFC 9052 §4.4 `Sig_structure`, reusing `tokens.ts`'s own `sig1ToBeSigned` rather than a second hand-rolled construction, and NEVER accepting a pre-assembled to-be-signed blob from a coordinator. Mirrors `wire_mesh_threshold::subject` on the Rust side exactly.
|
|
@@ -27,7 +27,7 @@ function refuseUnrecognisedKind(subject, known = KNOWN_KINDS) {
|
|
|
27
27
|
* Reconstructs the exact bytes this participant signs its round-1 commitment (and, transitively, its round-2 share) over -- the Sig_structure built from THIS participant's own decoding of `protected`/`payload`, never a coordinator-supplied to-be-signed blob.
|
|
28
28
|
*/
|
|
29
29
|
function toBeSigned(subject) {
|
|
30
|
-
return Uint8Array.from(
|
|
30
|
+
return Uint8Array.from(require_domain_tokens.sig1ToBeSigned(subject.protected, subject.payload));
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
33
|
exports.KNOWN_KINDS = KNOWN_KINDS;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sig1ToBeSigned } from "./tokens.mjs";
|
|
2
2
|
//#region src/domain/threshold-subject.ts
|
|
3
3
|
/**
|
|
4
4
|
* `threshold-subject` -- what a signing session actually asks a group to sign, and the single most important design decision in the whole protocol: it carries the FULL decoded content, never a bare hash. A participant that commits to an opaque digest is a blind signer, and the entire security value of T-of-N is that each participant independently reviews and authorises the content -- returning a round-1 commitment IS that act of authorisation. This module builds exactly the bytes a participant signs its round-1 commitment over: the RFC 9052 §4.4 `Sig_structure`, reusing `tokens.ts`'s own `sig1ToBeSigned` rather than a second hand-rolled construction, and NEVER accepting a pre-assembled to-be-signed blob from a coordinator. Mirrors `wire_mesh_threshold::subject` on the Rust side exactly.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_domain_device_id = require("./device-id.cjs");
|
|
3
|
+
const require_domain_token_scope = require("./token-scope.cjs");
|
|
4
|
+
let zod = require("zod");
|
|
5
|
+
let trilean = require("trilean");
|
|
6
|
+
//#region src/domain/token-predicates.ts
|
|
7
|
+
/** The wire shape of `token-claims.conditions` once CBOR-decoded: trilean's own PredicateNodeSchema is the single source of truth for what a condition entry may contain, re-validated here rather than trusted from a CDDL-generated shadow schema (see tokens.cddl's own comment on why `conditions` is an opaque bstr, not a native CDDL type) -- a token from an untrusted peer must pass trilean's real schema before any of its conditions are evaluated. Explicitly annotated: trilean's PredicateNodeSchema is a deeply recursive z.lazy() type whose inferred shape is too large for tsdown's declaration-file generator to serialise (TS7056) without this. */
|
|
8
|
+
const conditionsListSchema = zod.z.array(trilean.PredicateNodeSchema);
|
|
9
|
+
/**
|
|
10
|
+
* tokens.cddl's mandated core predicate-op vocabulary (issue #85): the five narrowing checks a verifier previously enforced as five hardcoded `if`s, now expressed as `delegate` systems every conforming verifier registers a handler for. Order matches the historical check order in verifyTokenChain/checkNarrowing, preserved so a caller mapping a failing system to its own reason vocabulary (MintRefusalReason's five distinct values; verifyTokenChain's single collapsed "delegation_exceeds_parent") sees the same check run first that always ran first.
|
|
11
|
+
*/
|
|
12
|
+
const BEARER_IS = "bearer-is";
|
|
13
|
+
const EXPIRES_AT = "expires-at";
|
|
14
|
+
const SCOPE_NARROWS = "scope-narrows";
|
|
15
|
+
const CAPABILITY_IS = "capability-is";
|
|
16
|
+
const DEPTH_REMAINING = "depth-remaining";
|
|
17
|
+
const NARROWING_SYSTEMS = [
|
|
18
|
+
BEARER_IS,
|
|
19
|
+
EXPIRES_AT,
|
|
20
|
+
SCOPE_NARROWS,
|
|
21
|
+
CAPABILITY_IS,
|
|
22
|
+
DEPTH_REMAINING
|
|
23
|
+
];
|
|
24
|
+
function isNarrowingSystem(system) {
|
|
25
|
+
return system === "bearer-is" || system === "expires-at" || system === "scope-narrows" || system === "capability-is" || system === "depth-remaining";
|
|
26
|
+
}
|
|
27
|
+
/** The two subject-mode delegate systems (wire-mesh#323), registered only when a ConditionsContext carries a subject, and indeterminate (fail-closed) whenever one does not. */
|
|
28
|
+
const GRANTEE_IS = "grantee-is";
|
|
29
|
+
const GRANTED_CAPABILITY_IS = "granted-capability-is";
|
|
30
|
+
function isSubjectSystem(system) {
|
|
31
|
+
return system === "grantee-is" || system === "granted-capability-is";
|
|
32
|
+
}
|
|
33
|
+
const subjectHandlers = {
|
|
34
|
+
[GRANTEE_IS]: (payload, context) => {
|
|
35
|
+
if (context.subject === void 0) return { found: false };
|
|
36
|
+
if (typeof payload !== "string") return { found: false };
|
|
37
|
+
return booleanFound(require_domain_device_id.deviceIdToHex(context.subject.bearer) === payload);
|
|
38
|
+
},
|
|
39
|
+
[GRANTED_CAPABILITY_IS]: (payload, context) => {
|
|
40
|
+
if (context.subject === void 0) return { found: false };
|
|
41
|
+
if (typeof payload !== "string") return { found: false };
|
|
42
|
+
return booleanFound(context.subject.capability === payload);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** The named no-self-grant bar (wire-mesh#323): one conditions entry an issuer puts on a grant-capability so its bearer may not mint a `capability` token naming itself as bearer. `not(and(...))` under strict Kleene: a child granted to anyone else passes, a child granted to the bearer fails, and any unresolvable leg stays indeterminate and refuses. */
|
|
46
|
+
function noSelfGrantBar(capability, bearer) {
|
|
47
|
+
return {
|
|
48
|
+
kind: "not",
|
|
49
|
+
operand: {
|
|
50
|
+
kind: "and",
|
|
51
|
+
left: delegateIsTrue(GRANTED_CAPABILITY_IS, capability),
|
|
52
|
+
right: delegateIsTrue(GRANTEE_IS, require_domain_device_id.deviceIdToHex(bearer))
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function booleanFound(value) {
|
|
57
|
+
return {
|
|
58
|
+
found: true,
|
|
59
|
+
value: {
|
|
60
|
+
kind: "boolean",
|
|
61
|
+
value
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** The five mandatory narrowing ops, each reading only from TokenPredicateContext -- never the delegate node's own payload, since every fact each op needs (which parent, which candidate) already travels via the context the caller constructs per evaluation, not via wire-encoded payload data. A context with no parentClaims (narrowing is being checked with nothing to narrow against) resolves every op to `false` rather than throwing -- fail-closed, matching how an indeterminate result already refuses a token. */
|
|
66
|
+
const narrowingHandlers = {
|
|
67
|
+
[BEARER_IS]: (_payload, context) => {
|
|
68
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
69
|
+
return booleanFound(require_domain_token_scope.bytesEqual(context.parentClaims.bearer, context.childIssuer));
|
|
70
|
+
},
|
|
71
|
+
[EXPIRES_AT]: (_payload, context) => {
|
|
72
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
73
|
+
return booleanFound(context.candidate.expires <= context.parentClaims.expires);
|
|
74
|
+
},
|
|
75
|
+
[SCOPE_NARROWS]: (_payload, context) => {
|
|
76
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
77
|
+
return booleanFound(require_domain_token_scope.scopeNarrows(context.parentClaims.scope, context.candidate.scope));
|
|
78
|
+
},
|
|
79
|
+
[CAPABILITY_IS]: (_payload, context) => {
|
|
80
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
81
|
+
return booleanFound(context.parentClaims.capability === context.candidate.capability);
|
|
82
|
+
},
|
|
83
|
+
[DEPTH_REMAINING]: (_payload, context) => {
|
|
84
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
85
|
+
const parentRemaining = context.parentClaims["delegations-remaining"];
|
|
86
|
+
if (parentRemaining === void 0) return booleanFound(true);
|
|
87
|
+
return booleanFound(context.candidate.delegationsRemaining !== void 0 && context.candidate.delegationsRemaining < parentRemaining);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload}, right:{kind:"booleanLiteral", value:true}}` -- delegate is an ExpressionNode, not itself a PredicateNode (trilean's PredicateNodeSchema has no `delegate` member), so every predicate op is expressed this way: resolve the delegate to a boolean ComputedValue, then compare it against the literal `true`. The narrowing checks pass no payload (every fact they need arrives via context); the subject-mode systems take their comparison value as the payload. */
|
|
91
|
+
function delegateIsTrue(system, payload = null) {
|
|
92
|
+
return {
|
|
93
|
+
kind: "compare",
|
|
94
|
+
op: "eq",
|
|
95
|
+
left: {
|
|
96
|
+
kind: "delegate",
|
|
97
|
+
system,
|
|
98
|
+
payload
|
|
99
|
+
},
|
|
100
|
+
right: {
|
|
101
|
+
kind: "booleanLiteral",
|
|
102
|
+
value: true
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** trilean's three required resolvers are never exercised by any node this module constructs (narrowing/conditions nodes are built entirely from `compare`+`delegate`+`booleanLiteral`, never `reference`/`lookup`/`fold`/`some`/`every`) -- these stubs exist only because `Resolvers` requires them structurally. */
|
|
107
|
+
const unusedResolveValue = async () => Promise.resolve({ found: false });
|
|
108
|
+
const unusedResolveLookup = async () => Promise.resolve({ found: false });
|
|
109
|
+
const unusedResolveCollection = async () => Promise.resolve([]);
|
|
110
|
+
/**
|
|
111
|
+
* Evaluates the five mandatory narrowing predicates against parentClaims/candidate, in their historical check order, short-circuiting on and returning the first that does not hold. Returns undefined when every check holds. This is the single mechanism both mintCapabilityToken's own pre-mint check and verifyTokenChain's own delegation-chain walk call -- the two can never silently drift into different ideas of what "narrows" means, because there is now exactly one implementation of each check, not two hardcoded copies.
|
|
112
|
+
*/
|
|
113
|
+
async function evaluateNarrowing(parentClaims, childIssuer, candidate) {
|
|
114
|
+
const context = {
|
|
115
|
+
childIssuer,
|
|
116
|
+
candidate,
|
|
117
|
+
parentClaims
|
|
118
|
+
};
|
|
119
|
+
const resolvers = {
|
|
120
|
+
resolveValue: unusedResolveValue,
|
|
121
|
+
resolveLookup: unusedResolveLookup,
|
|
122
|
+
resolveCollection: unusedResolveCollection,
|
|
123
|
+
resolveDelegate: async (system, payload) => {
|
|
124
|
+
if (!isNarrowingSystem(system)) return { found: false };
|
|
125
|
+
return narrowingHandlers[system](payload, context);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
for (const system of NARROWING_SYSTEMS) {
|
|
129
|
+
const result = await (0, trilean.evaluatePredicate)(delegateIsTrue(system), context, resolvers);
|
|
130
|
+
if (result.status !== "definite" || !result.value) return system;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Evaluates token-claims' own `conditions` field (issue #85): additional, issuer-chosen predicates strictly additive to the five mandatory narrowing checks above, which are computed structurally (from a delegated token's own claims and its parent's) and never read from this field -- the five built-ins are inherently about a parent/child relationship, so they are not meaningfully reusable inside a bare, parent-less conditions evaluation, unlike a genuine domain-specific condition (a message TTL, #84's future revoke-authorization check) which reasons about the presented token's own claims directly. `extraHandlers` is the registration point for those: none exist yet, so a `conditions` entry naming any system with no registered handler resolves `{found: false}` -- indeterminate, fail-closed. Absent `nodes` (an empty array, the caller's own signal for "no conditions field present") is trivially `{ok: true}` -- no extra restrictions, exactly today's behaviour. Every entry MUST evaluate to a definite `true`; the loop short-circuits on the first indeterminate or false result.
|
|
135
|
+
*/
|
|
136
|
+
async function evaluateConditions(nodes, claims, clock, extraHandlers = {}, subject) {
|
|
137
|
+
const context = subject === void 0 ? {
|
|
138
|
+
clock,
|
|
139
|
+
claims
|
|
140
|
+
} : {
|
|
141
|
+
clock,
|
|
142
|
+
claims,
|
|
143
|
+
subject
|
|
144
|
+
};
|
|
145
|
+
const resolvers = {
|
|
146
|
+
resolveValue: unusedResolveValue,
|
|
147
|
+
resolveLookup: unusedResolveLookup,
|
|
148
|
+
resolveCollection: unusedResolveCollection,
|
|
149
|
+
resolveDelegate: async (system, payload) => {
|
|
150
|
+
if (subject !== void 0 && isSubjectSystem(system)) try {
|
|
151
|
+
return await subjectHandlers[system](payload, context);
|
|
152
|
+
} catch {
|
|
153
|
+
return { found: false };
|
|
154
|
+
}
|
|
155
|
+
const handler = Object.hasOwn(extraHandlers, system) ? extraHandlers[system] : void 0;
|
|
156
|
+
if (handler === void 0) return { found: false };
|
|
157
|
+
try {
|
|
158
|
+
return await handler(payload, context);
|
|
159
|
+
} catch {
|
|
160
|
+
return { found: false };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
for (const node of nodes) {
|
|
165
|
+
const result = await (0, trilean.evaluatePredicate)(node, context, resolvers);
|
|
166
|
+
if (result.status !== "definite" || !result.value) return {
|
|
167
|
+
ok: false,
|
|
168
|
+
reason: "not_satisfied"
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { ok: true };
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
exports.BEARER_IS = BEARER_IS;
|
|
175
|
+
exports.CAPABILITY_IS = CAPABILITY_IS;
|
|
176
|
+
exports.DEPTH_REMAINING = DEPTH_REMAINING;
|
|
177
|
+
exports.EXPIRES_AT = EXPIRES_AT;
|
|
178
|
+
exports.GRANTED_CAPABILITY_IS = GRANTED_CAPABILITY_IS;
|
|
179
|
+
exports.GRANTEE_IS = GRANTEE_IS;
|
|
180
|
+
exports.SCOPE_NARROWS = SCOPE_NARROWS;
|
|
181
|
+
exports.conditionsListSchema = conditionsListSchema;
|
|
182
|
+
exports.evaluateConditions = evaluateConditions;
|
|
183
|
+
exports.evaluateNarrowing = evaluateNarrowing;
|
|
184
|
+
exports.noSelfGrantBar = noSelfGrantBar;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as evaluateConditions, a as DEPTH_REMAINING, c as GRANTEE_IS, d as NarrowingSystem, f as SCOPE_NARROWS, g as conditionsListSchema, h as TokenPredicateContext, i as ConditionsVerdict, l as GrantSubject, m as TokenDelegateHandler, n as CAPABILITY_IS, o as EXPIRES_AT, p as SubjectSystem, r as ConditionsContext, s as GRANTED_CAPABILITY_IS, t as BEARER_IS, u as NarrowingCandidate, v as evaluateNarrowing, y as noSelfGrantBar } from "../token-predicates-D4NNPLnp.cjs";
|
|
2
|
+
export { BEARER_IS, CAPABILITY_IS, ConditionsContext, ConditionsVerdict, DEPTH_REMAINING, EXPIRES_AT, GRANTED_CAPABILITY_IS, GRANTEE_IS, GrantSubject, NarrowingCandidate, NarrowingSystem, SCOPE_NARROWS, SubjectSystem, TokenDelegateHandler, TokenPredicateContext, conditionsListSchema, evaluateConditions, evaluateNarrowing, noSelfGrantBar };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as evaluateConditions, a as DEPTH_REMAINING, c as GRANTEE_IS, d as NarrowingSystem, f as SCOPE_NARROWS, g as conditionsListSchema, h as TokenPredicateContext, i as ConditionsVerdict, l as GrantSubject, m as TokenDelegateHandler, n as CAPABILITY_IS, o as EXPIRES_AT, p as SubjectSystem, r as ConditionsContext, s as GRANTED_CAPABILITY_IS, t as BEARER_IS, u as NarrowingCandidate, v as evaluateNarrowing, y as noSelfGrantBar } from "../token-predicates-DqpG8VZZ.mjs";
|
|
2
|
+
export { BEARER_IS, CAPABILITY_IS, ConditionsContext, ConditionsVerdict, DEPTH_REMAINING, EXPIRES_AT, GRANTED_CAPABILITY_IS, GRANTEE_IS, GrantSubject, NarrowingCandidate, NarrowingSystem, SCOPE_NARROWS, SubjectSystem, TokenDelegateHandler, TokenPredicateContext, conditionsListSchema, evaluateConditions, evaluateNarrowing, noSelfGrantBar };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
|
+
import { bytesEqual, scopeNarrows } from "./token-scope.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { PredicateNodeSchema, evaluatePredicate } from "trilean";
|
|
5
|
+
//#region src/domain/token-predicates.ts
|
|
6
|
+
/** The wire shape of `token-claims.conditions` once CBOR-decoded: trilean's own PredicateNodeSchema is the single source of truth for what a condition entry may contain, re-validated here rather than trusted from a CDDL-generated shadow schema (see tokens.cddl's own comment on why `conditions` is an opaque bstr, not a native CDDL type) -- a token from an untrusted peer must pass trilean's real schema before any of its conditions are evaluated. Explicitly annotated: trilean's PredicateNodeSchema is a deeply recursive z.lazy() type whose inferred shape is too large for tsdown's declaration-file generator to serialise (TS7056) without this. */
|
|
7
|
+
const conditionsListSchema = z.array(PredicateNodeSchema);
|
|
8
|
+
/**
|
|
9
|
+
* tokens.cddl's mandated core predicate-op vocabulary (issue #85): the five narrowing checks a verifier previously enforced as five hardcoded `if`s, now expressed as `delegate` systems every conforming verifier registers a handler for. Order matches the historical check order in verifyTokenChain/checkNarrowing, preserved so a caller mapping a failing system to its own reason vocabulary (MintRefusalReason's five distinct values; verifyTokenChain's single collapsed "delegation_exceeds_parent") sees the same check run first that always ran first.
|
|
10
|
+
*/
|
|
11
|
+
const BEARER_IS = "bearer-is";
|
|
12
|
+
const EXPIRES_AT = "expires-at";
|
|
13
|
+
const SCOPE_NARROWS = "scope-narrows";
|
|
14
|
+
const CAPABILITY_IS = "capability-is";
|
|
15
|
+
const DEPTH_REMAINING = "depth-remaining";
|
|
16
|
+
const NARROWING_SYSTEMS = [
|
|
17
|
+
BEARER_IS,
|
|
18
|
+
EXPIRES_AT,
|
|
19
|
+
SCOPE_NARROWS,
|
|
20
|
+
CAPABILITY_IS,
|
|
21
|
+
DEPTH_REMAINING
|
|
22
|
+
];
|
|
23
|
+
function isNarrowingSystem(system) {
|
|
24
|
+
return system === "bearer-is" || system === "expires-at" || system === "scope-narrows" || system === "capability-is" || system === "depth-remaining";
|
|
25
|
+
}
|
|
26
|
+
/** The two subject-mode delegate systems (wire-mesh#323), registered only when a ConditionsContext carries a subject, and indeterminate (fail-closed) whenever one does not. */
|
|
27
|
+
const GRANTEE_IS = "grantee-is";
|
|
28
|
+
const GRANTED_CAPABILITY_IS = "granted-capability-is";
|
|
29
|
+
function isSubjectSystem(system) {
|
|
30
|
+
return system === "grantee-is" || system === "granted-capability-is";
|
|
31
|
+
}
|
|
32
|
+
const subjectHandlers = {
|
|
33
|
+
[GRANTEE_IS]: (payload, context) => {
|
|
34
|
+
if (context.subject === void 0) return { found: false };
|
|
35
|
+
if (typeof payload !== "string") return { found: false };
|
|
36
|
+
return booleanFound(deviceIdToHex(context.subject.bearer) === payload);
|
|
37
|
+
},
|
|
38
|
+
[GRANTED_CAPABILITY_IS]: (payload, context) => {
|
|
39
|
+
if (context.subject === void 0) return { found: false };
|
|
40
|
+
if (typeof payload !== "string") return { found: false };
|
|
41
|
+
return booleanFound(context.subject.capability === payload);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** The named no-self-grant bar (wire-mesh#323): one conditions entry an issuer puts on a grant-capability so its bearer may not mint a `capability` token naming itself as bearer. `not(and(...))` under strict Kleene: a child granted to anyone else passes, a child granted to the bearer fails, and any unresolvable leg stays indeterminate and refuses. */
|
|
45
|
+
function noSelfGrantBar(capability, bearer) {
|
|
46
|
+
return {
|
|
47
|
+
kind: "not",
|
|
48
|
+
operand: {
|
|
49
|
+
kind: "and",
|
|
50
|
+
left: delegateIsTrue(GRANTED_CAPABILITY_IS, capability),
|
|
51
|
+
right: delegateIsTrue(GRANTEE_IS, deviceIdToHex(bearer))
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function booleanFound(value) {
|
|
56
|
+
return {
|
|
57
|
+
found: true,
|
|
58
|
+
value: {
|
|
59
|
+
kind: "boolean",
|
|
60
|
+
value
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The five mandatory narrowing ops, each reading only from TokenPredicateContext -- never the delegate node's own payload, since every fact each op needs (which parent, which candidate) already travels via the context the caller constructs per evaluation, not via wire-encoded payload data. A context with no parentClaims (narrowing is being checked with nothing to narrow against) resolves every op to `false` rather than throwing -- fail-closed, matching how an indeterminate result already refuses a token. */
|
|
65
|
+
const narrowingHandlers = {
|
|
66
|
+
[BEARER_IS]: (_payload, context) => {
|
|
67
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
68
|
+
return booleanFound(bytesEqual(context.parentClaims.bearer, context.childIssuer));
|
|
69
|
+
},
|
|
70
|
+
[EXPIRES_AT]: (_payload, context) => {
|
|
71
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
72
|
+
return booleanFound(context.candidate.expires <= context.parentClaims.expires);
|
|
73
|
+
},
|
|
74
|
+
[SCOPE_NARROWS]: (_payload, context) => {
|
|
75
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
76
|
+
return booleanFound(scopeNarrows(context.parentClaims.scope, context.candidate.scope));
|
|
77
|
+
},
|
|
78
|
+
[CAPABILITY_IS]: (_payload, context) => {
|
|
79
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
80
|
+
return booleanFound(context.parentClaims.capability === context.candidate.capability);
|
|
81
|
+
},
|
|
82
|
+
[DEPTH_REMAINING]: (_payload, context) => {
|
|
83
|
+
if (context.parentClaims === void 0) return booleanFound(false);
|
|
84
|
+
const parentRemaining = context.parentClaims["delegations-remaining"];
|
|
85
|
+
if (parentRemaining === void 0) return booleanFound(true);
|
|
86
|
+
return booleanFound(context.candidate.delegationsRemaining !== void 0 && context.candidate.delegationsRemaining < parentRemaining);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload}, right:{kind:"booleanLiteral", value:true}}` -- delegate is an ExpressionNode, not itself a PredicateNode (trilean's PredicateNodeSchema has no `delegate` member), so every predicate op is expressed this way: resolve the delegate to a boolean ComputedValue, then compare it against the literal `true`. The narrowing checks pass no payload (every fact they need arrives via context); the subject-mode systems take their comparison value as the payload. */
|
|
90
|
+
function delegateIsTrue(system, payload = null) {
|
|
91
|
+
return {
|
|
92
|
+
kind: "compare",
|
|
93
|
+
op: "eq",
|
|
94
|
+
left: {
|
|
95
|
+
kind: "delegate",
|
|
96
|
+
system,
|
|
97
|
+
payload
|
|
98
|
+
},
|
|
99
|
+
right: {
|
|
100
|
+
kind: "booleanLiteral",
|
|
101
|
+
value: true
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** trilean's three required resolvers are never exercised by any node this module constructs (narrowing/conditions nodes are built entirely from `compare`+`delegate`+`booleanLiteral`, never `reference`/`lookup`/`fold`/`some`/`every`) -- these stubs exist only because `Resolvers` requires them structurally. */
|
|
106
|
+
const unusedResolveValue = async () => Promise.resolve({ found: false });
|
|
107
|
+
const unusedResolveLookup = async () => Promise.resolve({ found: false });
|
|
108
|
+
const unusedResolveCollection = async () => Promise.resolve([]);
|
|
109
|
+
/**
|
|
110
|
+
* Evaluates the five mandatory narrowing predicates against parentClaims/candidate, in their historical check order, short-circuiting on and returning the first that does not hold. Returns undefined when every check holds. This is the single mechanism both mintCapabilityToken's own pre-mint check and verifyTokenChain's own delegation-chain walk call -- the two can never silently drift into different ideas of what "narrows" means, because there is now exactly one implementation of each check, not two hardcoded copies.
|
|
111
|
+
*/
|
|
112
|
+
async function evaluateNarrowing(parentClaims, childIssuer, candidate) {
|
|
113
|
+
const context = {
|
|
114
|
+
childIssuer,
|
|
115
|
+
candidate,
|
|
116
|
+
parentClaims
|
|
117
|
+
};
|
|
118
|
+
const resolvers = {
|
|
119
|
+
resolveValue: unusedResolveValue,
|
|
120
|
+
resolveLookup: unusedResolveLookup,
|
|
121
|
+
resolveCollection: unusedResolveCollection,
|
|
122
|
+
resolveDelegate: async (system, payload) => {
|
|
123
|
+
if (!isNarrowingSystem(system)) return { found: false };
|
|
124
|
+
return narrowingHandlers[system](payload, context);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
for (const system of NARROWING_SYSTEMS) {
|
|
128
|
+
const result = await evaluatePredicate(delegateIsTrue(system), context, resolvers);
|
|
129
|
+
if (result.status !== "definite" || !result.value) return system;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Evaluates token-claims' own `conditions` field (issue #85): additional, issuer-chosen predicates strictly additive to the five mandatory narrowing checks above, which are computed structurally (from a delegated token's own claims and its parent's) and never read from this field -- the five built-ins are inherently about a parent/child relationship, so they are not meaningfully reusable inside a bare, parent-less conditions evaluation, unlike a genuine domain-specific condition (a message TTL, #84's future revoke-authorization check) which reasons about the presented token's own claims directly. `extraHandlers` is the registration point for those: none exist yet, so a `conditions` entry naming any system with no registered handler resolves `{found: false}` -- indeterminate, fail-closed. Absent `nodes` (an empty array, the caller's own signal for "no conditions field present") is trivially `{ok: true}` -- no extra restrictions, exactly today's behaviour. Every entry MUST evaluate to a definite `true`; the loop short-circuits on the first indeterminate or false result.
|
|
134
|
+
*/
|
|
135
|
+
async function evaluateConditions(nodes, claims, clock, extraHandlers = {}, subject) {
|
|
136
|
+
const context = subject === void 0 ? {
|
|
137
|
+
clock,
|
|
138
|
+
claims
|
|
139
|
+
} : {
|
|
140
|
+
clock,
|
|
141
|
+
claims,
|
|
142
|
+
subject
|
|
143
|
+
};
|
|
144
|
+
const resolvers = {
|
|
145
|
+
resolveValue: unusedResolveValue,
|
|
146
|
+
resolveLookup: unusedResolveLookup,
|
|
147
|
+
resolveCollection: unusedResolveCollection,
|
|
148
|
+
resolveDelegate: async (system, payload) => {
|
|
149
|
+
if (subject !== void 0 && isSubjectSystem(system)) try {
|
|
150
|
+
return await subjectHandlers[system](payload, context);
|
|
151
|
+
} catch {
|
|
152
|
+
return { found: false };
|
|
153
|
+
}
|
|
154
|
+
const handler = Object.hasOwn(extraHandlers, system) ? extraHandlers[system] : void 0;
|
|
155
|
+
if (handler === void 0) return { found: false };
|
|
156
|
+
try {
|
|
157
|
+
return await handler(payload, context);
|
|
158
|
+
} catch {
|
|
159
|
+
return { found: false };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
for (const node of nodes) {
|
|
164
|
+
const result = await evaluatePredicate(node, context, resolvers);
|
|
165
|
+
if (result.status !== "definite" || !result.value) return {
|
|
166
|
+
ok: false,
|
|
167
|
+
reason: "not_satisfied"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return { ok: true };
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { BEARER_IS, CAPABILITY_IS, DEPTH_REMAINING, EXPIRES_AT, GRANTED_CAPABILITY_IS, GRANTEE_IS, SCOPE_NARROWS, conditionsListSchema, evaluateConditions, evaluateNarrowing, noSelfGrantBar };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
1
2
|
//#region src/domain/token-scope.ts
|
|
2
3
|
/**
|
|
3
4
|
* Extracted out of tokens.ts specifically so token-predicates.ts (the generic evaluator's own delegate handlers) and tokens.ts (the chain walk, and its re-export for capability-grant.ts) can both depend on this pure, no-domain-imports module without creating an import cycle between the two -- tokens.ts imports evaluateNarrowing from token-predicates.ts, and token-predicates.ts's own scope-narrows/bearer-is handlers need these exact comparisons, so neither of those two files may import the other.
|
|
@@ -29,15 +30,5 @@ function scopeNarrows(parent, child) {
|
|
|
29
30
|
return pathNarrows(child.path, parent.path);
|
|
30
31
|
}
|
|
31
32
|
//#endregion
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
get: function() {
|
|
35
|
-
return bytesEqual;
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
Object.defineProperty(exports, "scopeNarrows", {
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: function() {
|
|
41
|
-
return scopeNarrows;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
33
|
+
exports.bytesEqual = bytesEqual;
|
|
34
|
+
exports.scopeNarrows = scopeNarrows;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { en as TokenClaims } from "../protocol-DzmpxxUr.cjs";
|
|
2
|
+
//#region src/domain/token-scope.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Extracted out of tokens.ts specifically so token-predicates.ts (the generic evaluator's own delegate handlers) and tokens.ts (the chain walk, and its re-export for capability-grant.ts) can both depend on this pure, no-domain-imports module without creating an import cycle between the two -- tokens.ts imports evaluateNarrowing from token-predicates.ts, and token-predicates.ts's own scope-narrows/bearer-is handlers need these exact comparisons, so neither of those two files may import the other.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True when childScope narrows parentScope per tokens.cddl ("each hop can only narrow authority, never widen it"): the kind must be identical (a different kind is a different kind of authority, not a narrower one), and a parent with a path requires the child to carry an equal-or-descendant path -- an absent child path means the kind's whole-scope root, which is wider than any path-narrowed parent. A parent with no path (whole-scope root) lets any child path under the same kind through. Exported for capability-grant.ts's own obligation 4 (an unsolicited push's embedded token must equal-or-root the enclosing request's own scope), which is exactly this same narrowing relation applied outside a delegation chain, and for token-predicates.ts's own `scope-narrows` delegate handler.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scopeNarrows(parent: TokenClaims["scope"], child: TokenClaims["scope"]): boolean;
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { en as TokenClaims } from "../protocol-DzmpxxUr.mjs";
|
|
2
|
+
//#region src/domain/token-scope.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Extracted out of tokens.ts specifically so token-predicates.ts (the generic evaluator's own delegate handlers) and tokens.ts (the chain walk, and its re-export for capability-grant.ts) can both depend on this pure, no-domain-imports module without creating an import cycle between the two -- tokens.ts imports evaluateNarrowing from token-predicates.ts, and token-predicates.ts's own scope-narrows/bearer-is handlers need these exact comparisons, so neither of those two files may import the other.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True when childScope narrows parentScope per tokens.cddl ("each hop can only narrow authority, never widen it"): the kind must be identical (a different kind is a different kind of authority, not a narrower one), and a parent with a path requires the child to carry an equal-or-descendant path -- an absent child path means the kind's whole-scope root, which is wider than any path-narrowed parent. A parent with no path (whole-scope root) lets any child path under the same kind through. Exported for capability-grant.ts's own obligation 4 (an unsolicited push's embedded token must equal-or-root the enclosing request's own scope), which is exactly this same narrowing relation applied outside a delegation chain, and for token-predicates.ts's own `scope-narrows` delegate handler.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scopeNarrows(parent: TokenClaims["scope"], child: TokenClaims["scope"]): boolean;
|
|
11
|
+
//#endregion
|