@steve02081504/fount-p2p 0.0.38 → 0.0.39

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/AGENTS.md CHANGED
@@ -58,7 +58,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
58
58
  - **Fanout vs targeted / timed collect:** [wire.md](docs/wire.md).
59
59
  - **Channel encryption:** per-channel `K_ch`, scheme `channel-key` (`CHANNEL_KEY_SCHEME`); decrypted payloads are untrusted outside DAG Ed25519 context.
60
60
  - **Denylist vs personal lists:** node `denylist.json` vs per-entity `personal_block.json` / `personal_hide.json`.
61
- - **Manifest ACL / transfer owner / servicer:** shells register matchers and `registerManifestServicer`; core does not hard-code chat/social types. Non-public manifests are served cross-node only via a registered servicer (default deny).
61
+ - **Manifest ACL / transfer owner / servicer:** ownership is routed by `registerManifestOwner` matchers only; ACL & servicer handlers are keyed by `ownerId` (a `type` may be reused across families but never acts as a routing key). Core does not hard-code chat/social types. Non-public manifests are served cross-node only via a registered servicer; unclaimed non-public is denied (no `type` fallback).
62
62
 
63
63
  ### Node / network
64
64
 
package/docs/evfs.md CHANGED
@@ -32,10 +32,13 @@ Outer caller timeouts must **not** abort the in-flight work — background fill
32
32
 
33
33
  - `file-master-key-wrap` / `vault-wrap` / `identity-wrap` manifests carry no author signature, so the remote-acquisition trust boundary is **not** a signature — it is the **fanout target set** plus the **serving node's authorization**.
34
34
  - Client: the caller passes the authorized node set (group roster / cabinet members) as `fanoutTargets`. `fed_manifest_get` is sent only to those nodes; a response is accepted when it normalizes, its `ownerEntityHash`+`logicalPath` match the request, and its authenticated `senderNodeHash` belongs to the target set.
35
- - Server: `handleIncomingManifestGet` refuses non-public manifests unless a **servicer** is registered for the type. Shells call `registerManifestServicer(type, ownerId, handler)`; the handler receives `{ manifest, ownerEntityHash, logicalPath, requesterNodeHash, peerId, payload }` and returns a boolean. `requesterNodeHash` is **always** the transport-authenticated sender nodeHash (`peerId`); the client no longer self-asserts a `nodeHash` in `fed_manifest_get`, so the fanout target node can never be mistaken for the requester — authorization must still be enforced from the trust graph / group membership.
35
+ - Server: `handleIncomingManifestGet` refuses non-public manifests unless a **servicer** is registered for the matched owner family. Shells call `registerManifestOwner(ownerId, match)` to claim ownership and `registerManifestServicer(ownerId, handler)` to serve; the handler receives `{ manifest, ownerEntityHash, logicalPath, requesterNodeHash, peerId, payload }` and returns a boolean. `requesterNodeHash` is **always** the transport-authenticated sender nodeHash (`peerId`); the client no longer self-asserts a `nodeHash` in `fed_manifest_get`, so the fanout target node can never be mistaken for the requester — authorization must still be enforced from the trust graph / group membership.
36
36
  - Non-public responses carry the **full manifest including `meta`** (public responses strip meta to `publicSig`): the reader needs `meta.dagParts` / `meta.groupId` to read DAG plaintext.
37
37
  - Confidentiality does not rely on manifest secrecy: ciphertext blocks are content-addressed, AES-GCM authenticated, and decryption requires a wrap key held by the shell. A forged manifest can at worst fail the read (DoS), never decrypt content.
38
38
 
39
- ## ACL
39
+ ## ACL & owner routing
40
40
 
41
- Shells register matchers via the ACL registry. Core does not hard-code chat/social entity types.
41
+ - Ownership is decided **only** by matchers: `registerManifestOwner(ownerId, match)` with `match(manifest, ownerEntityHash)` returning a boolean. `transferKeyDescriptor.type` is **not** a routing key — the same type (e.g. `file-master-key-wrap`) may be reused by several families (chat group, cabinet shared, vault, …).
42
+ - Matchers must be mutually exclusive: a manifest belongs to exactly one family; resolution is first-match in registration order. A broad matcher that claims another family's entities will shadow them, so matchers should key on their own entity namespace (e.g. the `fount:chat:group:` prefix).
43
+ - Local read/write ACL (`registerManifestAcl(ownerId, handler)`) and cross-node serve (`registerManifestServicer(ownerId, handler)`) are both keyed by `ownerId` (same `ownerId` as `registerTransferKeyDependencies`). A claimed family with no handler denies (fail-closed); an unclaimed non-public manifest is denied cross-node — there is no `type` fallback.
44
+ - Core does not hard-code chat/social entity types.
@@ -1,38 +1,67 @@
1
1
  import { isLogicalEntityHash } from '../../core/logical_entity.mjs'
2
2
  import { isWritableLocalEntity } from '../../node/identity.mjs'
3
3
 
4
- import { checkManifestAcl, resolveManifestAclType } from './acl_registry.mjs'
4
+ import { resolveManifestOwner } from './routing.mjs'
5
5
 
6
6
  /**
7
+ * @typedef {{
8
+ * replicaUsername: string,
9
+ * ownerEntityHash: string,
10
+ * manifest: import('./normalize.mjs').FileManifest | object,
11
+ * }} ManifestAclContext
12
+ */
13
+
14
+ /** @type {Map<string, (context: ManifestAclContext, logicalPath?: string) => Promise<boolean>>} */
15
+ const aclHandlers = new Map()
16
+
17
+ /**
18
+ * 注册本族 manifest 的本地读写授权判定(ownerId 须与 registerManifestOwner 一致)。
19
+ * @param {string} ownerId 注册方
20
+ * @param {(context: ManifestAclContext, logicalPath?: string) => Promise<boolean>} handler 授权判定
21
+ * @returns {void}
22
+ */
23
+ export function registerManifestAcl(ownerId, handler) {
24
+ aclHandlers.set(ownerId, handler)
25
+ }
26
+
27
+ /**
28
+ * @param {string} ownerId 注册方
29
+ * @returns {void}
30
+ */
31
+ export function unregisterManifestAcl(ownerId) {
32
+ aclHandlers.delete(ownerId)
33
+ }
34
+
35
+ /**
36
+ * 本地读授权:仅认 matcher 结果——命中族须注册 ACL handler,否则 deny。
7
37
  * @param {string} replicaUsername 请求 replica
8
38
  * @param {string} ownerEntityHash 文件 owner
9
39
  * @param {import('./normalize.mjs').FileManifest} manifest 清单
10
40
  * @returns {Promise<boolean>} 是否允许读
11
41
  */
12
42
  export async function canReadManifest(replicaUsername, ownerEntityHash, manifest) {
13
- const type = resolveManifestAclType(manifest, ownerEntityHash)
14
- if (type)
15
- return checkManifestAcl(type, { replicaUsername, ownerEntityHash, manifest })
16
-
17
- if (isLogicalEntityHash(ownerEntityHash))
43
+ const ownerId = resolveManifestOwner(manifest, ownerEntityHash)
44
+ if (ownerId) {
45
+ const handler = aclHandlers.get(ownerId)
46
+ if (handler) return handler({ replicaUsername, ownerEntityHash, manifest })
18
47
  return false
19
-
20
- return true
48
+ }
49
+ return !isLogicalEntityHash(ownerEntityHash)
21
50
  }
22
51
 
23
52
  /**
53
+ * 本地写授权:仅认 matcher 结果(写路径无 manifest,只按 ownerEntityHash 判定)。
24
54
  * @param {string} replicaUsername 请求 replica
25
55
  * @param {string} ownerEntityHash 所有者
26
56
  * @param {string} logicalPath 路径
27
57
  * @returns {Promise<boolean>} 是否允许写
28
58
  */
29
59
  export async function canWriteManifestPath(replicaUsername, ownerEntityHash, logicalPath) {
30
- const type = resolveManifestAclType(null, ownerEntityHash)
31
- if (type)
32
- return checkManifestAcl(type, { replicaUsername, ownerEntityHash, manifest: /** @type {any} */ {} }, logicalPath)
33
-
34
- if (isLogicalEntityHash(ownerEntityHash))
60
+ const ownerId = resolveManifestOwner(null, ownerEntityHash)
61
+ if (ownerId) {
62
+ const handler = aclHandlers.get(ownerId)
63
+ if (handler) return handler({ replicaUsername, ownerEntityHash, manifest: {} }, logicalPath)
35
64
  return false
36
-
37
- return isWritableLocalEntity(ownerEntityHash)
65
+ }
66
+ return !isLogicalEntityHash(ownerEntityHash) && isWritableLocalEntity(ownerEntityHash)
38
67
  }
@@ -8,7 +8,6 @@ import { loadFileManifest, saveFileManifest } from '../evfs.mjs'
8
8
  import { beginFedFanoutFetch } from '../fed/fetch_shared.mjs'
9
9
  import { canonicalizeFanoutTargets } from '../fetch_fanout.mjs'
10
10
 
11
- import { resolveManifestAclType } from './acl_registry.mjs'
12
11
  import { normalizeFileManifest } from './normalize.mjs'
13
12
  import {
14
13
  manifestFetchExpectedKey,
@@ -16,6 +15,7 @@ import {
16
15
  registerManifestFetchWait,
17
16
  } from './pending.mjs'
18
17
  import { shouldPreferIncomingPublicManifest } from './public.mjs'
18
+ import { resolveManifestOwner } from './routing.mjs'
19
19
  import { getManifestServicer } from './servicer_registry.mjs'
20
20
 
21
21
  const DEFAULT_MANIFEST_FETCH_TIMEOUT_MS = ms('8s')
@@ -153,9 +153,9 @@ export async function handleIncomingManifestGet(payload, sendResponse, peerId) {
153
153
  return
154
154
  }
155
155
 
156
- // 非 public:ACL servicer 授权后回完整 manifest(读侧解密依赖 meta.dagParts / groupId)。
157
- const aclType = resolveManifestAclType(manifest, ownerEntityHash) || manifest.transferKeyDescriptor.type
158
- const servicer = getManifestServicer(aclType)
156
+ // 非 public:仅认 matcher 命中的族 servicer(无 matcher deny,不按 transferKeyDescriptor.type 兜底路由)。
157
+ const ownerId = resolveManifestOwner(manifest, ownerEntityHash)
158
+ const servicer = getManifestServicer(ownerId)
159
159
  if (!servicer) return
160
160
  const allowed = await servicer({
161
161
  manifest,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Manifest 族路由:matcher 声明「该 manifest / 实体归哪个族(ownerId)所有」。
3
+ * matcher 是唯一路由依据(注册序先命中者);`transferKeyDescriptor.type` 可被多族复用、不参与路由。
4
+ * 族间 matcher 必须互斥,否则按注册顺序先到先得。
5
+ */
6
+
7
+ /** @type {Array<{ ownerId: string, match: (manifest: import('./normalize.mjs').FileManifest | null, ownerEntityHash: string) => boolean }>} */
8
+ const owners = []
9
+
10
+ /**
11
+ * @param {string} ownerId 注册方
12
+ * @param {(manifest: import('./normalize.mjs').FileManifest | null, ownerEntityHash: string) => boolean} match 归属判定
13
+ * @returns {void}
14
+ */
15
+ export function registerManifestOwner(ownerId, match) {
16
+ owners.push({ ownerId, match })
17
+ }
18
+
19
+ /**
20
+ * @param {string} ownerId 注册方
21
+ * @returns {void}
22
+ */
23
+ export function unregisterManifestOwner(ownerId) {
24
+ for (let index = owners.length - 1; index >= 0; index--)
25
+ if (owners[index].ownerId === ownerId) owners.splice(index, 1)
26
+ }
27
+
28
+ /**
29
+ * 解析 manifest 归属族;无命中为 null(非 public 跨节点即 deny)。
30
+ * @param {import('./normalize.mjs').FileManifest | null} manifest manifest(写路径可空)
31
+ * @param {string} ownerEntityHash 所有者
32
+ * @returns {string | null} 族 id
33
+ */
34
+ export function resolveManifestOwner(manifest, ownerEntityHash) {
35
+ for (const { ownerId, match } of owners)
36
+ if (match(manifest, ownerEntityHash)) return ownerId
37
+ return null
38
+ }
@@ -1,51 +1,43 @@
1
1
  /**
2
- * Manifest servicer 注册表:按 ACL 类型路由,决定跨节点是否对外提供非 public manifest。
3
- * 服务端缺省 deny;未注册的类型直接不响应 fed_manifest_get
2
+ * Manifest servicer 注册表:按族(ownerId)路由,决定跨节点是否对外提供非 public manifest。
3
+ * type 可被多族复用;路由只经 registerManifestOwner 的 matcher 唯一命中。服务端缺省 deny。
4
4
  */
5
5
 
6
- /** @type {Map<string, { ownerId: string, handler: (context: ManifestServicerContext) => Promise<boolean> | boolean }>} */
7
- const servicersByType = new Map()
6
+ /** @type {Map<string, (context: ManifestServicerContext) => Promise<boolean> | boolean>} */
7
+ const servicersByOwner = new Map()
8
8
 
9
9
  /**
10
10
  * @typedef {{
11
- * manifest: import('./normalize.mjs').FileManifest,
12
- * ownerEntityHash: string,
13
- * logicalPath: string,
14
- * requesterNodeHash: string,
15
- * peerId: string,
16
- * payload: object,
11
+ * manifest: import('./normalize.mjs').FileManifest,
12
+ * ownerEntityHash: string,
13
+ * logicalPath: string,
14
+ * requesterNodeHash: string,
15
+ * peerId: string,
16
+ * payload: object,
17
17
  * }} ManifestServicerContext
18
18
  */
19
19
 
20
20
  /**
21
- * @param {string} type ACL 类型(如 vault-wrap、file-master-key-wrap,或 resolveManifestAclType 匹配结果)
22
- * @param {string} ownerId 注册方
21
+ * @param {string} ownerId 注册方(族,须与 registerManifestOwner 一致)
23
22
  * @param {(context: ManifestServicerContext) => Promise<boolean> | boolean} handler 授权判定
24
23
  * @returns {void}
25
24
  */
26
- export function registerManifestServicer(type, ownerId, handler) {
27
- const existing = servicersByType.get(type)
28
- if (existing && existing.ownerId !== ownerId)
29
- throw new Error(`manifest servicer for '${type}' already registered by '${existing.ownerId}'`)
30
- servicersByType.set(type, { ownerId, handler })
25
+ export function registerManifestServicer(ownerId, handler) {
26
+ servicersByOwner.set(ownerId, handler)
31
27
  }
32
28
 
33
29
  /**
34
- * @param {string} type ACL 类型
35
30
  * @param {string} ownerId 注册方
36
31
  * @returns {void}
37
32
  */
38
- export function unregisterManifestServicer(type, ownerId) {
39
- const existing = servicersByType.get(type)
40
- if (!existing) return
41
- if (existing.ownerId === ownerId)
42
- servicersByType.delete(type)
33
+ export function unregisterManifestServicer(ownerId) {
34
+ servicersByOwner.delete(ownerId)
43
35
  }
44
36
 
45
37
  /**
46
- * @param {string} type ACL 类型
38
+ * @param {string} ownerId id
47
39
  * @returns {(context: ManifestServicerContext) => Promise<boolean> | boolean | undefined} 已注册 handler
48
40
  */
49
- export function getManifestServicer(type) {
50
- return servicersByType.get(type)?.handler
41
+ export function getManifestServicer(ownerId) {
42
+ return servicersByOwner.get(ownerId)
51
43
  }
@@ -1,3 +1,5 @@
1
+ import { resolveManifestOwner } from './manifest/routing.mjs'
2
+
1
3
  /** @type {Map<string, { getGroupFileMasterKey?: (replicaUsername: string, groupId: string, keyGeneration?: number) => Promise<Buffer | string | null>, getVaultMasterKey?: (replicaUsername: string, entityHash: string) => Promise<Buffer | string | null> }>} */
2
4
  const transferDependenciesByOwner = new Map()
3
5
 
@@ -5,14 +7,13 @@ const transferDependenciesByOwner = new Map()
5
7
  const dagPlaintextReadersByOwner = new Map()
6
8
 
7
9
  /**
8
- * @param {string} ownerId 注册方
10
+ * @param {string} ownerId 注册方(族,须与 registerManifestOwner 一致)
9
11
  * @param {{ getGroupFileMasterKey?: (replicaUsername: string, groupId: string, keyGeneration?: number) => Promise<Buffer | string | null>, getVaultMasterKey?: (replicaUsername: string, entityHash: string) => Promise<Buffer | string | null> }} dependencies 密钥源
10
12
  * @returns {void}
11
13
  */
12
14
  export function registerTransferKeyDependencies(ownerId, dependencies) {
13
- const key = ownerId
14
- const prev = transferDependenciesByOwner.get(key) || {}
15
- transferDependenciesByOwner.set(key, { ...prev, ...dependencies })
15
+ const prev = transferDependenciesByOwner.get(ownerId) || {}
16
+ transferDependenciesByOwner.set(ownerId, { ...prev, ...dependencies })
16
17
  }
17
18
 
18
19
  /**
@@ -24,48 +25,13 @@ export function registerDagManifestPlaintextReader(ownerId, reader) {
24
25
  dagPlaintextReadersByOwner.set(ownerId, reader)
25
26
  }
26
27
 
27
- /** @type {Array<{ ownerId: string, match: (manifest: import('./manifest/normalize.mjs').FileManifest) => boolean }>} */
28
- const manifestOwnerMatchers = []
29
-
30
- /**
31
- * @param {string} ownerId 注册方
32
- * @param {(manifest: import('./manifest/normalize.mjs').FileManifest) => boolean} match manifest 匹配谓词
33
- * @returns {void}
34
- */
35
- export function registerManifestOwnerMatcher(ownerId, match) {
36
- manifestOwnerMatchers.push({ ownerId, match })
37
- }
38
-
39
- /**
40
- * @param {string} ownerId 注册方
41
- * @returns {void}
42
- */
43
- export function unregisterManifestOwnerMatchers(ownerId) {
44
- const id = ownerId
45
- for (let i = manifestOwnerMatchers.length - 1; i >= 0; i--)
46
- if (manifestOwnerMatchers[i].ownerId === id) manifestOwnerMatchers.splice(i, 1)
47
- }
48
-
49
28
  /**
50
29
  * @param {string} ownerId 注册方
51
30
  * @returns {void}
52
31
  */
53
32
  export function unregisterTransferKeyDependencies(ownerId) {
54
- const key = ownerId
55
- transferDependenciesByOwner.delete(key)
56
- dagPlaintextReadersByOwner.delete(key)
57
- unregisterManifestOwnerMatchers(key)
58
- }
59
-
60
- /**
61
- * 从 manifest 推断 transfer key 注册方 id(按 registerManifestOwnerMatcher 注册顺序匹配)。
62
- * @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
63
- * @returns {string | null} 所有者 id
64
- */
65
- export function resolveManifestTransferOwnerId(manifest) {
66
- for (const { ownerId, match } of manifestOwnerMatchers)
67
- if (match(manifest)) return ownerId
68
- return null
33
+ transferDependenciesByOwner.delete(ownerId)
34
+ dagPlaintextReadersByOwner.delete(ownerId)
69
35
  }
70
36
 
71
37
  /**
@@ -74,7 +40,7 @@ export function resolveManifestTransferOwnerId(manifest) {
74
40
  * @returns {{ getGroupFileMasterKey?: (replicaUsername: string, groupId: string, keyGeneration?: number) => Promise<Buffer | string | null>, getVaultMasterKey?: (replicaUsername: string, entityHash: string) => Promise<Buffer | string | null> }} 依赖
75
41
  */
76
42
  export function resolveTransferKeyDependencies(ownerId, manifest) {
77
- const resolved = ownerId || (manifest ? resolveManifestTransferOwnerId(manifest) : null)
43
+ const resolved = ownerId || (manifest ? resolveManifestOwner(manifest, manifest.ownerEntityHash) : null)
78
44
  if (resolved) return transferDependenciesByOwner.get(resolved) || {}
79
45
  return {}
80
46
  }
@@ -85,7 +51,7 @@ export function resolveTransferKeyDependencies(ownerId, manifest) {
85
51
  * @returns {Promise<Buffer | null>} 明文;无 reader 或未命中为 null
86
52
  */
87
53
  export async function readDagManifestPlaintext(replicaUsername, manifest) {
88
- const ownerId = resolveManifestTransferOwnerId(manifest)
54
+ const ownerId = resolveManifestOwner(manifest, manifest.ownerEntityHash)
89
55
  if (ownerId) {
90
56
  const reader = dagPlaintextReadersByOwner.get(ownerId)
91
57
  if (reader)
@@ -93,9 +59,7 @@ export async function readDagManifestPlaintext(replicaUsername, manifest) {
93
59
  const buffer = await reader(replicaUsername, manifest)
94
60
  if (buffer?.length) return buffer
95
61
  }
96
- catch { /* fall through */ }
97
- return null
62
+ catch { /* 读取失败则回落到分块路径 */ }
98
63
  }
99
-
100
64
  return null
101
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -1,84 +0,0 @@
1
- /**
2
- * Manifest ACL 注册表:按 transferKeyDescriptor.type 路由,不硬编码业务概念。
3
- */
4
-
5
- /** @type {Map<string, { ownerId: string, handler: (context: ManifestAclContext, logicalPath?: string) => Promise<boolean> }>} */
6
- const handlersByType = new Map()
7
-
8
- /** @type {Map<string, { ownerId: string, match: (manifest: import('./normalize.mjs').FileManifest | null | undefined, ownerEntityHash: string) => string | null }>} */
9
- const matchersByOwner = new Map()
10
-
11
- /**
12
- * @typedef {{
13
- * replicaUsername: string,
14
- * ownerEntityHash: string,
15
- * manifest: import('./normalize.mjs').FileManifest,
16
- * }} ManifestAclContext
17
- */
18
-
19
- /**
20
- * @param {string} type transferKeyDescriptor.type(如 vault-wrap、file-master-key-wrap)
21
- * @param {string} ownerId 注册方
22
- * @param {(context: ManifestAclContext, logicalPath?: string) => Promise<boolean>} handler ACL 检查
23
- * @returns {void}
24
- */
25
- export function registerManifestAcl(type, ownerId, handler) {
26
- const existing = handlersByType.get(type)
27
- if (existing && existing.ownerId !== ownerId)
28
- throw new Error(`manifest acl handler for '${type}' already registered by '${existing.ownerId}'`)
29
- handlersByType.set(type, { ownerId, handler })
30
- }
31
-
32
- /**
33
- * @param {string} type transferKeyDescriptor.type
34
- * @param {string} ownerId 注册方
35
- * @returns {void}
36
- */
37
- export function unregisterManifestAcl(type, ownerId) {
38
- const existing = handlersByType.get(type)
39
- if (!existing) return
40
- if (existing.ownerId === ownerId)
41
- handlersByType.delete(type)
42
- }
43
-
44
- /**
45
- * @param {string} ownerId 注册方
46
- * @param {(manifest: import('./normalize.mjs').FileManifest | null | undefined, ownerEntityHash: string) => string | null} match ACL 类型匹配
47
- * @returns {void}
48
- */
49
- export function registerManifestAclMatcher(ownerId, match) {
50
- matchersByOwner.set(ownerId, { ownerId, match })
51
- }
52
-
53
- /**
54
- * @param {string} ownerId 注册方
55
- * @returns {void}
56
- */
57
- export function unregisterManifestAclMatcher(ownerId) {
58
- matchersByOwner.delete(ownerId)
59
- }
60
-
61
- /**
62
- * @param {import('./normalize.mjs').FileManifest | null | undefined} manifest manifest
63
- * @param {string} ownerEntityHash 所有者
64
- * @returns {string | null} ACL 类型
65
- */
66
- export function resolveManifestAclType(manifest, ownerEntityHash) {
67
- for (const { match } of matchersByOwner.values()) {
68
- const type = match(manifest, ownerEntityHash)
69
- if (type) return type
70
- }
71
- return null
72
- }
73
-
74
- /**
75
- * @param {string} type transferKeyDescriptor.type
76
- * @param {ManifestAclContext} context 上下文
77
- * @param {string} [logicalPath] 写路径(仅写 ACL 需要)
78
- * @returns {Promise<boolean>} 是否允许(未注册为 false)
79
- */
80
- export async function checkManifestAcl(type, context, logicalPath) {
81
- const row = handlersByType.get(type)
82
- if (!row?.handler) return false
83
- return row.handler(context, logicalPath)
84
- }