@steve02081504/fount-p2p 0.0.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 (154) hide show
  1. package/README.md +62 -0
  2. package/core/bytes_codec.mjs +51 -0
  3. package/core/canonical_json.mjs +21 -0
  4. package/core/composite_key.mjs +91 -0
  5. package/core/constants.mjs +53 -0
  6. package/core/entity_id.mjs +53 -0
  7. package/core/entity_id_parse.mjs +40 -0
  8. package/core/evfs_logical_path.mjs +15 -0
  9. package/core/hexIds.mjs +73 -0
  10. package/core/hlc.mjs +114 -0
  11. package/core/random_id.mjs +9 -0
  12. package/crypto/channel.mjs +101 -0
  13. package/crypto/checkpoint_sign.mjs +67 -0
  14. package/crypto/crypto.mjs +158 -0
  15. package/crypto/key.mjs +431 -0
  16. package/dag/canonicalize_row.mjs +65 -0
  17. package/dag/event_query.mjs +15 -0
  18. package/dag/index.mjs +335 -0
  19. package/dag/storage.mjs +333 -0
  20. package/dag/strip_extensions.mjs +18 -0
  21. package/dag/tunables.json +6 -0
  22. package/discovery/bt.mjs +260 -0
  23. package/discovery/index.mjs +134 -0
  24. package/discovery/mdns.mjs +138 -0
  25. package/discovery/nostr.mjs +273 -0
  26. package/entity/files/acl.mjs +38 -0
  27. package/entity/files/evfs.mjs +189 -0
  28. package/entity/files/evfs_ref.mjs +37 -0
  29. package/entity/files/manifest_acl_registry.mjs +93 -0
  30. package/entity/files/replica_host_cache.mjs +46 -0
  31. package/entity/files/url.mjs +19 -0
  32. package/entity/hosting_registry.mjs +47 -0
  33. package/entity/localized_core.mjs +141 -0
  34. package/entity/logical_entity.mjs +25 -0
  35. package/entity/logical_entity_id_registry.mjs +40 -0
  36. package/entity/node_hash.mjs +15 -0
  37. package/entity/presentation_registry.mjs +44 -0
  38. package/entity/profile.mjs +256 -0
  39. package/entity/replica.mjs +20 -0
  40. package/entity/session_snapshot_registry.mjs +38 -0
  41. package/federation/chunk_fetch_pending.mjs +128 -0
  42. package/federation/chunk_fetch_scheduler.mjs +97 -0
  43. package/federation/dag_order_cache.mjs +124 -0
  44. package/federation/dedupe_slot.mjs +33 -0
  45. package/federation/message_rate_limit.mjs +23 -0
  46. package/federation/operator_key_chain.mjs +200 -0
  47. package/federation/topo_order_memo.mjs +44 -0
  48. package/federation/volatile_streams.mjs +52 -0
  49. package/federation/want_ids.mjs +158 -0
  50. package/files/assemble.mjs +220 -0
  51. package/files/assemble_stream.mjs +129 -0
  52. package/files/chunk_fetch.mjs +113 -0
  53. package/files/chunk_fetch_verify.mjs +23 -0
  54. package/files/chunk_provider_registry.mjs +70 -0
  55. package/files/chunk_responder.mjs +79 -0
  56. package/files/chunk_store.mjs +87 -0
  57. package/files/manifest.mjs +99 -0
  58. package/files/transfer_key.mjs +87 -0
  59. package/files/transfer_key_registry.mjs +108 -0
  60. package/governance/branch.mjs +230 -0
  61. package/governance/join_pow.mjs +165 -0
  62. package/governance/owner_succession_ballot.mjs +57 -0
  63. package/governance/tunables.json +7 -0
  64. package/index.mjs +39 -0
  65. package/link/channel_mux.mjs +222 -0
  66. package/link/frame.mjs +246 -0
  67. package/link/handshake.mjs +175 -0
  68. package/link/link.mjs +617 -0
  69. package/link/rtc.mjs +109 -0
  70. package/link/sdp_fingerprint.mjs +25 -0
  71. package/mailbox/consumer_registry.mjs +71 -0
  72. package/mailbox/deliver_or_store.mjs +190 -0
  73. package/mailbox/importance.mjs +72 -0
  74. package/mailbox/parse.mjs +73 -0
  75. package/mailbox/prune.mjs +106 -0
  76. package/mailbox/rate.mjs +86 -0
  77. package/mailbox/settings.mjs +53 -0
  78. package/mailbox/store.mjs +256 -0
  79. package/mailbox/tunables.json +12 -0
  80. package/mailbox/wire.mjs +40 -0
  81. package/node/denylist.mjs +309 -0
  82. package/node/entity_store.mjs +214 -0
  83. package/node/identity.mjs +99 -0
  84. package/node/instance.mjs +115 -0
  85. package/node/network.mjs +306 -0
  86. package/node/personal_block.mjs +286 -0
  87. package/node/reputation_store.mjs +326 -0
  88. package/node/retention_policy.mjs +82 -0
  89. package/node/signaling_config.mjs +32 -0
  90. package/node/storage.mjs +30 -0
  91. package/node/storage_plugins.mjs +71 -0
  92. package/node/user_paths.mjs +10 -0
  93. package/overlay/index.mjs +191 -0
  94. package/package.json +73 -0
  95. package/permissions/bitmask.mjs +35 -0
  96. package/permissions/evaluator.mjs +90 -0
  97. package/permissions/index.mjs +12 -0
  98. package/permissions/layered.mjs +39 -0
  99. package/registries/action.mjs +94 -0
  100. package/registries/event_type.mjs +75 -0
  101. package/registries/inbound.mjs +90 -0
  102. package/registries/p2p_viewer.mjs +35 -0
  103. package/registries/part_path.mjs +35 -0
  104. package/registries/room_provider.mjs +47 -0
  105. package/reputation/engine.mjs +458 -0
  106. package/reputation/math.mjs +73 -0
  107. package/reputation/pick_score.mjs +11 -0
  108. package/reputation/relay_dedupe.mjs +22 -0
  109. package/reputation/tunables.json +36 -0
  110. package/rooms/scoped_link.mjs +185 -0
  111. package/schemas/discovery.mjs +110 -0
  112. package/schemas/federation_pull.mjs +102 -0
  113. package/schemas/mailbox.mjs +37 -0
  114. package/schemas/remote_event.mjs +17 -0
  115. package/timeline/append_core.mjs +54 -0
  116. package/timeline/materialize_runner.mjs +26 -0
  117. package/timeline/prune.mjs +39 -0
  118. package/timeline/retention.mjs +54 -0
  119. package/timeline/retention_runner.mjs +35 -0
  120. package/timeline/verify_remote.mjs +25 -0
  121. package/transport/group_link_set.mjs +324 -0
  122. package/transport/ice_servers.mjs +72 -0
  123. package/transport/link_registry.mjs +749 -0
  124. package/transport/peer_identity_maps.mjs +94 -0
  125. package/transport/peer_pool.mjs +355 -0
  126. package/transport/remote_user_room.mjs +109 -0
  127. package/transport/room_scopes.mjs +2 -0
  128. package/transport/room_wire_action.mjs +19 -0
  129. package/transport/rtc_connection_budget.mjs +180 -0
  130. package/transport/rtc_mdns_filter.mjs +118 -0
  131. package/transport/stale_peer_log.mjs +50 -0
  132. package/transport/user_room.mjs +263 -0
  133. package/trust_graph/build.mjs +99 -0
  134. package/trust_graph/cache.mjs +44 -0
  135. package/trust_graph/engine.mjs +184 -0
  136. package/trust_graph/registry.mjs +38 -0
  137. package/trust_graph/resolve.mjs +105 -0
  138. package/trust_graph/send.mjs +88 -0
  139. package/trust_graph/tunables.json +13 -0
  140. package/utils/async_mutex.mjs +69 -0
  141. package/utils/debug_log.mjs +16 -0
  142. package/utils/duration.mjs +28 -0
  143. package/utils/json_io.mjs +57 -0
  144. package/utils/lru.mjs +22 -0
  145. package/utils/map_pool.mjs +27 -0
  146. package/wire/group_part.mjs +43 -0
  147. package/wire/ingress.mjs +58 -0
  148. package/wire/part_common.mjs +55 -0
  149. package/wire/part_fanout.mjs +71 -0
  150. package/wire/part_ingress.mjs +172 -0
  151. package/wire/part_invoke.mjs +81 -0
  152. package/wire/rate_bucket.mjs +49 -0
  153. package/wire/volatile_signature.mjs +76 -0
  154. package/wire/wait.mjs +127 -0
@@ -0,0 +1,191 @@
1
+ import { Buffer } from 'node:buffer'
2
+
3
+ import { pubKeyHash, sign, verify } from '../crypto/crypto.mjs'
4
+ import { randomMsgIdHex } from '../link/frame.mjs'
5
+ import { createLruMap } from '../utils/lru.mjs'
6
+
7
+ const ROUTE_DOMAIN = 'fount-route-v1'
8
+
9
+ /**
10
+ * 构造 overlay 路由签名用的字节序列。
11
+ * @param {string} reqId 路由请求 id
12
+ * @param {string[]} path 已遍历节点路径
13
+ * @returns {Uint8Array} 待签名字节
14
+ */
15
+ function routeSignBytes(reqId, path) {
16
+ return Buffer.from(`${ROUTE_DOMAIN}\0${reqId}\0${path.join(',')}`, 'utf8')
17
+ }
18
+
19
+ /**
20
+ * 创建 overlay 多跳路由与 relay 路由器。
21
+ * @param {object} registry link registry(含 localIdentity、sendToNodeLink、listLinks、subscribeScope)
22
+ * @param {number} [ttl=3] 默认路由 TTL(最大跳数)
23
+ * @returns {object} 路由器接口(discoverRoute、relay、onRelay、close)
24
+ */
25
+ export function createOverlayRouter(registry, ttl = 3) {
26
+ const selfNodeHash = registry.localIdentity.nodeHash
27
+ const selfPubKey = registry.localIdentity.nodePubKey
28
+ const secretKey = registry.localIdentity.secretKey
29
+ const seenReqs = createLruMap(4096)
30
+ /** @type {Map<string, { resolve: (path: string[]) => void, reject: (err: Error) => void, timer: number }>} */
31
+ const pendingRoutes = new Map()
32
+ /** @type {Set<(body: unknown, meta: { path: string[], from: string }) => void>} */
33
+ const relayListeners = new Set()
34
+
35
+ /**
36
+ * 经 overlay scope 向节点发送 payload。
37
+ * @param {string} nodeHash 目标节点 64 hex
38
+ * @param {object} payload overlay action 载荷
39
+ * @returns {Promise<void>}
40
+ */
41
+ async function sendOverlay(nodeHash, payload) {
42
+ await registry.sendToNodeLink(nodeHash, { scope: 'overlay', action: payload.action, payload })
43
+ }
44
+
45
+ /**
46
+ * 处理入站 overlay envelope(route_req / route_resp / relay)。
47
+ * @param {string} senderNodeHash 发送方节点 64 hex
48
+ * @param {object} envelope overlay 信封
49
+ * @returns {Promise<void>}
50
+ */
51
+ async function handleOverlay(senderNodeHash, envelope) {
52
+ const payload = envelope?.payload
53
+ const action = String(envelope?.action || '')
54
+ if (!payload || typeof payload !== 'object') return
55
+ if (action === 'route_req') {
56
+ const reqId = String(payload.reqId || '')
57
+ const target = String(payload.target || '')
58
+ const hops = Array.isArray(payload.path) ? payload.path.map(String) : []
59
+ const remainingTtl = Number(payload.ttl)
60
+ if (!reqId || !target || !hops.length || remainingTtl <= 0) return
61
+ if (seenReqs.has(reqId) || hops.includes(selfNodeHash) || hops.length > 6) return
62
+ seenReqs.touch(reqId, true)
63
+ const nextPath = [...hops, selfNodeHash]
64
+ if (target === selfNodeHash) {
65
+ const sig = await sign(routeSignBytes(reqId, nextPath), secretKey)
66
+ const prevHop = hops[hops.length - 1]
67
+ if (prevHop)
68
+ await sendOverlay(prevHop, {
69
+ action: 'route_resp',
70
+ reqId,
71
+ path: nextPath,
72
+ nodePubKey: selfPubKey,
73
+ sig: Buffer.from(sig).toString('hex'),
74
+ })
75
+ return
76
+ }
77
+ for (const { nodeHash } of registry.listLinks())
78
+ if (nodeHash !== senderNodeHash && !hops.includes(nodeHash))
79
+ await sendOverlay(nodeHash, {
80
+ action: 'route_req',
81
+ reqId,
82
+ target,
83
+ ttl: remainingTtl - 1,
84
+ path: nextPath,
85
+ })
86
+ return
87
+ }
88
+ if (action === 'route_resp') {
89
+ const reqId = String(payload.reqId || '')
90
+ const path = Array.isArray(payload.path) ? payload.path.map(String) : []
91
+ const nodePubKey = String(payload.nodePubKey || '')
92
+ const sigHex = String(payload.sig || '')
93
+ if (!reqId || path.length < 2 || !nodePubKey || !sigHex) return
94
+ if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== path[path.length - 1]) return
95
+ const ok = await verify(Buffer.from(sigHex, 'hex'), routeSignBytes(reqId, path), Buffer.from(nodePubKey, 'hex'))
96
+ if (!ok) return
97
+ if (path[0] === selfNodeHash) {
98
+ const pending = pendingRoutes.get(reqId)
99
+ if (!pending) return
100
+ clearTimeout(pending.timer)
101
+ pendingRoutes.delete(reqId)
102
+ pending.resolve(path)
103
+ return
104
+ }
105
+ const index = path.indexOf(selfNodeHash)
106
+ if (index <= 0) return
107
+ await sendOverlay(path[index - 1], payload)
108
+ return
109
+ }
110
+ if (action === 'relay') {
111
+ const path = Array.isArray(payload.path) ? payload.path.map(String) : []
112
+ const index = Number(payload.idx)
113
+ if (!path.length || path[index] !== selfNodeHash) return
114
+ if (index === path.length - 1) {
115
+ for (const listener of relayListeners)
116
+ listener(payload.body, { path, from: senderNodeHash })
117
+ return
118
+ }
119
+ await sendOverlay(path[index + 1], {
120
+ action: 'relay',
121
+ path,
122
+ idx: index + 1,
123
+ body: payload.body,
124
+ })
125
+ }
126
+ }
127
+
128
+ const unsubscribe = registry.subscribeScope('overlay', handleOverlay)
129
+
130
+ return {
131
+ /**
132
+ * 发现到目标节点的签名路由路径。
133
+ * @param {string} targetNodeHash 目标节点 64 hex
134
+ * @param {object} [opts] 选项
135
+ * @param {number} [opts.ttl] 路由 TTL
136
+ * @param {number} [opts.timeoutMs] 超时毫秒
137
+ * @returns {Promise<string[]>} 从本节点到目标的 nodeHash 路径
138
+ */
139
+ async discoverRoute(targetNodeHash, opts = {}) {
140
+ const reqId = randomMsgIdHex()
141
+ const maxTtl = Number(opts.ttl) || ttl
142
+ const timeoutMs = Number(opts.timeoutMs) || 10_000
143
+ const promise = new Promise((resolve, reject) => {
144
+ const timer = setTimeout(() => {
145
+ pendingRoutes.delete(reqId)
146
+ reject(new Error(`overlay: route discovery timeout for ${targetNodeHash}`))
147
+ }, timeoutMs)
148
+ pendingRoutes.set(reqId, { resolve, reject, timer })
149
+ })
150
+ for (const { nodeHash } of registry.listLinks())
151
+ await sendOverlay(nodeHash, {
152
+ action: 'route_req',
153
+ reqId,
154
+ target: targetNodeHash,
155
+ ttl: maxTtl,
156
+ path: [selfNodeHash],
157
+ })
158
+ return await promise
159
+ },
160
+ /**
161
+ * 沿已发现路径 relay 载荷到路径末端。
162
+ * @param {string[]} path 路由路径(首节点须为本节点)
163
+ * @param {unknown} body relay 载荷
164
+ * @returns {Promise<void>}
165
+ */
166
+ async relay(path, body) {
167
+ if (!Array.isArray(path) || path[0] !== selfNodeHash || path.length < 2)
168
+ throw new Error('overlay: invalid relay path')
169
+ await sendOverlay(path[1], { action: 'relay', path, idx: 1, body })
170
+ },
171
+ /**
172
+ * 订阅 relay 到达本节点(路径末端)的载荷。
173
+ * @param {(body: unknown, meta: { path: string[], from: string }) => void} listener 回调
174
+ * @returns {() => void} 取消订阅函数
175
+ */
176
+ onRelay(listener) {
177
+ relayListeners.add(listener)
178
+ return () => relayListeners.delete(listener)
179
+ },
180
+ /**
181
+ * 关闭路由器并清理 pending 与监听器。
182
+ * @returns {void}
183
+ */
184
+ close() {
185
+ unsubscribe()
186
+ for (const pending of pendingRoutes.values()) clearTimeout(pending.timer)
187
+ pendingRoutes.clear()
188
+ relayListeners.clear()
189
+ },
190
+ }
191
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@steve02081504/fount-p2p",
3
+ "version": "0.0.0",
4
+ "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
+ "categories": [
6
+ "network",
7
+ "p2p",
8
+ "fount",
9
+ "AI"
10
+ ],
11
+ "type": "module",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/steve02081504/fount-p2p.git",
15
+ "directory": "."
16
+ },
17
+ "engines": {
18
+ "node": ">=20"
19
+ },
20
+ "scripts": {
21
+ "test": "node --test --test-concurrency=1 --test-force-exit test/pure/*.test.mjs test/integration/*.test.mjs",
22
+ "test:live": "node --test --test-concurrency=1 --test-force-exit test/live/*.test.mjs",
23
+ "test:sim": "node --test --test-concurrency=1 --test-force-exit sim/test/*.test.mjs"
24
+ },
25
+ "files": [
26
+ "**/*.mjs",
27
+ "**/*.json",
28
+ "!test/**",
29
+ "!sim/**",
30
+ "!scripts/**"
31
+ ],
32
+ "exports": {
33
+ ".": "./index.mjs",
34
+ "./core/*": "./core/*.mjs",
35
+ "./crypto": "./crypto/crypto.mjs",
36
+ "./crypto/*": "./crypto/*.mjs",
37
+ "./wire/*": "./wire/*.mjs",
38
+ "./schemas/*": "./schemas/*.mjs",
39
+ "./node/*": "./node/*.mjs",
40
+ "./discovery": "./discovery/index.mjs",
41
+ "./discovery/*": "./discovery/*.mjs",
42
+ "./link/*": "./link/*.mjs",
43
+ "./transport/*": "./transport/*.mjs",
44
+ "./rooms/*": "./rooms/*.mjs",
45
+ "./overlay": "./overlay/index.mjs",
46
+ "./overlay/*": "./overlay/*.mjs",
47
+ "./entity/*": "./entity/*.mjs",
48
+ "./entity/files/*": "./entity/files/*.mjs",
49
+ "./trust_graph/*": "./trust_graph/*.mjs",
50
+ "./mailbox/*": "./mailbox/*.mjs",
51
+ "./dag": "./dag/index.mjs",
52
+ "./dag/*": "./dag/*.mjs",
53
+ "./timeline/*": "./timeline/*.mjs",
54
+ "./files/*": "./files/*.mjs",
55
+ "./federation/*": "./federation/*.mjs",
56
+ "./governance/*": "./governance/*.mjs",
57
+ "./registries/*": "./registries/*.mjs",
58
+ "./permissions": "./permissions/index.mjs",
59
+ "./reputation/*": "./reputation/*.mjs",
60
+ "./utils/*": "./utils/*.mjs"
61
+ },
62
+ "dependencies": {
63
+ "@noble/curves": "^1.9.0",
64
+ "node-datachannel": "^0.11.0"
65
+ },
66
+ "devDependencies": {
67
+ "@steve02081504/exec": "file:../../exec"
68
+ },
69
+ "optionalDependencies": {
70
+ "@stoprocent/bleno": "^0.11.0",
71
+ "@stoprocent/noble": "^1.9.0"
72
+ }
73
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 通用权限位图编解码。
3
+ */
4
+
5
+ /**
6
+ * @param {readonly string[]} order 权限名有序列表(位序固定)
7
+ * @returns {{ encode: (permissions: Record<string, boolean> | null | undefined) => bigint, decode: (bits: bigint) => Record<string, boolean> }} codec
8
+ */
9
+ export function createPermissionCodec(order) {
10
+ const names = [...order]
11
+ /**
12
+ * @param {Record<string, boolean> | null | undefined} permissions 权限对象
13
+ * @returns {bigint} 按位编码
14
+ */
15
+ function encode(permissions) {
16
+ let bits = 0n
17
+ if (!permissions) return bits
18
+ for (let index = 0; index < names.length; index++)
19
+ if (permissions[names[index]])
20
+ bits |= 1n << BigInt(index)
21
+ return bits
22
+ }
23
+ /**
24
+ * @param {bigint} bits 权限位
25
+ * @returns {Record<string, boolean>} 各权限名到布尔值
26
+ */
27
+ function decode(bits) {
28
+ /** @type {Record<string, boolean>} */
29
+ const permissions = {}
30
+ for (let index = 0; index < names.length; index++)
31
+ permissions[names[index]] = Boolean(bits & (1n << BigInt(index)))
32
+ return permissions
33
+ }
34
+ return { encode, decode }
35
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Discord 式「按特定度分层」权限求值器工厂。
3
+ */
4
+ import { createPermissionCodec } from './bitmask.mjs'
5
+ import { applyDenyAllowOverride, mergeRoleOverrides } from './layered.mjs'
6
+
7
+ /**
8
+ * @typedef {object} LayeredEvaluatorSchema
9
+ * @property {readonly string[]} order 权限名有序列表
10
+ * @property {string} superuserName 旁路全部 scope 覆写的权限名(如 ADMIN)
11
+ * @property {string} [everyoneRoleId='@everyone'] scope 内最宽角色 id
12
+ */
13
+
14
+ /**
15
+ * @param {LayeredEvaluatorSchema} schema 求值 schema
16
+ * @returns {{
17
+ * order: readonly string[],
18
+ * encode: (permissions: Record<string, boolean> | null | undefined) => bigint,
19
+ * decode: (bits: bigint) => Record<string, boolean>,
20
+ * calculate: (member: { roles?: string[] }, roles: Record<string, { permissions?: Record<string, boolean> }>, scopeId: string, scopeOverrides: Record<string, Record<string, { deny?: Record<string, boolean>, allow?: Record<string, boolean> }>>) => Record<string, boolean>,
21
+ * has: (member: object, permission: string, roles: object, scopeId: string, scopeOverrides: object) => boolean,
22
+ * }} evaluator
23
+ */
24
+ export function createLayeredEvaluator(schema) {
25
+ const { encode, decode } = createPermissionCodec(schema.order)
26
+ const superuserBit = 1n << BigInt(schema.order.indexOf(schema.superuserName))
27
+ const everyoneRoleId = schema.everyoneRoleId ?? '@everyone'
28
+
29
+ /**
30
+ * @param {{ roles?: string[] }} member 成员
31
+ * @param {Record<string, { permissions?: Record<string, boolean> }>} roles 角色映射
32
+ * @param {string} scopeId scope id(如 channelId)
33
+ * @param {Record<string, Record<string, { deny?: Record<string, boolean>, allow?: Record<string, boolean> }>>} scopeOverrides scope 覆写表
34
+ * @returns {Record<string, boolean>} 最终权限
35
+ */
36
+ function calculate(member, roles, scopeId, scopeOverrides) {
37
+ const roleIds = member.roles || []
38
+
39
+ let baseBits = 0n
40
+ for (const roleId of roleIds) {
41
+ const role = roles[roleId]
42
+ if (role) baseBits |= encode(role.permissions)
43
+ }
44
+
45
+ if (baseBits & superuserBit) {
46
+ /** @type {Record<string, boolean>} */
47
+ const perms = {}
48
+ for (const p of schema.order) perms[p] = true
49
+ return perms
50
+ }
51
+
52
+ let bits = baseBits
53
+ const scopeOverride = scopeOverrides?.[scopeId]
54
+ if (scopeOverride) {
55
+ const everyone = scopeOverride[everyoneRoleId]
56
+ if (everyone)
57
+ bits = applyDenyAllowOverride(bits, everyone, encode)
58
+
59
+ const roleOverrides = []
60
+ for (const roleId of roleIds) {
61
+ if (roleId === everyoneRoleId) continue
62
+ const override = scopeOverride[roleId]
63
+ if (override) roleOverrides.push(override)
64
+ }
65
+ bits = mergeRoleOverrides(bits, roleOverrides, encode)
66
+ }
67
+
68
+ return decode(bits)
69
+ }
70
+
71
+ /**
72
+ * @param {object} member 成员
73
+ * @param {string} permission 权限名
74
+ * @param {object} roles 角色映射
75
+ * @param {string} scopeId scope id
76
+ * @param {object} scopeOverrides scope 覆写表
77
+ * @returns {boolean} 是否具备权限
78
+ */
79
+ function has(member, permission, roles, scopeId, scopeOverrides) {
80
+ return calculate(member, roles, scopeId, scopeOverrides)[permission] === true
81
+ }
82
+
83
+ return {
84
+ order: schema.order,
85
+ encode,
86
+ decode,
87
+ calculate,
88
+ has,
89
+ }
90
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ *
3
+ */
4
+ export { createPermissionCodec } from './bitmask.mjs'
5
+ /**
6
+ *
7
+ */
8
+ export { applyDenyAllowOverride, mergeRoleOverrides } from './layered.mjs'
9
+ /**
10
+ *
11
+ */
12
+ export { createLayeredEvaluator } from './evaluator.mjs'
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 分层 allow/deny 覆写:先去 deny 再叠 allow。
3
+ */
4
+
5
+ /**
6
+ * @param {bigint} baseBits 基线权限位
7
+ * @param {{ deny?: Record<string, boolean> | null, allow?: Record<string, boolean> | null }} override 覆写
8
+ * @param {(permissions: Record<string, boolean> | null | undefined) => bigint} encode 编码函数
9
+ * @returns {bigint} 覆写后的权限位
10
+ */
11
+ export function applyDenyAllowOverride(baseBits, override, encode) {
12
+ if (!override) return baseBits
13
+ let bits = baseBits
14
+ if (override.deny)
15
+ bits &= ~encode(override.deny)
16
+ if (override.allow)
17
+ bits |= encode(override.allow)
18
+ return bits
19
+ }
20
+
21
+ /**
22
+ * 合并多个角色的 allow/deny 后整体应用(顺序无关)。
23
+ * @param {bigint} baseBits 基线权限位
24
+ * @param {Array<{ deny?: Record<string, boolean> | null, allow?: Record<string, boolean> | null }>} overrides 覆写列表
25
+ * @param {(permissions: Record<string, boolean> | null | undefined) => bigint} encode 编码函数
26
+ * @returns {bigint} 覆写后的权限位
27
+ */
28
+ export function mergeRoleOverrides(baseBits, overrides, encode) {
29
+ let roleAllow = 0n
30
+ let roleDeny = 0n
31
+ for (const override of overrides) {
32
+ if (!override) continue
33
+ if (override.allow)
34
+ roleAllow |= encode(override.allow)
35
+ if (override.deny)
36
+ roleDeny |= encode(override.deny)
37
+ }
38
+ return (baseBits & ~roleDeny) | roleAllow
39
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 基于 room.makeAction(name) 的通用 action 注册表。
3
+ */
4
+
5
+ /**
6
+ * P2P 房间 action 注册表,封装 makeAction 的 send/receive 对。
7
+ */
8
+ export class ActionRegistry {
9
+ /** @type {Map<string, { send: Function, get: Function }>} */
10
+ #entries = new Map()
11
+
12
+ /**
13
+ * @param {{ makeAction: (name: string) => [Function, Function] }} room 提供 makeAction 的房间对象
14
+ */
15
+ constructor(room) {
16
+ this.room = room
17
+ }
18
+
19
+ /**
20
+ * 预注册一个或多个 action 名称。
21
+ * @param {string | string[]} names action 名称或名称列表
22
+ * @returns {ActionRegistry} 当前实例(链式调用)
23
+ */
24
+ register(names) {
25
+ const list = Array.isArray(names) ? names : [names]
26
+ for (const name of list)
27
+ this.#ensureEntry(name)
28
+ return this
29
+ }
30
+
31
+ /**
32
+ * 获取或创建指定 action 的 send/get 条目。
33
+ * @param {string} name action 名称
34
+ * @returns {{ send: Function, get: Function }} send 与 get 函数对
35
+ */
36
+ #ensureEntry(name) {
37
+ let entry = this.#entries.get(name)
38
+ if (!entry) {
39
+ const [send, get] = this.room.makeAction(name)
40
+ entry = { send, get }
41
+ this.#entries.set(name, entry)
42
+ }
43
+ return entry
44
+ }
45
+
46
+ /**
47
+ * 获取指定 action 的发送函数。
48
+ * @param {string} name action 名称
49
+ * @returns {Function} 发送函数
50
+ */
51
+ sender(name) {
52
+ return this.#ensureEntry(name).send
53
+ }
54
+
55
+ /**
56
+ * 获取指定 action 的接收注册函数。
57
+ * @param {string} name action 名称
58
+ * @returns {Function} 接收注册函数
59
+ */
60
+ receiver(name) {
61
+ return this.#ensureEntry(name).get
62
+ }
63
+
64
+ /**
65
+ * 向指定 peer 发送 action 载荷。
66
+ * @param {string} name action 名称
67
+ * @param {unknown} payload 载荷
68
+ * @param {string | null} [peerId] 目标 peer;null 表示广播
69
+ * @returns {void}
70
+ */
71
+ send(name, payload, peerId = null) {
72
+ void this.sender(name)(payload, peerId)
73
+ }
74
+
75
+ /**
76
+ * 注册 action 入站 handler。
77
+ * @param {string} name action 名称
78
+ * @param {(payload: unknown, peerId: string) => void} handler 入站回调
79
+ * @returns {ActionRegistry} 当前实例(链式调用)
80
+ */
81
+ on(name, handler) {
82
+ this.receiver(name)(handler)
83
+ return this
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 创建 ActionRegistry 实例。
89
+ * @param {{ makeAction: (name: string) => [Function, Function] }} room 提供 makeAction 的房间对象
90
+ * @returns {ActionRegistry} 新注册表
91
+ */
92
+ export function createActionRegistry(room) {
93
+ return new ActionRegistry(room)
94
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @typedef {object} EventTypeFlags
3
+ * @property {boolean} [aclGated]
4
+ * @property {boolean} [gcExclude]
5
+ * @property {boolean} [governance]
6
+ * @property {boolean} [permissionAnchor]
7
+ */
8
+
9
+ /** @type {Map<string, Record<string, EventTypeFlags>>} */
10
+ const defsByOwner = new Map()
11
+
12
+ /**
13
+ * @param {string} ownerId 注册方
14
+ * @param {Record<string, EventTypeFlags>} defs 事件 type 元数据
15
+ * @returns {void}
16
+ */
17
+ export function registerEventTypeDefs(ownerId, defs) {
18
+ defsByOwner.set(String(ownerId), defs)
19
+ }
20
+
21
+ /**
22
+ * @param {string} ownerId 注册方
23
+ * @returns {void}
24
+ */
25
+ export function unregisterEventTypeDefs(ownerId) {
26
+ defsByOwner.delete(String(ownerId))
27
+ }
28
+
29
+ /** @returns {void} */
30
+ export function clearEventTypeRegistry() {
31
+ defsByOwner.clear()
32
+ }
33
+
34
+ /**
35
+ * @returns {Record<string, EventTypeFlags>} 合并后的 defs
36
+ */
37
+ export function mergedEventTypeDefs() {
38
+ /** @type {Record<string, EventTypeFlags>} */
39
+ const merged = {}
40
+ for (const defs of defsByOwner.values())
41
+ Object.assign(merged, defs)
42
+ return merged
43
+ }
44
+
45
+ /**
46
+ * @param {'aclGated' | 'gcExclude' | 'governance' | 'permissionAnchor'} flag 标志位名
47
+ * @returns {Set<string>} 含该标志的事件 type 集合
48
+ */
49
+ export function typesWithFlag(flag) {
50
+ return new Set(Object.entries(mergedEventTypeDefs()).filter(([, f]) => f[flag]).map(([k]) => k))
51
+ }
52
+
53
+ /** §8 治理分叉选支:祖先闭包内计入信誉加权的类型。 */
54
+ /** @returns {Set<string>} 治理事件 type 集合 */
55
+ export function getGovernanceAuthzTypes() {
56
+ return typesWithFlag('governance')
57
+ }
58
+
59
+ /** 联邦入站/中继前须物化 ACL 门控的类型。 */
60
+ /** @returns {Set<string>} ACL 门控事件 type 集合 */
61
+ export function getFederationAclGatedEventTypes() {
62
+ return typesWithFlag('aclGated')
63
+ }
64
+
65
+ /** §6.2 频道 GC 沉寂计时排除的类型。 */
66
+ /** @returns {Set<string>} GC 排除事件 type 集合 */
67
+ export function getChannelGcExcludedEventTypes() {
68
+ return typesWithFlag('gcExclude')
69
+ }
70
+
71
+ /** 裁剪时不得早于最早一条权限锚点事件(§7.1)。 */
72
+ /** @returns {Set<string>} 权限锚点事件 type 集合 */
73
+ export function getPermissionAnchorTypes() {
74
+ return typesWithFlag('permissionAnchor')
75
+ }