@qlzt/dsh-api-remote-access 0.0.0-stage → 0.2.1-rc.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/api/remote-access/README.md
5
+ /:
6
+ en: bbd88553f12d4f3d
7
+ zh: c5fa4ac3f9746170
8
+ /deepseek-ai-dsh-api-remote-access:
9
+ en: 15713a51f827c9c1
10
+ zh: a1349a5f83efc82c
11
+ /deepseek-ai-dsh-api-remote-access/summary:
12
+ en: ee81bdee09f42c9b
13
+ zh: 41c6864e59c24519
14
+ /deepseek-ai-dsh-api-remote-access/table-of-contents:
15
+ en: a18ecc232e3c3933
16
+ zh: b8e43580403f6d77
17
+ /deepseek-ai-dsh-api-remote-access/host-service-remoteaccessservice-ctx-key-remoteaccess:
18
+ en: 9175835ea5c5bff1
19
+ zh: 0e33b266fdac3d13
20
+ /deepseek-ai-dsh-api-remote-access/client-face:
21
+ en: 1db01235b6af84fa
22
+ zh: c0ced511cc1841a0
23
+ /deepseek-ai-dsh-api-remote-access/model-experience:
24
+ en: 8b07250cecacd3aa
25
+ zh: d28ab93980bdcdad
26
+ /deepseek-ai-dsh-api-remote-access/model-experience/kv-cache-effect:
27
+ en: 60835eca1cee4e0e
28
+ zh: 3b5579ceccd5b5e7
29
+ /deepseek-ai-dsh-api-remote-access/known-limitations-and-deferred-work:
30
+ en: 66b483bf5144ac46
31
+ zh: 84bde5e0b1af270b
32
+ /deepseek-ai-dsh-api-remote-access/known-limitations-and-deferred-work/dev-note:
33
+ en: e67f633dc6fe7773
34
+ zh: e88aeb617ffb74ea
package/README.md CHANGED
@@ -1,3 +1,97 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Method-level Remote access control for non-owner Peers: role-based policy decisions, list filtering, and the per-Peer access snapshot."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @deepseek-ai/dsh-api-remote-access
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Decides which Remote methods a non-owner Peer may call and which forwarded events it may receive. Business packages define roles, each an action set over one resource kind, and register one policy per non-owner principal kind that binds roles to resources. The [API Gateway](../gateway/README.md) resolves the resources a call or event touches and asks this package before it resolves lookups or decodes arguments. List methods filter items through `allows()`, and each Peer follows its own access snapshot, so the Client can hide entries the Host would refuse. The [Peer principals Agent Note](../../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.md) owns the design.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Host service: `RemoteAccessService` (ctx key: `remoteAccess`)](#host-service)
17
+ - [Client face](#client-face)
18
+ - [Model Experience](#model-experience)
19
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
20
+ - [Dev Note](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="host-service"></a>
25
+ ## Host service: `RemoteAccessService` (ctx key: `remoteAccess`)
26
+
27
+ Every composition that mounts [API Gateway](../gateway/README.md) mounts this service: the Gateway injects `remoteAccess` and does not start without it. [`bundle/base`](../../bundle/base/cordis.patch.yml) loads it beside `typert-gateway`. The owner (`{ kind: 'owner' }`) is never checked; every method below concerns non-owner principals.
28
+
29
+ | Member | Contract |
30
+ |---|---|
31
+ | `registerPolicy(kind, policy)` | Registers the `RemoteAccessPolicy` of one non-owner principal kind; its `bindings()` and `explain()` receive principals typed by that kind. A second policy for the same kind throws. The registration is a Cordis effect of the caller's fiber; registration and removal recompute open snapshots. |
32
+ | `invalidate(principal?)` | Reports that bindings or condition answers for one principal, or for every principal, may have changed. A business package calls it on every grant, revocation, mode change, and expiry. |
33
+ | `decide(request)` | Gateway-only. `request` is `{ principal, action, resources, args? }`: the caller or recipient, the endpoint `ns/method` or `event:<name>`, the valid resources the Gateway resolved, and a call's arguments validated against their Typert-compiled types, with lookup parameters as wire identities. Returns `{ kind: 'allow' }` or `{ kind: 'deny', reason }`; the Gateway throws `remote-access/denied` with details `{ action, reason }` for a denied call and drops a denied event without building an error. The owner and `remoteAccess/follow` are always allowed. |
34
+ | `allows(resource)` | Reads the invocation of the Context that accessed the service, so a list method calls `this.ctx.remoteAccess.allows(resource)` for each item. Decides the call's endpoint action on that resource without arguments, reading the bindings again on every check. True for the owner and outside a Remote call. |
35
+ | `snapshotFor(principal)` | The baseline a page opens with: `{ type: 'baseline', owner: true }` for the owner, otherwise the principal's current grants. |
36
+ | `follow(signal)` | Remote stream `remoteAccess/follow`: one `baseline`, then `upsert` and `remove` frames. The owner receives only `{ type: 'baseline', owner: true }`. The stream ends when `signal` aborts or the service unloads, so a reloaded service starts every reader from a fresh baseline. |
37
+
38
+ A role `{ name, resource, actions }` is a plain value naming an action set over one resource kind, `global` or a kind declared in `TypertResourceMap`. A binding `{ role, resource, condition? }` grants the role on one resource of the role's kind. A policy answers `bindings(principal)` and an optional `explain(request)` synchronously from state it holds in memory. A request is allowed only when, on every resolved resource, some binding names that resource, its role lists the action, and it has no condition or its condition allows. Resources are independent: a grant on `global` implies nothing on any other resource, and no resource implies another. A condition receives `{ action, resource, args? }` and runs only for a binding whose resource and role match; `args` is absent for events, snapshots, and list filtering. `bindings()` runs once per `decide()`, once per `allows()` check, and once per snapshot computation; it is never cached across decisions, so time-dependent state applies to the next call.
39
+
40
+ The Gateway resolves resources from `@Remote` and `@RemoteScope` `resources` readers, lookup and Host Context adapter `resource` declarations, and the default fields in `typert.resources`, and the [API Gateway](../gateway/README.md) owns that order; this package reads no argument field. A call that names no resource touches the `global` resource alone, so only a `global` role can allow it; putting an action in a `global` role lets the principal call it on any resource the call does not name. The Gateway refuses a call whose resources are invalid with `resource-invalid` without asking `decide()`.
41
+
42
+ When `bindings()`, a condition, or `explain()` throws, the decision is refused with `policy-failed`; in a snapshot, a throwing `bindings()` leaves the principal no grant and a throwing condition excludes its action. A faulty rule therefore never breaks the shared `$events` source, the snapshot stream, or page rendering. The service logger reports the first failure of each policy after each `invalidate()` and each policy registration or removal; later failures of that policy stay silent until then.
43
+
44
+ A forwarded event is decided as the action `event:<name>` on the resources its forwarding entry's readers name, or on `global` when the entry declares none, and reaches a non-owner whole or not at all; its payload is never trimmed. An event carries every resource it declares, so a reader that returns `undefined` makes the event's resources invalid, and the Gateway delivers it to no non-owner without asking `decide()`. An event names at most one resource, so collection changes travel on follow streams (`session/follow`, `session/control`, `workspace/follow`), whose methods decide each item inline through `allows()`.
45
+
46
+ The service adds a `global` row named `REMOTE_ACCESS_SNAPSHOT_KEY` (`__DSH_REMOTE_ACCESS__`) holding the owner baseline `{ type: 'baseline', owner: true }` to every index the webServer renders through `webserver/index-inject`. A business package that renders a non-owner page, such as a liveshare viewer page rendered on the owner Host, places an assignment of that global to `ctx.remoteAccess.snapshotFor(principal)` after every index injection row, for example a `global` row appended to the end of `ctx.webServer.collectIndexInjections()`. The later assignment replaces the owner baseline, and the Client entry reads the global when it mounts, so the page opens with the viewer's grants instead of the owner's.
47
+
48
+ | `details.reason` of `remote-access/denied` | Cause |
49
+ |---|---|
50
+ | `resource-invalid` | Given by the Gateway before `decide()`: a resource reader threw or returned an empty list, or a resource id, including a Context identity, is not a non-empty string. |
51
+ | `no-allow` | The principal kind has no policy, or no binding allows the action on some resource and neither a condition nor `explain()` names a reason. |
52
+ | `policy-failed` | `bindings()`, a condition, or `explain()` threw; the failure is logged as described above. |
53
+ | condition or `explain()` reason | On the first resource no binding allows, the first refusing condition of a matching binding names the reason; without one, `explain(request)` does. |
54
+
55
+ Snapshot grants list, per resource the principal's bindings name, the sorted endpoint actions of those bindings' roles that `decide()` allows there without call arguments, so the snapshot agrees with `decide()` for the same principal, action, and resource; a resource with no such action gets no grant, and the stream pushes `remove` once its allowed set becomes empty. `event:` actions never enter the snapshot. Snapshots are recomputed only after `invalidate()` and policy registration or removal.
56
+
57
+ <a id="client-face"></a>
58
+ ## Client face
59
+
60
+ The Client entry reads the baseline the index injected under `REMOTE_ACCESS_SNAPSHOT_KEY` synchronously, mounts the generated `remoteAccess` contribution itself through `mountAccess(ctx, contribution, initial)`, follows the snapshot stream, and provides:
61
+
62
+ - the Client service `ctx.remoteAccess.source(kind, id, action)`, an identity-stable boolean source per `(kind, id, action)` for one resource of a declared kind;
63
+ - the standard Hook `useRemoteAccess(action)`, which answers on the `global` resource.
64
+
65
+ Domain UI packages publish their resource Hooks over `source()`: [ui-session](../../client/ui-session/README.md) provides `useSessionAccess(sessionId, action)` and the Session-scope `useAccess(action)` on Session resources, a different name from the `global` `useRemoteAccess`, and [ui-workspace](../../client/ui-workspace/README.md) provides `useWorkspaceAccess(workspaceId, action)` on Workspace resources. A Hook's `action` is a `RemoteEndpoint`, an endpoint some Client Remote namespace declares. A non-owner answer is whether that resource's grant lists the action and never falls back to the `global` answer, so a resource without a grant, or whose grant lists no action, answers false. Every answer is true for the owner; a non-owner page without an injected baseline, and a non-owner page whose stream fails terminally, answers false; the terminal failure is reported with its error, for a non-owner with the advice to reload the page and for the owner with the note that its answers are unaffected; a reconnect keeps the last snapshot. A malformed injected baseline fails the mount. Hooks never issue requests, and the Host still checks every call. [`dsh-client-test-runtime`](../../test-support/client-runtime/README.md) replaces this row with allow-all access in Client specs.
66
+
67
+ <a id="model-experience"></a>
68
+ ## Model Experience
69
+
70
+ None, as this package decides Remote access for Client callers and registers nothing model-facing.
71
+
72
+ #### KV Cache effect
73
+
74
+ No direct effect.
75
+
76
+ ## Known Limitations and Deferred Work
77
+
78
+ <a id="known-limitations-and-deferred-work"></a>
79
+
80
+ - Snapshot and list-filtering decisions carry no call arguments, so a condition that refines its answer from `args` can refuse a call that the snapshot shows as allowed.
81
+ - Resource readers and automatic resolution must cover every resource a method touches; a resource the call does not name is not checked, so a principal granted on the named resources reaches it.
82
+ - Default resource resolution matches wire field names; adding a field with a registered kind's name to a method adds that kind's resource to the call.
83
+ - Exact Connection Fetch routes do not pass through the Gateway; the shared Fetch handler refuses them to every non-owner Peer with 403 until each route declares its own access rule.
84
+ - Interaction participation (answering forwarded waterfalls) is owner-only: non-owner `$events` streams receive only the broadcast frames whose `event:<name>` action the policy allows on the event's resources.
85
+ - `api-session/added` carries the whole `SessionSummary`, so a non-owner allowed to receive it also learns its `parentSessionId`.
86
+ - A granted Session's cwd and tool paths already contain the owner's absolute paths, so the `$events` ready frame gives a non-owner the full `host`, including `home`; the Gateway does not hide `home` separately.
87
+ - `workspace/follow` notices a Workspace whose visibility a policy revoked only when that Workspace or the Workspace order next changes, and then replaces the non-owner's baseline; an access change alone sends no frame.
88
+
89
+ <a id="dev-note"></a>
90
+ ### Dev Note
91
+
92
+ <details>
93
+ <summary>Working context for maintainers — click to expand</summary>
94
+
95
+ None.
96
+
97
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,97 @@
1
+ ---
2
+ description: "非 owner Peer 的 Remote 方法级访问控制:基于角色的 policy 判定、列表过滤与每个 Peer 的访问快照。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-api-remote-access
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 决定非 owner Peer 可以调用哪些 Remote 方法、可以收到哪些转发事件。业务包定义 role(限定一种资源种类的 action 集合),并为每种非 owner principal kind 注册一个 policy,把 role 绑定到资源上。[API Gateway](../gateway/README.zh.md) 解析一次调用或一个事件访问的资源,并在解析 lookup、解码参数之前询问本包。列表方法经 `allows()` 逐项过滤;每个 Peer 订阅自己的访问快照,Client 据此隐藏 Host 会拒绝的入口。设计由 [Peer 身份属性 Agent Note](../../../.agents/notes/implemented/architecture/2026-10-12-peer-principal-and-remote-access.zh.md) 负责。
13
+
14
+ ## 目录
15
+
16
+ - [Host 服务:`RemoteAccessService`(ctx key:`remoteAccess`)](#host-service)
17
+ - [Client 面](#client-face)
18
+ - [模型体验](#model-experience)
19
+ - [已知限制与暂缓事项](#known-limitations-and-deferred-work)
20
+ - [开发备注](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="host-service"></a>
25
+ ## Host 服务:`RemoteAccessService`(ctx key:`remoteAccess`)
26
+
27
+ 挂载 [API Gateway](../gateway/README.zh.md) 的组合都要挂载本服务:Gateway 注入 `remoteAccess`,没有它就不启动。[`bundle/base`](../../bundle/base/cordis.patch.yml) 紧随 `typert-gateway` 装配它。owner(`{ kind: 'owner' }`)从不受检查;下列成员都只针对非 owner principal。
28
+
29
+ | 成员 | 契约 |
30
+ |---|---|
31
+ | `registerPolicy(kind, policy)` | 为一种非 owner principal kind 注册 `RemoteAccessPolicy`;其 `bindings()` 与 `explain()` 收到的 principal 按该 kind 定型。同一 kind 再注册一个 policy 即抛错。登记是调用方 fiber 的 Cordis effect;注册与撤回都会重新计算已打开的快照。 |
32
+ | `invalidate(principal?)` | 报告某个 principal(省略时为所有 principal)的 binding 或 condition 结论可能已变化。业务包在每次授权、撤权、模式切换与过期时调用。 |
33
+ | `decide(request)` | 仅供 Gateway 使用。`request` 为 `{ principal, action, resources, args? }`:调用方或接收方、端点 `ns/method` 或 `event:<name>`、Gateway 解析出的合法资源,以及调用中按 Typert 编译类型校验后的参数(lookup 参数为 wire 身份)。返回 `{ kind: 'allow' }` 或 `{ kind: 'deny', reason }`;调用被拒时由 Gateway 抛出 details 为 `{ action, reason }` 的 `remote-access/denied`,事件被拒时由 Gateway 直接丢弃、不构造错误。owner 与 `remoteAccess/follow` 恒放行。 |
34
+ | `allows(resource)` | 读访问本服务的 Context 上的 invocation,因此列表方法对每一项调用 `this.ctx.remoteAccess.allows(resource)`。不带参数判定该调用的端点 action 在该资源上是否被允许,每次判定都重新读取 binding。owner 与 Remote 调用之外恒为 true。 |
35
+ | `snapshotFor(principal)` | 页面打开时的 baseline:owner 为 `{ type: 'baseline', owner: true }`,否则为该 principal 当前的 grant。 |
36
+ | `follow(signal)` | Remote stream `remoteAccess/follow`:一个 `baseline`,之后是 `upsert` 与 `remove` 帧。owner 只收到 `{ type: 'baseline', owner: true }`。`signal` abort 或服务卸载时流结束,因此重载后的服务从新的 baseline 回答每个读者。 |
37
+
38
+ role `{ name, resource, actions }` 是普通值,表示限定一种资源种类(`global` 或在 `TypertResourceMap` 中声明的种类)的 action 集合。binding `{ role, resource, condition? }` 在该 role 种类的一个资源上授予该 role。policy 根据预先载入内存的状态同步回答 `bindings(principal)` 与可选的 `explain(request)`。只有在每个已解析资源上都有某个 binding 指向该资源、其 role 列出该 action、且没有 condition 或 condition 放行时,请求才被放行。资源之间相互独立:`global` 上的授权不蕴含任何其他资源上的授权,任何资源也不蕴含另一个资源。condition 收到 `{ action, resource, args? }`,只对资源与 role 都匹配的 binding 调用;事件、快照与列表过滤时没有 `args`。`bindings()` 在每次 `decide()`、每次 `allows()` 判定与每次快照计算中各调用一次,不跨判定缓存,因此依赖时间的状态对下一次调用即时生效。
39
+
40
+ Gateway 依次从 `@Remote` 与 `@RemoteScope` 的 `resources` 读取函数、lookup 与 Host Context adapter 的 `resource` 声明、`typert.resources` 中的默认字段解析资源,解析顺序由 [API Gateway](../gateway/README.zh.md) 负责;本包不读取任何参数字段。不指明任何资源的调用只访问 `global` 资源,因此只有 `global` 级 role 能放行;把某个 action 放进 `global` 级 role,等于允许该 principal 在调用未指明的任何资源上调用它。资源非法的调用由 Gateway 以 `resource-invalid` 拒绝,不询问 `decide()`。
41
+
42
+ `bindings()`、condition 或 `explain()` 抛错时,本次判定以 `policy-failed` 拒绝;计算快照时,`bindings()` 抛错即该 principal 没有 grant,condition 抛错即该 action 不放行。有问题的规则因此不会破坏共享的 `$events` 事件源、快照流或页面渲染。每次 `invalidate()` 以及每次 policy 注册或撤回之后,服务日志对每个 policy 只报告第一次失败;此后该 policy 的失败不再记录,直到下一次上述变化。
43
+
44
+ 转发事件按其转发条目的读取函数指明的资源(条目未声明时为 `global`)判定 action `event:<name>`,对非 owner 整条送达或整条不送,从不裁剪 payload。事件携带其声明的每个资源,因此读取函数返回 `undefined` 时该事件的资源非法,Gateway 不询问 `decide()`,不送达任何非 owner。一个事件至多指明一个资源,因此集合类变化走 follow 类流(`session/follow`、`session/control`、`workspace/follow`),由这些方法内联经 `allows()` 逐项判定。
45
+
46
+ 本服务经 `webserver/index-inject` 为 webServer 渲染的每个 index 加入名为 `REMOTE_ACCESS_SNAPSHOT_KEY`(`__DSH_REMOTE_ACCESS__`)的 `global` 行,值为 owner baseline `{ type: 'baseline', owner: true }`。渲染非 owner 页面的业务包(例如在 owner Host 上渲染 liveshare 读者页)在所有 index 注入行之后放置一条把该全局赋值为 `ctx.remoteAccess.snapshotFor(principal)` 的语句,例如追加到 `ctx.webServer.collectIndexInjections()` 末尾的一条 `global` 行。后写的赋值覆盖 owner baseline,Client 入口在挂载时读取该全局,因此页面以读者的 grant 而非 owner 的快照打开。
47
+
48
+ | `remote-access/denied` 的 `details.reason` | 原因 |
49
+ |---|---|
50
+ | `resource-invalid` | 由 Gateway 在 `decide()` 之前给出:资源读取函数抛错或返回空列表,或某个资源 id(含 Context 身份)不是非空字符串。 |
51
+ | `no-allow` | 该 principal kind 没有 policy,或在某个资源上没有 binding 放行该 action,且 condition 与 `explain()` 都没有给出原因。 |
52
+ | `policy-failed` | `bindings()`、condition 或 `explain()` 抛错;该失败按上文规则记录日志。 |
53
+ | condition 或 `explain()` 的原因 | 在第一个没有 binding 放行的资源上,取匹配 binding 中第一个拒绝的 condition 的原因;没有时取 `explain(request)` 的返回值。 |
54
+
55
+ 快照 grant 按该 principal 的 binding 所指的每个资源,列出这些 binding 的 role 中、经 `decide()` 在该资源上不带调用参数放行的端点 action(已排序),因此在同一 principal、action 与资源上快照与 `decide()` 一致;没有此类 action 的资源不出 grant,放行集合变空时推送 `remove`。`event:` action 不进入快照。快照只在 `invalidate()` 与 policy 注册或撤回之后重算。
56
+
57
+ <a id="client-face"></a>
58
+ ## Client 面
59
+
60
+ Client 入口同步读取 index 注入在 `REMOTE_ACCESS_SNAPSHOT_KEY` 下的 baseline,经 `mountAccess(ctx, contribution, initial)` 自行挂载生成的 `remoteAccess` contribution,订阅快照流,并提供:
61
+
62
+ - Client 服务 `ctx.remoteAccess.source(kind, id, action)`,为已声明种类的某个资源上的每个 `(kind, id, action)` 提供一个身份稳定的布尔 source;
63
+ - 标准 Hook `useRemoteAccess(action)`,回答 `global` 资源上的访问。
64
+
65
+ 领域 UI 包在 `source()` 之上发布各自资源的 Hook:[ui-session](../../client/ui-session/README.zh.md) 提供 Session 资源上的 `useSessionAccess(sessionId, action)` 与会话作用域的 `useAccess(action)`(与 `global` 资源上的 `useRemoteAccess` 不同名),[ui-workspace](../../client/ui-workspace/README.zh.md) 提供 Workspace 资源上的 `useWorkspaceAccess(workspaceId, action)`。Hook 的 `action` 类型为 `RemoteEndpoint`,即某个 Client Remote namespace 声明的端点。非 owner 的答案是该资源的 grant 是否列出该 action,不回退 `global` 答案,因此没有 grant 或 grant 未列出任何 action 的资源回答 false。owner 的所有答案为 true;没有注入 baseline 的非 owner 页面、快照流终止失败的非 owner 页面,所有答案为 false;终止失败会连同错误一并报告,非 owner 附“刷新页面重连”的提示,owner 附“答案不受影响”的说明;重连期间保留上次快照。注入的 baseline 格式错误时挂载失败。Hook 从不发请求,Host 仍检查每次调用。[`dsh-client-test-runtime`](../../test-support/client-runtime/README.zh.md) 在 Client spec 中以全允许的访问替换本行。
66
+
67
+ <a id="model-experience"></a>
68
+ ## 模型体验
69
+
70
+ 无:本包为 Client 调用方判别 Remote 访问,不注册任何面向模型的内容。
71
+
72
+ #### KV Cache 影响
73
+
74
+ 无直接影响。
75
+
76
+ ## 已知限制与暂缓事项
77
+
78
+ <a id="known-limitations-and-deferred-work"></a>
79
+
80
+ - 快照与列表过滤的判定不带调用参数,因此按 `args` 细化结论的 condition 可能拒绝快照显示为允许的调用。
81
+ - 资源读取函数与自动解析必须覆盖方法访问的全部资源;调用未指明的资源不受检查,持有已指明资源授权的 principal 可以访问它。
82
+ - 默认资源解析按 wire 字段名匹配;给方法新增与已登记种类同名的字段,会让调用多访问该种类的资源。
83
+ - exact Connection Fetch route 不经过 Gateway;在各路由声明自己的访问规则之前,共享 Fetch handler 对所有非 owner Peer 返回 403。
84
+ - 交互参与(回答转发的 waterfall)仅限 owner:非 owner 的 `$events` 流只收到 policy 在事件资源上放行其 `event:<name>` action 的广播帧。
85
+ - `api-session/added` 携带完整的 `SessionSummary`,因此被允许接收它的非 owner 也会得知其 `parentSessionId`。
86
+ - 被授权 Session 的 cwd 与工具路径本就包含 owner 的绝对路径,因此 `$events` ready 帧向非 owner 提供完整的 `host`(含 `home`);Gateway 不单独隐藏 `home`。
87
+ - policy 撤销某个 Workspace 的可见性后,`workspace/follow` 只在该 Workspace 或 Workspace 顺序下一次变化时察觉并重推非 owner 的 baseline;仅访问变化不会发送帧。
88
+
89
+ <a id="dev-note"></a>
90
+ ### 开发备注
91
+
92
+ <details>
93
+ <summary>维护者工作上下文——点击展开</summary>
94
+
95
+ 无。
96
+
97
+ </details>