Skip to content

Add typed Goal lifecycle action catalog - #4132

Merged
huangruiteng merged 2 commits into
loopx-project:mainfrom
huashuai:codex/c4-goal-actions
Sep 14, 2026
Merged

huangruiteng merged 2 commits into
loopx-project:mainfrom
huashuai:codex/c4-goal-actions

Conversation

@huashuai

@huashuai huashuai commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What changed

  • add a TypeScript-owned Goal action catalog with typed stop/resume and operator-gate argv
  • add a read-only goal-actions CLI for cockpit clients
  • bind lifecycle execution to the projected registry fingerprint and reject stale actions before writes

Validation

  • 140 focused/compatibility pytest cases passed
  • TypeScript control-plane typecheck passed
  • Ruff and mypy passed on changed Python surfaces
  • standard loopx canary premerge --from-git-diff passed 12 selected checks with no failures or warnings

Boundaries

  • no arbitrary command execution surface
  • C4 remains a consumer; legal transitions stay in the typed LoopX control plane
  • no private state, local paths, credentials, raw logs, or external effects in the change

Future-facing review

The nearest reusable boundary is the existing Goal activation service. This change adds a narrow typed projection and freshness fence there; a broader lifecycle refactor is unnecessary.

@huashuai

huashuai commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Post-review validation update:

  • All GitHub checks are green, including DCO, dependency review, release build, Python shards, installed/e2e/mutant checks, Windows PowerShell, Sonar, and merge-gate.
  • The exact reviewed head dadb1f7ae6a0 was promoted on two representative remote hosts; loopx doctor --deep passed on both and reported the packaged source at that commit.
  • The public goal-actions command projected goal.stop for active state and goal.resume for stopped state.
  • A downstream cockpit used the projected argv over its real remote route and observed QUIET -> MOVING -> QUIET; the final state matched the original stopped state.
  • The future-facing boundary remains narrow: TypeScript owns lifecycle policy and fingerprints, while Python only adapts the typed result. No broader refactor is needed in this PR.

Manual hold: repository review policy still requires a maintainer merge. The current author attempted the authorized admin merge but does not have MergePullRequest permission.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:请求修改(exact head dadb1f7ae6a0a46defaaa31314b8b957ffb9c8f9)

动机

这个 PR 为 cockpit/CLI 投影 Goal 的可执行 owner actions:active Goal 给 stop,stopped Goal 给 resume,controller 状态再附带 approve/reject/defer,并用 registry fingerprint 防止用户点击旧生命周期动作后覆盖新状态。把合法动作集合放到 TypeScript control plane、让 Python 只负责读取 registry 与执行现有 service,是符合当前 authority 迁移方向的;显式 freshness fence 也比 UI 自己猜合法 transition 更可靠。

改动思路

goal-actions 先调用 status 推断是否存在 controller gate,再由 Python adapter 解析 authoritative source registry、计算 SHA-256 fingerprint,并把 activation state 与 gate flag交给 projectGoalOperatorActions。TypeScript reducer生成有限 action catalog;生命周期 action 回到 goal-lifecycle --expected-state-fingerprint ... --execute,Python 在锁前和锁内都校验 registry bytes,最后沿既有 source→global route 写入并 read back。类型化状态、domain-neutral 文案和“requires_confirmation”义务表达总体清楚;默认未调用 goal-actions 时没有行为变化。

具体改动

关键代码讲解

  • projectGoalOperatorActions:拥有 active/stopped 到 stop/resume 的合法映射,并按 controller flag 生成有限 gate actions。
  • build_goal_action_catalog:解析 source authority、计算 fingerprint,并校验 TS 返回的 schema/actions shape。
  • set_goal_activation_state(expected_state_fingerprint=...):在写锁前后检查 source registry,拒绝 stale action。
  • handle_goal_actions_command:把 status 的 waiting kind 适配成 operator-gate 是否可见的输入。
  • test_projected_lifecycle_action_rejects_stale_registry_without_writing:验证 registry 变化后 lifecycle 写入被拒绝。

变更为 8 个文件、约 596 行;新增 CLI、typed reducer、adapter 和 focused regression 是一个完整 stage,而不是纯脚手架。

对主干的风险

[P1] catalog 中的 execution argv 没有绑定生成它的 registry,且 gate actions 根本没有 freshness fence

lifecycleAction 返回的 argv 从 loopx goal-lifecycle ... 开始,没有 --registry / --runtime-root,但 goal-actions 本身允许针对任意显式 registry 生成 catalog。真实 client 逐字执行返回 argv 时会落到 ambient/default registry,而不是生成 fingerprint 的 authority;当前测试在 _run_cli(registry_path, *argv[1:]) 中额外偷偷补回 registry,所以没有验证公开 execution contract。更严重的是 gateAction 虽然和 catalog 共用 state_fingerprint,执行 argv 既不携带 fingerprint,也没有 exact gate identity,只会调用默认 read_only_map_opt_in;状态或待决 gate 在投影后变化,旧 approve/reject/defer 仍可写入。于是 catalog 所称的“fresh typed owner actions”只对 lifecycle action 部分成立。

最小修复有两个可接受方向:要么 v1 只发布已绑定的 lifecycle actions并暂时移除 gate actions;要么把 authoritative registry/runtime locator、exact gate id 和 expected decision fingerprint 纳入 typed request/action/CLI,执行端在同一 authority 锁下拒绝 stale gate。回归应从一个非默认 registry 生成 catalog,然后不注入任何隐藏上下文地逐字执行完整 argv,并证明 registry 或 gate 状态改变后不会写入。

本地 6 个 focused pytest 与 Ruff 通过,git diff --check 通过;本地 worktree 缺少 Node type definitions,因此没有把本地 TS typecheck计为证据,远端 TypeScript/pytest/installed/e2e/mutants/merge-gate 均为成功。

我的整体评价

TypeScript 拥有 transition decision、Python 复用既有 activation service,以及锁内二次 fingerprint校验,都是值得保留的设计。future-facing pass 应收紧 action 的 authority/freshness envelope,而不是新增更泛的 command framework。当前公开 catalog 把“可执行 argv”作为 contract,却依赖调用方补一个未声明的 registry 上下文,并把未受 freshness 保护的 gate action 混在同一个 fresh catalog 中;这是会导致错 registry 或过期授权写入的语义缺陷,因此需修复后复审。

English verdict: REQUEST_CHANGES — make every executable action self-contained and authority-bound, and either freshness-bind exact operator-gate actions or omit them from this catalog.

huashuai and others added 2 commits September 14, 2026 10:35
Signed-off-by: Huashuai Qu <256761+huashuai@users.noreply.github.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:批准(exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c)

动机

这个 PR 为 cockpit/CLI 提供一个由 TypeScript control plane 拥有的 Goal 生命周期动作目录,并让客户端能够逐字执行返回的 argv。原始实现的主要风险是:目录允许针对显式 registry 生成动作,但 lifecycle argv 没有携带该 registry/runtime 上下文,调用方只能暗中补参数;同时 approve/reject/defer 的 operator-gate argv 没有同等 freshness fence,旧 gate 可能继续写入。修复后,目录只发布已经具备完整 authority/freshness envelope 的 stop/resume 动作;gate 决策留在既有 operator-gate 命令路径,待未来补齐 exact gate identity 与决策点 freshness 后再纳入目录。这解决了本次可验证的错误 authority 和 stale lifecycle 写入问题,不扩大为任意命令执行或权限授权。

改动思路

入口仍是 registry_admin 注册的 goal-actions,Python adapter 读取请求 registry,经 activation_service._source_and_target 解析 source→global authority route,并对完整 source registry bytes 计算 SHA-256。projectGoalOperatorActions 在 TypeScript effect runtime 中校验 v2 request、Goal id、active/stopped 状态、registry locator 和 fingerprint,负责唯一的 active→stop / stopped→resume 合法映射。它生成包含 --registry、可选 --runtime-root、--format json、operation 和 --expected-state-fingerprint 的自包含 argv;不会写 registry。

执行 argv 回到既有 goal-lifecycle / set_goal_activation_state。该 service 在执行前检查 fingerprint,并在 source exclusive lock 内重新读取 bytes、再次检查 fingerprint 和 activation state,然后沿既有 sync/readback 路径写入。变化时返回 goal_action_stale、written=false,由 client 刷新目录重试。这个边界复用了现有 authority route、锁、atomic write、global sync 和 readback,没有新建第二个生命周期 writer。由于 operator-gate writer 还没有同等决策点 fence,本 head 明确不再从 status 推导并发布 gate 写动作。

具体改动

关键代码讲解

  • loopx/cli_commands/goal_actions.py:32 新增只读 CLI adapter;它直接调用 build_goal_action_catalog,失败时返回 bounded ok=false payload,不触碰 registry。
  • loopx/control_plane/goals/operator_actions.ts:85 新增 v2 typed reducer。lifecycleAction 将 registry/runtime locator、JSON 输出和 fingerprint 放入完整 argv;projectGoalOperatorActions 只允许两种明确生命周期状态,gate action 不再混入同一 catalog。
  • loopx/control_plane/goals/operator_actions.py:35 将 requested registry 与解析出的 route 适配到 TypeScript effect,并验证返回 catalog/action shape;没有复刻 transition 规则。
  • loopx/control_plane/goals/activation_service.py:206 增加可选 expected fingerprint,在无锁预检和 source lock 内二次检查之间形成 stale/TOCTOU 防线;原有写入、同步和 readback owner 不变。
  • loopx/cli_commands/goal_lifecycle.py:34 接收并转发 fingerprint;registry_admin.py 与 effect_runtime_handlers.ts 接入新 CLI/effect。tests/control_plane/test_goal_operator_actions.py 覆盖 typed 正负路径、完整 argv、非默认 registry、stale 不写入和 stop→resume。

对主干的风险

没有遗留阻塞性 finding。新增 request schema 为 loopx_goal_action_projection_request_v2,因为 authority locator 现在是生成可执行 action 的必需输入;consumer-facing catalog schema 仍为 v1。直接调用既有 goal-lifecycle 时 fingerprint 仍是可选参数,因此默认路径保持兼容;新 goal-actions 本身是只读,只有明确带 --execute 的生命周期 argv 才会写入。

本地验证包括 59 个聚焦/相关 pytest、Ruff、Python 编译、TypeScript strip-types 语法检查、git diff --check,以及 loopx canary premerge --from-git-diff:12 个选中 checks 全部通过,0 failure、0 warning、0 manual hold。回归测试真正执行完整返回 argv,没有 helper 隐藏注入 registry,并验证 registry 变化后 goal_action_stale 且字节不变。远端 exact-head 的 DCO、依赖、构建、Python Tests(含 kernel/static、Node 兼容、dashboard、Windows、shards、e2e、mutants)需在合并前保持全绿;gate affordances 是有意的后续边界,不应通过恢复无 freshness 的 gate argv 来补齐。

我的整体评价

这是一个范围收敛后可合并的完整 stage:TypeScript 负责 typed lifecycle decision,Python 复用既有 authority/write owner,客户端拿到的 action 不再依赖未声明环境。删除 status-derived gate projection 比继续堆叠一个不完整 gate 协议更安全,也让目录的“fresh executable action”语义真实成立。exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c 的行为、负路径和主干对齐均已复核;剩余风险仅是 operator-gate 尚未进入该目录,后续应以独立的 exact gate identity/fingerprint 合同补齐。

English verdict: APPROVE — exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c binds lifecycle argv to the declared registry/runtime authority, rejects stale writes before and under lock, and removes unfenced operator-gate actions. Focused tests, local premerge canary, and the full remote required checks are green; no blocking finding remains.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:批准(exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c)

动机

这个 PR 为 cockpit/CLI 提供一个由 TypeScript control plane 拥有的 Goal 生命周期动作目录,并让客户端能够逐字执行返回的 argv。原始实现的主要风险是:目录允许针对显式 registry 生成动作,但 lifecycle argv 没有携带该 registry/runtime 上下文,调用方只能暗中补参数;同时 approve/reject/defer 的 operator-gate argv 没有同等 freshness fence,旧 gate 可能继续写入。修复后,目录只发布已经具备完整 authority/freshness envelope 的 stop/resume 动作;gate 决策留在既有 operator-gate 命令路径,待未来补齐 exact gate identity 与决策点 freshness 后再纳入目录。这解决了本次可验证的错误 authority 和 stale lifecycle 写入问题,不扩大为任意命令执行或权限授权。

改动思路

入口仍是 registry_admin 注册的 goal-actions,Python adapter 读取请求 registry,经 activation_service._source_and_target 解析 source→global authority route,并对完整 source registry bytes 计算 SHA-256。projectGoalOperatorActions 在 TypeScript effect runtime 中校验 v2 request、Goal id、active/stopped 状态、registry locator 和 fingerprint,负责唯一的 active→stop / stopped→resume 合法映射。它生成包含 --registry、可选 --runtime-root、--format json、operation 和 --expected-state-fingerprint 的自包含 argv;不会写 registry。

执行 argv 回到既有 goal-lifecycle / set_goal_activation_state。该 service 在执行前检查 fingerprint,并在 source exclusive lock 内重新读取 bytes、再次检查 fingerprint 和 activation state,然后沿既有 sync/readback 路径写入。变化时返回 goal_action_stale、written=false,由 client 刷新目录重试。这个边界复用了现有 authority route、锁、atomic write、global sync 和 readback,没有新建第二个生命周期 writer。由于 operator-gate writer 还没有同等决策点 fence,本 head 明确不再从 status 推导并发布 gate 写动作。

具体改动

关键代码讲解

  • loopx/cli_commands/goal_actions.py:32 新增只读 CLI adapter;它直接调用 build_goal_action_catalog,失败时返回 bounded ok=false payload,不触碰 registry。
  • loopx/control_plane/goals/operator_actions.ts:85 新增 v2 typed reducer。lifecycleAction 将 registry/runtime locator、JSON 输出和 fingerprint 放入完整 argv;projectGoalOperatorActions 只允许两种明确生命周期状态,gate action 不再混入同一 catalog。
  • loopx/control_plane/goals/operator_actions.py:35 将 requested registry 与解析出的 route 适配到 TypeScript effect,并验证返回 catalog/action shape;没有复刻 transition 规则。
  • loopx/control_plane/goals/activation_service.py:206 增加可选 expected fingerprint,在无锁预检和 source lock 内二次检查之间形成 stale/TOCTOU 防线;原有写入、同步和 readback owner 不变。
  • loopx/cli_commands/goal_lifecycle.py:34 接收并转发 fingerprint;registry_admin.py 与 effect_runtime_handlers.ts 接入新 CLI/effect。tests/control_plane/test_goal_operator_actions.py 覆盖 typed 正负路径、完整 argv、非默认 registry、stale 不写入和 stop→resume。

对主干的风险

没有遗留阻塞性 finding。新增 request schema 为 loopx_goal_action_projection_request_v2,因为 authority locator 现在是生成可执行 action 的必需输入;consumer-facing catalog schema 仍为 v1。直接调用既有 goal-lifecycle 时 fingerprint 仍是可选参数,因此默认路径保持兼容;新 goal-actions 本身是只读,只有明确带 --execute 的生命周期 argv 才会写入。

本地验证包括 59 个聚焦/相关 pytest、Ruff、Python 编译、TypeScript strip-types 语法检查、git diff --check,以及 loopx canary premerge --from-git-diff:12 个选中 checks 全部通过,0 failure、0 warning、0 manual hold。回归测试真正执行完整返回 argv,没有 helper 隐藏注入 registry,并验证 registry 变化后 goal_action_stale 且字节不变。远端 exact-head 的 DCO、依赖、构建、Python Tests(含 kernel/static、Node 兼容、dashboard、Windows、shards、e2e、mutants)需在合并前保持全绿;gate affordances 是有意的后续边界,不应通过恢复无 freshness 的 gate argv 来补齐。

我的整体评价

这是一个范围收敛后可合并的完整 stage:TypeScript 负责 typed lifecycle decision,Python 复用既有 authority/write owner,客户端拿到的 action 不再依赖未声明环境。删除 status-derived gate projection 比继续堆叠一个不完整 gate 协议更安全,也让目录的“fresh executable action”语义真实成立。exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c 的行为、负路径和主干对齐均已复核;剩余风险仅是 operator-gate 尚未进入该目录,后续应以独立的 exact gate identity/fingerprint 合同补齐。

English verdict: APPROVE — exact head a9fff9ec0c8160bd53fc8d1f581014232a057d9c binds lifecycle argv to the declared registry/runtime authority, rejects stale writes before and under lock, and removes unfenced operator-gate actions. Focused tests, local premerge canary, and the full remote required checks are green; no blocking finding remains.

@huangruiteng
huangruiteng merged commit e743f24 into loopx-project:main Sep 14, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants