Repository navigation
feat(node): hand model grants to plugin-owned adapters - #708
Conversation
2fa880d to
fdcb679
Compare
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:fdcb67973c23371147ea3486f16ff0bc849602a8(base 9774456,对照 upstream/main 42f224a)。结论:Request changes。
协议、Controller 派发门禁、grant 与 adapter 上下文的交接本身是对的,CI 8/8 通过。但这次替代路线把 #705 里与已批准 ADR 直接相关的三项保护丢了:两项是 ADR 明文要求,第三项会把 live token 交给任意未声明的插件。补齐后才能算实现 D1–D4/D6。
阻塞 1:启动时没有核对安装包与声明(ADR D2 第 1 步)
证据:
apps/ora-node/src/session/driver.rs:107-116只取安装租约和installed(plugin, version);runs_package()(:238-243)只比较包根和 contributes,没有读安装包的声明,也没有比较冻结的包摘要/指纹/Settings。crates/node-protocol/src/domain/model_adapter.rs:77-113只做格式检查:两个 digest 是 64 位小写 hex、Settings 是 ≤64 KiB 的 JSON 对象、plugin id/version 等于会话里的值、adapter_version == 1。package_sha256与declaration_fingerprint只被转发(apps/ora-node/src/model_proxy/adapter.rs:49,51);adapter 的AgentModelApplied只是把 fingerprint 原样回显(crates/agent-runtime/src/plugin_agent/control.rs:119-125),证明它“收到过这个字符串”,不证明安装的声明哈希等于它。settings_json在 Node 上从未按声明校验。
ADR D2 第 1 步是“Node 核对执行指定的安装包及声明关联,取得安装使用租约…”,D2 还有“身份/声明/契约不匹配必须在首次模型调用前拒绝”;personal-agent-configurations D3 也写明“能力检查并不替代实际 Node 安装和启动时的精确身份检查”。#705 的 plugins/model_configuration.rs 用 receipt、读取 assets/config.json、指纹/协议/Settings 校验实现了这一条,这次没有替代实现。
后果:Cloud 在准入时校验过声明,但从准入到会话启动之间,同版本目录被重新发布或替换时,冻结摘要和实际运行的字节不再对应;冻结的声明指纹/Settings 也可能属于另一份声明。这类情况现在没有任何一层会拒绝,而 Node 会把 live grant 和 token 交给这个包。测试里也没有“替换包后必须拒绝”的用例(#705 有)。
最小修法:恢复 #705 的 receipt 方案。在 prepare_and_converse 拿到租约后、ModelAccess::open 之前,读取安装的声明并与冻结配置比较 target/schema/fingerprint/adapter contract,要求冻结 protocol ∈ 声明 protocols,并按声明校验 Settings;失败用有界错误码(如 agent_model_configuration_invalid / agent_model_protocol_mismatch)在首次请求前结束。
阻塞 2:插件进程继承 Node 的环境(ADR D4)
证据:插件和它经宿主拉起的 CLI 都用 TokioProcessSpawner(crates/plugin-lifecycle/src/runtime.rs:70,79,93;apps/ora-node/src/session/workload.rs:97-124),该 spawner 只在显式 spec.env 上追加,不清空继承(crates/process/src/tokio_process.rs:64-83)。apps/ora-node/src/session/host.rs:59-65 的注释还明确写着 “The rest of the host environment (model credentials, proxy settings) still reaches the agent”。这次只加了 HOME/XDG/CA(apps/ora-node/src/model_proxy.rs:379-389)。
ADR D4 明文:“不得读取宿主登录文件或从继承环境取得原生 provider Key”;“仅换 HOME 不足以证明路由固定。adapter 必须处理该 CLI 的专用配置目录变量、项目设置、环境和其他高优先级设置……不支持可靠覆盖控制的固定版本拒绝进入此能力。”插件进程持有 --allow-env(crates/plugin-lifecycle/src/permissions.rs),Node 环境里的 ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL/OPENAI_*/CLAUDE_CONFIG_DIR/OPENCODE_CONFIG 以及部署凭据会一并到达第三方插件及其 CLI。CLI 用环境变量覆盖 provider 配置时,请求会绕过平台网关直连上游,网关的逐次校验根本看不到它——这正是 D4 要防的“改向另一个上游/登录态”。
最小修法:把 #705 的 ProcessEnvironment::Empty + ModelProcessIsolation 拿回来,agent_configuration.is_some() 时使用 Empty spawner,只显式补 PATH/LANG/LC_ALL/TMPDIR 和本次会话变量;加一个真实子进程测试,断言继承变量不存在(#705 的 crates/process/tests/environment.rs 就是这种证据)。
阻塞 3:未声明插件的模型绑定无条件落到 OpenCode 旧路线(ADR 兼容与落地)
证据:apps/ora-node/src/session/driver.rs:156-161 用 map_or(ModelRoute::LegacyOpenCode, ModelRoute::Adapter);任何没有冻结配置的插件(只要 Cloud 给了 binding)都会走 model_proxy/opencode.rs,被注入 OPENCODE_CONFIG_CONTENT、ORA_MODEL_ACCESS_TOKEN(本次 grant 的 live token)和 OPENCODE_CONFIG_DIR。
ADR 兼容与落地:“旧 OpenCode 数据/默认选择的兼容过渡只能留在明确的旧路线,不能成为新插件缺能力时的回退。” 现在 Cloud 确实只为 OpenCode 建无配置 binding(internal/core/model_run_binding.go),暂时不可达;但 Node 协议本身允许,Cloud 一侧的遗漏或未来接入就会把一个与 OpenCode 无关的插件接上这条路线,并把 token 交给它。这种兜底不该建立在“Cloud 不会这么发”的假设上。
最小修法:把旧路线限定为明确的插件(official/ora-space.opencode,或由 Cloud 显式标记 legacy);其它插件有 binding 没配置就拒绝。补一条测试断言不发起 grant、不启动进程。
should-fix
- 失败分类:
crates/agent-runtime/src/plugin_agent/control.rs的 adapter start 错误仍全部收敛为Failed("agent_model_adapter_failed");#705 区分AGENT_NOT_INSTALLED/AGENT_UNUSABLE(-32001/-32002)的 10 行改动没带过来。第一次在没装 CLI 的机器上跑 Claude,会从“Unavailable、安静重试”变成累计 3 次失败并进入 Failing/重启电路。建议补回;若留作后续,请在 PR 描述和运行 issue 写明承接 issue。 - target 集合:
apps/ora-node/src/session.rs:170-184对任何*-unknown-linux-gnu宣告 adapter 能力,而 cloud#106 的internal/core/agent_adapter_capability.go只接受 x86_64/aarch64。其它 GNU target 的 Node 会在登记时被 Cloud 以invalid_node_report拒绝整条登记,而不是不宣告能力。两边取齐。 - Settings 上限:Node 64 KiB(
crates/node-protocol/src/domain/model_adapter.rs:20)与 cloud#106 回执的 256 KiB 不一致;请确认声明编译器的上限小于 64 KiB,否则同一份冻结配置会在 Node 被拒。 third_party/cloud:现在钉的是 cloud#106 的 head1be0610,而 cloud#106 需要 rebase 并顺延迁移号;合并前按 cloud main 重新钉。
已核对、未验证
已核对:HelloAccepted.model_adapter 只在 model_proxy 时出现、旧 Node 省略;FrozenAgentConfiguration::validate 要求会话有 binding、Settings 是对象;Controller 在 claim 和 retransmit 两处都按 target/contract/protocol 匹配,不匹配保持排队;model_grant_mismatch 在生成任何 CLI 状态前撤销;结束/失败/进程退出都收敛到同一个 close();echo fixture 的真实进程测试覆盖 token 不在 env/history/Thread、未确认不收到 prompt、结束后撤销授权。
未验证:本地没有重跑 cargo(Windows 环境受 rsproxy 代理影响),门禁以 fdcb679 的 8/8 CI 为准;真实 Claude/OpenCode 验收仍由本运行后续承接。此结论只针对 fdcb679。
…figuration Moves third_party/cloud to the cloud#106 head (1be0610) that adds the frozen AgentModelConfiguration to AgentSessionSpec and the Node model adapter capabilities to node registration, and regenerates the client.
…ability A configured session names the exact non-secret configuration Cloud froze at admission next to its model binding; it must describe the session's own plugin release, a supported adapter contract and one JSON object of Settings. HelloAccepted advertises the Node's adapter target, contract and protocols, only beside model_proxy.
The Controller maps Cloud's frozen configuration into the Node start, reports the handshake's adapter facts at registration, and claims or retransmits a configured session only to an incarnation whose target, contract and protocols admit it; other work stays queued in Cloud.
A configured session no longer gets Node-written OpenCode configuration. The Node checks that the grant serves exactly the frozen protocol and model, gives the plugin a private home and the public CA, and passes the token only in the agent/start model context; the runtime refuses ACP until the adapter confirms the configuration, and every failure path revokes the grant. The echo fixture acts as a minimal adapter, and the gateway fixture is shared with the session integration tests.
An Agent that keeps using the personal model default and declares no Settings, such as a declared OpenCode, is frozen with configuration revision 0. Refusing it left the session queued in Cloud until its startup budget failed it with agent_start_failed.
…claration Expose the Settings validation the editor uses for a compiled Agent declaration, and let a host compile the declaration an installed package actually carries, so a configured session can re-check both at start.
The installer records a receipt of the archive digest and target it verified, outside the package, and clears it before republishing. A configured session then re-checks under its package lease, before requesting a grant, that the receipt matches the frozen package, that the installed declaration's fingerprint, schema and adapter contract match, that the frozen Settings validate against it and that it offers the frozen protocol (ADR D2 step 1). A failure ends the session with agent_model_configuration_invalid or agent_model_protocol_mismatch and starts no plugin process.
A configured session's plugin and every process the host spawns for it now clear inheritance and receive only PATH, LANG, LC_ALL, TMPDIR and the session's own variables, so provider keys, base URLs and CLI configuration in the Node's environment never reach the adapter or its CLI (ADR D4). Sessions without a frozen configuration keep the existing inherited environment. The echo fixture reports whether it still sees the test runner's own variables.
A model binding without a frozen configuration used to fall back to the legacy route for any plugin, injecting the OpenCode configuration and the live grant token. Only official/ora-space.opencode may take that route now; any other plugin ends with agent_model_configuration_required before a grant is requested or a process starts.
A configured agent/start failure still drops the plugin's message, but -32001 and -32002 again map to AgentNotInstalled and AgentUnusable, so a host without the CLI keeps retrying quietly instead of tripping the restart circuit.
Accept frozen Settings up to the 256 KiB Cloud stores and the plugin SDK accepts, and advertise the adapter capability only on the two Linux targets Cloud admits, so another target never gets its whole registration refused.
cloud#106 was rebased onto Cloud main without changing its proto; the pin follows the new head so the commit stays fetchable.
fdcb679 to
c91935c
Compare
|
Thanks for the review. All three blockers and the should-fix items are addressed on
Should-fix:
Locally: |
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:c91935c47ebce6d332d5acea2d02753062673e59(base 42f224a,15 commits,69 files,+2219/−319)。结论:Approve(无阻塞)。
三个上一轮阻塞在代码和可验证测试上都已真正落地,四个 should-fix 也全部落实。
三个阻塞
- 启动时核对安装包与声明(D2 第 1 步)——已修复。 租约先于一切(
apps/ora-node/src/session/driver.rs:122),check_configuration在 restore、placement 和任何 grant 之前(driver.rs:134-138),失败在首次模型调用前以agent_model_configuration_invalid/agent_model_protocol_mismatch结束(apps/ora-node/src/session/ports.rs:135-150)。检查内容(apps/ora-node/src/plugins/model_configuration.rs:104-151):target 等于本机 target、receipt 为精确{sha256,target}、声明指纹/schema/adapter contract 匹配、Settings 按已装声明校验、冻结 protocol ∈ 声明 protocols。receipt 在包内容之外、先清后写(apps/ora-node/src/plugins/catalog.rs:100-130),声明包缺 receipt 时强制重装(:87-91)。单测覆盖缺 receipt/错 digest/错指纹/错误 Settings/错协议/退役 receipt;会话级测试断言无 grant、无进程(apps/ora-node/tests/agent_session.rs:777-803);CI Ubuntu 日志已确认这些用例实际运行并通过。 - 配置会话从空继承环境启动(D4)——已修复。
ProcessEnvironment::Empty+ 同一 spawner 覆盖插件与宿主拉起的子进程(apps/ora-node/src/session/process.rs、session/workload.rs:99-127、crates/plugin-lifecycle/src/runtime.rs:150-165),只显式补 PATH/LANG/LC_ALL/TMPDIR 和会话变量;真实 echo 插件进程断言CARGO*不传入、token 不在 env/history/Thread(agent_session.rs:689-741);重启复用同一 launcher。 - 旧路线只给
official/ora-space.opencode——已修复。driver.rs:35-37,188-194;全仓ModelAccess::open只有一个调用点;测试断言agent_model_configuration_required、无 grant、无进程(agent_session.rs:805-848)。
should-fix:AGENT_NOT_INSTALLED/AGENT_UNUSABLE 区分、target 收窄至两个 Linux gnu、Settings 256 KiB、third_party/cloud 钉 132cc6a 均已落实;生成的 controller-proto 与 cloud proto 的字段 1–9/8/10 一致。
非阻塞事项(建议合并前或合并后承接)
- P1 跨 PR:新错误码在 Cloud 侧被折叠。 cloud#106 的
internal/core/agent_run_failure.go:15-23allowlist 没有agent_model_configuration_invalid、agent_model_protocol_mismatch、agent_model_configuration_required、model_grant_mismatch、agent_model_adapter_failed,公开failureCode会全部回落agent_failed,ADR D2 的分类要求在 Cloud 产品面不成立(Node wire 本身是对的)。已同步请 cloud#106 本轮补 allowlist + 测试;若不做,本 PR 描述里应说明 Cloud 侧仍是通用码。 - Node 文档与实现不一致。
docs/node/agent-sessions.md:49-53仍写「Node 环境中的模型凭据和代理设置仍会到达 Agent」,对配置会话已相反;docs/node/protocol.md:79-83的 AgentSessionSpec 字段清单未提新增的可选agent_configuration,HelloAccepted 的model_adapter也未提。建议同 PR 或紧随的 follow-up 补两行。 - Shared placement 与 D4 前提。
model_adapter()(apps/ora-node/src/session.rs:181-195)只按 model_proxy + target 宣告能力,不要求 Separate;Shared 下配置会话仍有 model_proxy 生成的私有 HOME/XDG,默认解析是隔离的,但 agent 与 Node 同 OS 身份,仍可按绝对路径读 Node 的~/.claude等,不满足 D4「不得读取宿主登录文件」。现行保护在别处(apps/ora-node/src/main.rs:42-46要求 mTLS 必须有 workload_uid,service/agents.rs:193-200要求 workload_directory),因此不是现行 live violation;建议在model_adapter()增加 Separate 前提或把该部署不变量写进文档,并补一个 Separate 配置会话测试(现在 CI 的配置会话测试是 Shared)。 - receipt 威胁模型与升级影响。 receipt 证明「安装时验证过该归档」,不证明「当前字节仍是该归档」;在 Separate(Node home 0700 属 root、包 view 硬链接只读)下足够,Shared 下可被伪造(与上一条同源)。
remove()不删 receipt(catalog.rs:133-151),旧 receipt 只有在「回滚后同版本重装不同字节且声明不变」的组合下才有低概率 fail-open;Node 升级后旧包无 receipt,配置会话会 fail-closed 直到下一次安装计划重装。建议 remove 时清 receipt,并在 PR 描述/文档写明升级与重装语义。 - 测试缺口:没有「真实安装器写 receipt → 替换/重发布包 → 启动被拒」的端到端用例;
agent_model_protocol_mismatch与 target mismatch 未经过 driver;256 KiB 边界、Node 侧model_adapter()宣告值、Controller retransmit mismatch、重启重放model_context均无直接测试;crates/process/tests/environment.rs仅 Unix。 - PR 描述措辞:"Door: two-way / older Controller ignores
model_adapter" 只说对一半:老 Node 的AgentSessionSpec也deny_unknown_fields,收到带agent_configuration的 start 会拒绝而不是忽略;安全性靠新 Controller 的能力门和 Cloud target 保持空。建议改准。 - 小项:
apps/ora-node/src/model_proxy/test_gateway.rs:31-37的anthropic开关在现有测试中无人设置;三层 target/contract 常量目前一致但靠人工同步,可考虑留 issue。
已核对
- CI 8/8 在
c91935c4上通过(Actions head_sha 核对);本地cargo fmt --all --check、5 个相关 crate 的cargo test、ora-node --lib、check-rust-size通过;Linux 集成用例以同 head 的 Ubuntu CI 日志为证据(本地 Windows 未复跑)。 - 69 个文件全是交付物,无过程记录;15 个 commit 拆分基本单一目的。
- PR 描述 ADR 状态准确:specs
0a100a6两份 ADR 仍approved,本 PR 实现 Node/Controller 半边 D1–D4/D6,adapter、发布与真实验收仍缺。合并顺序 cloud#106 → 重钉third_party/cloud→ 本 PR → 两个 adapter,当前 pin 是 cloud#106 的 PR head,cloud 合并后需按当时 main 重钉(描述已写明)。
以上非阻塞事项不影响 Approve;owner 合并前请记得重钉 submodule,并与 cloud#106 的 allowlist 一起收尾。
…ields agent-sessions still said the Node's model credentials and proxy settings reach every agent; a session with a frozen Agent configuration now starts from an empty environment. protocol now names agent_configuration on a session start and model_adapter in hello_accepted, and that an older Node rejects the field rather than ignoring it.
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:d7b5b6b8bf3941bee794ca2ad0b25f63d875367f(增量 c91935c4..d7b5b6b8:单 commit、4 个文档文件、+27/−7;上一轮已在 c91935c4 Approve)。结论:Approve(无阻塞)。
本轮只改 docs/node/agent-sessions{,.zh}.md 与 docs/node/protocol{,.zh}.md,中英同改,无过程记录、无其它文件。逐句核对与代码一致:
- 配置会话空环境:
apps/ora-node/src/session/driver.rs:266-268(for_session(spec.agent_configuration.is_some()))→apps/ora-node/src/session/process.rs:12,23-52(PROCESS_NECESSITIES+ProcessEnvironment::Empty)→apps/ora-node/src/session/workload.rs:98-131(Shared/Separate 都用同一 isolation spawner)→ 插件进程与其宿主子进程共用该 spawner(crates/plugin-lifecycle接线)→crates/process/src/tokio_process.rs:93-95的env_clear()。文档只对带冻结配置的会话这么写、并明确其他会话仍是继承环境,范围正确。 - protocol 新字段:
crates/node-protocol/src/domain/model_adapter.rs:57-74(逐字段对上)、agent_session.rs:166-173(agent_configuration必须与model_binding_id同时出现)、message/session.rs:44-54(hello_accepted.model_adapter只随model_proxy,Option+default);contract 1、Settings 单 JSON 对象上限 256 KiB。 - 旧 Node 拒绝:
upstream/main的AgentSessionSpec同样#[serde(deny_unknown_fields)](crates/node-protocol/src/domain/agent_session.rs:113),且upstream/main没有agent_configuration。文档说「会拒绝未知字段而不是忽略」成立。 - 旧 Controller 忽略:旧
HelloAccepted没有deny_unknown_fields,新字段是Option+default,旧 Controller 会忽略model_adapter。成立。 - PR 描述更正:安全边界(Controller 的能力门 + Cloud
plugins.personal_agent_target默认空)、合并顺序、两份 ADR 仍approved、Refs ora-space/circus#354均属实。
合并时条件(由 owner 在合并步骤执行)
third_party/cloud 现钉 132cc6a88c518c267774a37cdb2d4570dfd0968e,它是 cloud#106 当前 head f454afe 的祖先、不在 cloud main 上;两处的 proto/ 子树与 cloud main 中将要合并的契约一致,所以 CI 契约仍匹配。cloud#106 合并后,需要把 third_party/cloud 重钉到包含它的 cloud main 提交、push 新 head 并重跑 crates (ubuntu-latest)/crates (macos-15)(含 proto:init/proto:check)后再合本 PR。
非阻塞
docs/node/agent-sessions.md:52-53/agent-sessions.zh.md:43写「只保留PATH、LANG、LC_ALL和TMPDIR的空环境」,漏了「会话自带变量」(HOME/XDG/CA/Git 身份等);建议与docs/plugins/agent-configuration.md的口径对齐(PR 描述里的写法是对的)。docs/node/protocol.md:89/protocol.zh.md:74的「旧 Node 拒绝未知字段」可补半句:实际是协议违规、整条 Node↔Controller 连接关闭,「never sent one」是承重前提。- PR 描述「pins ora-space/cloud#106 head
132cc6a」已过期(现 head 是f454afe,132cc6a是祖先);Merge Danger 的Door: two-way标签建议改成 one-way 或删掉。 - 可选:文档未写
configuration_revision允许 0、adapter contract 当前必须为 1(PR 描述与测试已覆盖)。
验证与未覆盖
- CI:本 head 8/8 pass(run 39599234425:changes、ci-ok、crates ubuntu/macos/windows、e2e、frontend、tauri)。
- 本轮是文档改动,未本地跑 cargo;文档陈述以上面的代码阅读逐句核对,CI 只证明构建与测试未因该 commit 回退。
- ADR:specs main
3c41a34两份仍为approved;本 PR 未改 ADR(implemented由后续落地运行承接)。
…adapter contract third_party/cloud moves from ora-space/cloud#106's branch head 132cc6a to Cloud main 4d35aad, which merged it. proto/ is identical between the two, and proto:check regenerates the committed client without a diff.
…l rejection A configured session keeps its own variables beside PATH, LANG, LC_ALL and TMPDIR, and an older Node that cannot decode agent_configuration closes the whole control session as a protocol violation rather than refusing only that start.
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:5fdfef5fb23d5bef871a7282fffdaa9aa4f77a2f(增量 d7b5b6b8..5fdfef5f:2 个 commit、5 个文件、+11/−6,即 third_party/cloud 钉子与中英 4 个文档文件;上一轮已在 d7b5b6b8 Approve)。结论:Approve(无阻塞)。
本轮增量
-
72afd052重钉third_party/cloud。 从 cloud#106 的 PR head132cc6a改为 cloud main4d35aad。核对:4d35aad是当前 cloud main 的 tip(合并 cloud#103 的 merge commit),第一父提交正是 cloud#106 的 merge commite2b5a38,所以钉子既落在 main 上、又包含本 PR 依赖的契约;按 SHA 取子模块不会取不到。- 两个钉点之间
proto/完全一致:git diff --stat 132cc6a 4d35aad -- proto为空,已提交的crates/controller-proto/src/gen因此不需要变化(增量里确实没有变化),生成的AgentModelConfiguration字段 9 与PluginAgentConfiguration字段 5 都在。 - 该 head 的 CI 8/8 全绿(run 39613584763,
head_sha已核对为5fdfef5f)。其中crates (ubuntu-latest)与crates (macos-15)跑task test:crates→lint:crates→proto:check(Taskfile.yml:84-92:check-cloud-proto.sh校验子模块处于钉住的提交且未被改动、重新生成客户端后git diff --exit-code),所以钉子与已提交客户端的一致性由门禁证明,不只是描述里的说法。
-
5fdfef5f补两条文档措辞(只动docs/node/agent-sessions{,.zh}.md、docs/node/protocol{,.zh}.md,正是上一轮的两条非阻塞建议)。逐句核对实现:- 「会话自带的其他变量」:
apps/ora-node/src/session/host.rs:59-63,73-131的SessionEnvironment把自己的变量(Git 身份、HOME/XDG/DENO_DIR,以及插件连接的 runtime 变量)同时交给插件进程和宿主为它拉起的每个进程;apps/ora-node/src/session/process.rs:12,38-52只从 Node 环境补PATH/LANG/LC_ALL/TMPDIR(会话已设置的值优先),配合ProcessEnvironment::Empty的env_clear()。文档现在与实现一致,范围仍限定在带冻结配置的会话。 - 「旧 Node 解码失败是协议违规、关闭整个控制会话」:
crates/node-protocol/src/domain/agent_session.rs:91-93的AgentSessionSpec带deny_unknown_fields(upstream/main的同名结构同样如此,且 main 里没有agent_configuration);apps/ora-node/src/service/session.rs:220-229把解码失败映射为CloseReason::ProtocolViolation,crates/node-transport/src/lib.rs:31-52给出1002与「IPC 没有关闭码、只结束流」;apps/ora-node/src/service/session.rs:155-157在会话结束后把 admission 置 false,apps/ora-node/src/service/worker.rs:143-170据此丢弃尚未接纳的排队工作。control-session.md#closing/control-session.zh.md#关闭两个锚点都存在。
- 「会话自带的其他变量」:
-
PR 描述同步更正:钉点改为
4d35aad,Door: one-way并写明回滚前要先清空 Cloud target,与deny_unknown_fields的后果一致。
整体状态(沿用前两轮结论,本轮无代码变化)
- head
5fdfef5f为MERGEABLE/CLEAN;18 个 commit、73 个文件全部是交付物(代码、测试、docs/正式说明、Cargo.lock与子模块钉子),没有过程记录。 - 两份 ADR 仍是
approved(specs main83e9d01核对)。描述写明本 PR 实现 Node/Controller 半边 D1–D4/D6,缺 adapter、发布声明与真实验收、由本运行承接,符合「部分实现」的写法。 - 上一轮列出的非阻塞项(Shared placement 与 D4 前提、receipt 威胁模型与
remove()语义、若干测试缺口、test_gateway的anthropic开关)本轮没有新增,仍按 follow-up 处理。
验证范围
本轮是钉子 + 文档改动,未在本地重跑 cargo 与 proto:check(Windows 环境);契约一致性与 8 项门禁以 5fdfef5f 的 CI 为准,文档陈述按上面的代码逐句核对。真实 bundled CLI/gateway、双用户与浏览器验收仍未做,按交接留给本运行后续步骤。
Summary
FrozenAgentConfigurationbeside its model binding; it must describe the session's own plugin release, contract version 1 and one JSON object of Settings.HelloAccepted.model_adapteradvertises the Node's target, contract and protocols, only besidemodel_proxy.agent_model_configuration_invalid/agent_model_protocol_mismatch, no process started). The plugin and every process the host spawns for it start from an empty environment plusPATH,LANG,LC_ALL,TMPDIRand the session's own variables. The grant must serve exactly the frozen protocol and model (model_grant_mismatchotherwise, revoked). The token travels only in theagent/startmodel context; the runtime refuses ACP until the adapter confirms the configuration, and every failure path revokes the grant. Onlyofficial/ora-space.opencodemay still take the legacy route without a frozen configuration; any other plugin with a binding endsagent_model_configuration_requiredbefore a grant. A configured start keepsAGENT_NOT_INSTALLED/AGENT_UNUSABLEapart while still dropping the plugin's message.Stack: rebased onto main
42f224a.third_party/cloudpins Cloud main4d35aad, which contains the merged ora-space/cloud#106 (and cloud#103);proto/is identical to the earlier pin, andproto:checkregenerates the committed client without a diff.Covers the same scope as the Draft #705 by a different route. The protections from #705 that the first review asked for are now carried over: the install receipt and start-time package/declaration/Settings check (from #705's receipt approach), the empty inherited environment (#705's
ProcessEnvironment::Empty, cherry-picked with its test), the legacy route fence and the distinct start codes. Also aligned with Cloud: frozen Settings up to 256 KiB (as Cloud stores and the SDK accepts) and the adapter capability only onx86_64/aarch64-unknown-linux-gnu.Merge order: ora-space/cloud#106 (merged) → this PR → ora-space/claude-code-agent#4 → ora-space/opencode-agent#10.
ADR: plugin-owned-model-adapters remains approved. This PR implements the Node/Controller half of D1–D4 and D6. Still open: the concrete plugin adapters (ora-space/claude-code-agent and ora-space/opencode-agent PRs from the same run), publishing declarations, and real Claude acceptance; tracked in the run below.
Refs ora-space/circus#354
Evidence
After:
a_configured_adapter_receives_its_grant_only_in_the_startup_contextruns a real plugin process: the adapter answers with the frozen model and the platform proxy,token_in_environment=false,host_environment=false(none of the test runner'sCARGO*variables reach it), the token is in neither history nor Thread, and ending revokes the grant.an_unconfirmed_adapter_never_receives_a_promptendsagent_failed/agent_unavailablewith no Thread event, the grant revoked and the plugin process gone.a_configuration_the_installed_package_cannot_prove_never_gets_a_grantandan_undeclared_plugin_never_takes_the_legacy_model_route: the session ends before any grant request or plugin process.installed_configuration_requires_receipt_declaration_and_settingscovers a missing receipt, another package digest, another fingerprint, undeclared/missing Settings, another protocol and a retired receipt;empty_environment_does_not_inherit_host_authorityspawns a real process.model_grant_mismatch).model_proxy, duplicate or unknown protocols.cargo testfor ora-node-protocol, ora-node-db, ora-agent-runtime, ora-plugin-protocol, ora-controller and ora-node plus ora-process, ora-plugin-config and ora-plugin-manager pass on headc91935c(including thestandalonesuite, 52 passed). The CI clippy command (--workspace --exclude ora-desktop --exclude ora-desktop-tests -- -D warnings),cargo fmt --checkandcheck-rust-sizepass.Merge Danger
Door: one-way
New fields are optional on the wire; legacy starts and handshakes are unchanged, and an older Controller ignores
model_adapter. An older Node does not ignoreagent_configuration:AgentSessionSpecdenies unknown fields, so it would reject such a start. That is safe only because nothing sends one to it: the Controller dispatches a configured session only to a Node whosemodel_adapteradmits it (an older Node advertises none), and Cloud'spersonal_agent_targetstays empty until compatible Nodes are deployed. One-way because, once that target is set, rolling a Node back to a build without this PR makes it reject configured starts; a rollback has to clear the Cloud target first.Blast Radius: agent-session-startup
Cloud keeps
plugins.personal_agent_targetempty until compatible Nodes and plugins are deployed; until then no configured session is admitted.