From 3701644ead04070d6745431a75e323af9f9231db Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 15:08:59 +0800 Subject: [PATCH 1/2] docs(rfc): record the Codex provider-routing activation seam The package import ratchet fails on main. PR #7625 added from loopx.runtime_activation import managed_chat_activation_guard, RuntimeActivationDeferred to packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py, and the pin that records every `loopx.*` module a co-located package imports does not carry that edge: AssertionError: co-located packages gained imports of loopx internals: {'loopx-codex-provider-routing': ['loopx.runtime_activation']} The pin's own note says a new edge needs a recorded design decision, and the monorepo-distribution-split RFC Section 5 holds that decision. This records it. `loopx.runtime_activation` owns the transition of services that are already installed. `managed_chat_activation_guard` holds the accepted-Chat-work fence and the ingress lock while a managed client restarts, and the Codex provider routing package uses it, and no other entry point of that module, to fence accepted Chat work before it restarts the Chat services it manages. The guard grants no work authority: it takes the ingress lock and refuses to continue while accepted work or ingress is pending. Section 5 now names that module as the third public module in the supported host integration seam, beside MemT's facade and `loopx.file_lock`, and says that the seam does not widen: a package still may not import the executor or another kernel-private module, and the pinned Ark/JEV edges stay migration debt. Both mirrors carry the same text. The RFC's Appendix A records the baseline, the boundary and the remaining gaps, and its decision log records the alternatives: restarting the Chat services without the fence, copying the lock and state check into the package, or leaving the edge unrecorded. The pin gains the edge and its note names the new seam member. No production code, schema, wire or default change. Validation: the import ratchet passes; the RFC bilingual diff check, the RFC status index check and the docs governance smoke pass; mkdocs --strict builds the docs site and both developer books. Signed-off-by: xiaoan17 --- .../rfcs/monorepo-distribution-split-v0.md | 36 ++++++++++++++++++- .../monorepo-distribution-split-v0.zh-CN.md | 28 ++++++++++++++- .../package_kernel_import_pins.json | 7 ++-- .../test_package_kernel_import_ratchet.py | 7 ++-- 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 6a4b46ff83..a73c3d0b6a 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -302,6 +302,17 @@ records both edges. Existing Ark/JEV private edges remain migration debt and receive no new exemption. The facade forwards existing behavior; it must not copy candidate, acceptance, process-lifetime or locking decisions. +One more public module joins that seam. `loopx.runtime_activation` owns the +transition of services that are already installed, and its +`managed_chat_activation_guard` holds the accepted-Chat-work fence and the +ingress lock while a managed client restarts. `loopx-codex-provider-routing` +uses that guard, and no other entry point of that module, to fence accepted Chat +work before it restarts the Chat services it manages. The guard grants no work +authority: it takes the ingress lock and refuses to continue while accepted +work or ingress is pending. The import ratchet records that edge as well. This +is a third public module in the seam and does not widen it: a package still may +not import the executor or another kernel-private module. + The stdio relay cancels its owned process group on parent EOF, SIGTERM or SIGINT and waits for the existing supervisor's cleanup before exiting. POSIX process-group behavior and Windows best-effort tree cleanup retain their @@ -696,11 +707,34 @@ Approval needs a reviewed decision, not implementation progress or silence. - **Remaining:** the wider M1–M5 distribution and zero-private-import acceptance is not closed; D1–D4 stay open. No packaging or authority decision changes. +### 2026-10-29 — Codex provider-routing activation seam + +- **Baseline:** `5d7f0a61b`. +- **Delivered:** the supported host integration seam in Section 5 names one more + public module. `loopx.runtime_activation` owns the transition of services that + are already installed, and `loopx-codex-provider-routing` uses its + `managed_chat_activation_guard` to hold the accepted-Chat-work fence and the + ingress lock while it restarts the Chat services it manages. The import + ratchet records that edge. +- **Boundary repair:** the guard grants no work authority — it takes the ingress + lock and refuses to continue while accepted work or ingress is pending — and + the package uses no other entry point of that module. The seam stays closed: a + package still may not import the executor or another kernel-private module, + and the pinned Ark/JEV edges remain migration debt. +- **Evidence:** the import ratchet pins the edge instead of leaving it + unrecorded, and the Codex provider-routing native host smoke exercises the + guarded restart path. +- **Remaining:** the wider M1–M5 distribution and zero-private-import acceptance + is not closed; D1–D4 stay open. The seam records one module for one caller, + not a general package-to-kernel permission. + ## Appendix B: Decision log | Date | Decision | Owner / approval | Alternatives | Normative sections changed | | --- | --- | --- | --- | --- | -| — | — | — | — | — | +| 2026-10-29 | `loopx.runtime_activation` joins the supported host integration seam, so the Codex provider-routing package can hold the accepted-Chat-work fence while it restarts the Chat services it manages | Maintainers, through RFC review and merge | Restart the Chat services without the fence; copy the lock and state check into the package; keep the edge unrecorded | Section 5 | + +No other decision is approved. D1–D4 remain open proposals. ## Appendix C: Evidence registry diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 9d64e1d4d2..8c108bcf4a 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -208,6 +208,14 @@ MemT 只使用这两个公开模块,不能直接 import executor 或其他内 边。Ark/JEV 的既有私有边仍是迁移积压,不新增豁免。facade 只转发现有行为, 不能复制候选、验收、进程生命周期或文件锁决策。 +该接缝再增加一个公开模块。`loopx.runtime_activation` 负责已安装服务的切换, +其 `managed_chat_activation_guard` 在受管客户端重启期间持有"已受理 Chat 工作" +栅栏与入口锁。`loopx-codex-provider-routing` 只使用该栅栏、不使用该模块的其他 +入口,在重启自己管理的 Chat 服务之前为已受理的 Chat 工作加栅栏。该栅栏不授予 +工作权限:它获取入口锁,并在仍有已受理工作或入口流量时拒绝继续。import ratchet +同样钉住这条边。这是接缝中的第三个公开模块,并没有放宽接缝:包依旧不能 import +executor 或其他内核私有模块。 + stdio relay 在父进程 EOF、SIGTERM 或 SIGINT 时取消所持进程组,并等待已有 监督器完成回收再退出。POSIX 进程组行为与 Windows 尽力而为的进程树回收仍沿用 原资格边界。这是进程监督,不是沙箱;操作者选择的工具只保有原先获准的效果。 @@ -498,11 +506,29 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, - **剩余:** 不关闭更广的 M1–M5 发行物与零私有 import 验收;D1–D4 仍未决。 不改变打包方案或权威决策。 +### 2026-10-29 — Codex provider-routing 激活接缝 + +- **基线:** `5d7f0a61b`。 +- **交付:** 第 5 节的受支持宿主接入接缝再增加一个公开模块。 + `loopx.runtime_activation` 负责已安装服务的切换, + `loopx-codex-provider-routing` 使用其 `managed_chat_activation_guard`,在重启 + 自己管理的 Chat 服务期间持有"已受理 Chat 工作"栅栏与入口锁。import ratchet + 钉住这条边。 +- **边界修复:** 该栅栏不授予工作权限——它获取入口锁,并在仍有已受理工作或入口 + 流量时拒绝继续——且该包不使用该模块的其他入口。接缝仍然闭合:包依旧不能 + import executor 或其他内核私有模块,已钉住的 Ark/JEV 私有边仍是迁移积压。 +- **证据:** import ratchet 钉住这条边,而不是让它处于未记录状态;Codex + provider-routing 的 native host smoke 覆盖受保护的重启路径。 +- **剩余:** 不关闭更广的 M1–M5 发行物与零私有 import 验收;D1–D4 仍未决。 + 该接缝只为一个调用方记录一个模块,不是通用的包到内核许可。 + ## 附录 B:决策日志 | 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 | | --- | --- | --- | --- | --- | -| — | — | — | — | — | +| 2026-10-29 | `loopx.runtime_activation` 加入受支持的宿主接入接缝,使 Codex provider-routing 包能在重启自己管理的 Chat 服务期间持有"已受理 Chat 工作"栅栏 | maintainers,经 RFC 评审与合并 | 不带栅栏重启 Chat 服务;把锁与状态检查复制进包;让这条边保持未记录 | 第 5 节 | + +除此以外没有已批准的决策。D1–D4 仍是未决提案。 ## 附录 C:证据登记 diff --git a/tests/architecture/package_kernel_import_pins.json b/tests/architecture/package_kernel_import_pins.json index a1f468f70c..c8bc011238 100644 --- a/tests/architecture/package_kernel_import_pins.json +++ b/tests/architecture/package_kernel_import_pins.json @@ -1,12 +1,15 @@ { "schema": "loopx_package_kernel_import_pins_v0", - "baseline_commit": "70bc98198ce9e8c33e461cc41fe77243d713dcf0", - "note": "Exact pin of every `loopx.*` module imported by a co-located package. Pre-existing private edges remain migration debt. The supported-host-integration decision in the monorepo-distribution-split RFC Section 5 permits MemT's two public integration imports; it does not permit private kernel imports. Remove an entry in the PR that removes the import. Adding an entry needs a recorded design decision.", + "baseline_commit": "5d7f0a61b0b79876801c05e1d26352687f270794", + "note": "Exact pin of every `loopx.*` module imported by a co-located package. Pre-existing private edges remain migration debt. The supported-host-integration decision in the monorepo-distribution-split RFC Section 5 permits MemT's two public integration imports and the Codex provider-routing activation guard; it does not permit private kernel imports. Remove an entry in the PR that removes the import. Adding an entry needs a recorded design decision.", "imports": { "loopx-ark-turn": [ "loopx.control_plane.turn_driver.host_candidate", "loopx.file_lock" ], + "loopx-codex-provider-routing": [ + "loopx.runtime_activation" + ], "loopx-finance-value-discovery": [ "loopx.extensions.lark.presentation.message_card", "loopx.extensions.presentation" diff --git a/tests/architecture/test_package_kernel_import_ratchet.py b/tests/architecture/test_package_kernel_import_ratchet.py index 227d56fc4d..177c28bafd 100644 --- a/tests/architecture/test_package_kernel_import_ratchet.py +++ b/tests/architecture/test_package_kernel_import_ratchet.py @@ -5,9 +5,10 @@ contracts and CLI subcommands through `CapabilityRegistry`; it never imports kernel-private modules." Three packages already do. The Section 5 supported host integration decision adds MemT's public facade and shared file-lock API, -not another private-kernel exception. This guard pins those -edges exactly so a new one needs a recorded decision and a removed one -lowers the pin in the same PR, following `test_top_level_module_budget`. +plus the Codex provider-routing activation guard, not another private-kernel +exception. This guard pins those edges exactly so a new one needs a recorded +decision and a removed one lowers the pin in the same PR, following +`test_top_level_module_budget`. """ from __future__ import annotations From 7c5b252b3c500b4578df186ff79bd178da1175d0 Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 22:37:09 +0800 Subject: [PATCH 2/2] docs(rfc): narrow the activation guard's refusal to what it checks The recorded decision claimed the guard refuses to continue while accepted work or ingress is pending. The real guard takes the ingress admission lock and refuses to continue only while a non-terminal Turn exists; it reads accepted work through the Turn state and never reads the ingress receipt ledger, so a pending queue or inbox receipt passes through. Narrow the English text, the Chinese mirror and Appendix A to the non- terminal Turn check, and pin the narrowed boundary with a test. Signed-off-by: xiaoan17 --- .../rfcs/monorepo-distribution-split-v0.md | 16 ++++++---- .../monorepo-distribution-split-v0.zh-CN.md | 11 +++++-- tests/test_chat_runtime_activation.py | 30 ++++++++++++++++++- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index a73c3d0b6a..48cd622f6f 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -308,8 +308,9 @@ transition of services that are already installed, and its ingress lock while a managed client restarts. `loopx-codex-provider-routing` uses that guard, and no other entry point of that module, to fence accepted Chat work before it restarts the Chat services it manages. The guard grants no work -authority: it takes the ingress lock and refuses to continue while accepted -work or ingress is pending. The import ratchet records that edge as well. This +authority: it holds the ingress admission lock and refuses to continue while a +non-terminal Turn is pending. A pending ingress receipt is not part of that +check. The import ratchet records that edge as well. This is a third public module in the seam and does not widen it: a package still may not import the executor or another kernel-private module. @@ -716,14 +717,19 @@ Approval needs a reviewed decision, not implementation progress or silence. `managed_chat_activation_guard` to hold the accepted-Chat-work fence and the ingress lock while it restarts the Chat services it manages. The import ratchet records that edge. -- **Boundary repair:** the guard grants no work authority — it takes the ingress - lock and refuses to continue while accepted work or ingress is pending — and +- **Boundary repair:** the guard grants no work authority — it holds the ingress + admission lock and refuses to continue while a non-terminal Turn is pending, + and a pending ingress receipt is not part of that check — and the package uses no other entry point of that module. The seam stays closed: a package still may not import the executor or another kernel-private module, and the pinned Ark/JEV edges remain migration debt. - **Evidence:** the import ratchet pins the edge instead of leaving it unrecorded, and the Codex provider-routing native host smoke exercises the - guarded restart path. + guarded restart path. `tests/test_chat_runtime_activation.py:: + test_a_pending_ingress_receipt_alone_does_not_defer_the_restart` pins the + narrowed boundary: a pending `loopx_queue` or `loopx_inbox` receipt on its own + does not defer the restart, because the guard reads accepted work through the + Turn state. - **Remaining:** the wider M1–M5 distribution and zero-private-import acceptance is not closed; D1–D4 stay open. The seam records one module for one caller, not a general package-to-kernel permission. diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 8c108bcf4a..1a6b97e012 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -212,7 +212,8 @@ MemT 只使用这两个公开模块,不能直接 import executor 或其他内 其 `managed_chat_activation_guard` 在受管客户端重启期间持有"已受理 Chat 工作" 栅栏与入口锁。`loopx-codex-provider-routing` 只使用该栅栏、不使用该模块的其他 入口,在重启自己管理的 Chat 服务之前为已受理的 Chat 工作加栅栏。该栅栏不授予 -工作权限:它获取入口锁,并在仍有已受理工作或入口流量时拒绝继续。import ratchet +工作权限:它持有入口准入锁,并在仍有非终态 Turn 时拒绝继续。待处理的入口收据 +不在该检查范围内。import ratchet 同样钉住这条边。这是接缝中的第三个公开模块,并没有放宽接缝:包依旧不能 import executor 或其他内核私有模块。 @@ -514,11 +515,15 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, `loopx-codex-provider-routing` 使用其 `managed_chat_activation_guard`,在重启 自己管理的 Chat 服务期间持有"已受理 Chat 工作"栅栏与入口锁。import ratchet 钉住这条边。 -- **边界修复:** 该栅栏不授予工作权限——它获取入口锁,并在仍有已受理工作或入口 - 流量时拒绝继续——且该包不使用该模块的其他入口。接缝仍然闭合:包依旧不能 +- **边界修复:** 该栅栏不授予工作权限——它持有入口准入锁,并在仍有非终态 Turn 时 + 拒绝继续;待处理的入口收据不在该检查范围内——且该包不使用该模块的其他入口。 + 接缝仍然闭合:包依旧不能 import executor 或其他内核私有模块,已钉住的 Ark/JEV 私有边仍是迁移积压。 - **证据:** import ratchet 钉住这条边,而不是让它处于未记录状态;Codex provider-routing 的 native host smoke 覆盖受保护的重启路径。 + `tests/test_chat_runtime_activation.py::test_a_pending_ingress_receipt_alone_does_not_defer_the_restart` + 钉住收窄后的边界:只有一条待处理的 `loopx_queue` 或 `loopx_inbox` 收据并不会 + 推迟重启,因为该栅栏是通过 Turn 状态读取已受理工作的。 - **剩余:** 不关闭更广的 M1–M5 发行物与零私有 import 验收;D1–D4 仍未决。 该接缝只为一个调用方记录一个模块,不是通用的包到内核许可。 diff --git a/tests/test_chat_runtime_activation.py b/tests/test_chat_runtime_activation.py index 59d70c1f10..83b6e8a394 100644 --- a/tests/test_chat_runtime_activation.py +++ b/tests/test_chat_runtime_activation.py @@ -14,7 +14,7 @@ import pytest from loopx.chat_store import ChatSessionStore -from loopx.chat_ingress import chat_runtime_activation_lock +from loopx.chat_ingress import chat_runtime_activation_lock, chat_runtime_activation_state from loopx.runtime_activation import restart_services_for_runtime_activation from loopx.self_update import execute_update_plan, render_update_plan_markdown @@ -112,6 +112,34 @@ def test_queue_without_an_active_pointer_still_defers_until_original_work_finish assert store.load_turn(sid, queued["turn_id"])["message"] == "later input" +@pytest.mark.parametrize("mode", ["loopx_queue", "loopx_inbox"]) +def test_a_pending_ingress_receipt_alone_does_not_defer_the_restart(services, mode): + """The guard fences non-terminal Turns, not pending ingress receipts. + + A pending ingress receipt reserves external input before the provider + delivers it, so it is not a Turn yet. The activation guard reads the + accepted work through the Turn state, so a pending receipt on its own + does not defer the restart. This is the boundary the guard actually + holds, not a promise that pending receipts are covered. + """ + runtime, _agents, calls = services + store = ChatSessionStore(runtime) + sid = session(store) + receipt, created = store.create_ingress_receipt( + sid, + client_ingress_id=f"client-{mode}", + mode=mode, + message="reserved input", + ) + assert created is True + assert receipt["status"] == "pending" + assert chat_runtime_activation_state(runtime)["nonterminal_turns"] == 0 + + result = activate() + assert result["restart_status"] == "restarted" + assert len(calls) == 2 + + @pytest.mark.parametrize( "damage", ["session", "turn", "active_missing", "unknown_status", "invalid_status"] )