Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,14 @@ loopx coordination-shadow promote --goal-id <goal-id> \
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim --execute
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim \
--handoff-mode-migration preserve
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim \
--handoff-mode-migration hard_lease --execute
loopx coordination-shadow rollback --goal-id <goal-id> \
--provider-revision <revision-from-inspect> --execute
```
Expand All @@ -1823,10 +1831,18 @@ interruption. Apply holds the shared
maintenance and legacy source locks while it revalidates the source snapshot,
qualifies the exact shadow lineage, engages the durable writer fence, commits
the canonical head, and reads back the promotion receipt. v0 rejects a Goal
whose already-qualified mode is not `hard_lease`; promotion never changes that
mode as a side effect. A successful write is immediately read back through
the typed parity inspection. The command remains unavailable unless the exact
goal-level `file_v0` shadow opt-in is active.
whose already-qualified mode is not `hard_lease` when the migration option is
omitted. An explicit `preserve` plan canonicalizes a `legacy` or `soft_claim`
Goal without changing its ownership policy. An explicit `hard_lease` plan may
combine the authority cutover with the one supported policy upgrade while
preserving claims and safe lease records. It never synthesizes leases: a
preserved claim owner acquires a lease through the ordinary atomic path before
its next protected write. Preview exposes preserved claims, lease dispositions,
conflicts, and the exact target digest; the mode intent, registered-agent set,
and target digest are part of the promotion-plan identity. Other mode
transitions remain subject to the ordinary quiescence rule. A successful write
is immediately read back through the typed parity inspection. The command
remains unavailable unless the exact goal-level `file_v0` shadow opt-in is active.

Pre-promotion rollback is revision-fenced and non-destructive. TypeScript moves
the exact active file-shadow lineage into a durable quarantine archive; exact
Expand Down Expand Up @@ -2332,9 +2348,13 @@ remain reviewable in the same bounded slice.
preserved. The maintainer must approve the named removal explicitly in the
RFC decision log or PR review; absence of a discovered consumer is not
approval.*
9. Does v0 promotion cover only `hard_lease` goals? *Proposed answer: yes. A
`legacy` or `soft_claim` goal first switches mode under the Appendix B
quiescence rule; promotion never changes the mode implicitly.*
9. Does v0 promotion cover only `hard_lease` goals? *Resolved answer: the
backward-compatible default still requires a qualified `hard_lease` source.
A reviewed operator may explicitly choose `preserve` to canonicalize a
`legacy` or `soft_claim` Goal without changing its policy, or `hard_lease` to
perform the one supported claim-preserving upgrade inside the fenced
cutover. No lease is invented, and every other mode change still uses the
Appendix B quiescence rule.*
10. After the provider-first read flip, Markdown and lease files are
projections and the kernel forbids fallback to them. Which data belongs in
the head, and how are compatibility views rendered? *Proposed answer:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,14 @@ loopx coordination-shadow promote --goal-id <goal-id> \
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim --execute
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim \
--handoff-mode-migration preserve
loopx coordination-shadow promote --goal-id <goal-id> \
--minimum-operations 3 \
--require-event-kind todo_claim \
--handoff-mode-migration hard_lease --execute
loopx coordination-shadow rollback --goal-id <goal-id> \
--provider-revision <revision-from-inspect> --execute
```
Expand All @@ -1454,9 +1462,15 @@ operation 数与规范化后的必需
event kind;持久 fence、event 与 receipt 都携带同一 digest,因此 fence 已落盘而 canonical
尚未提交的中断只能由完全相同的受评审 plan 恢复。apply 会在同一段 maintenance 与 legacy source 锁生命周期内重新
验证 source snapshot、资格化精确 shadow lineage、engage 持久 writer fence、提交
canonical head,并读回 promotion receipt。v0 会拒绝尚未资格化为 `hard_lease` 的 Goal,
且绝不会把 handoff mode 变化藏在 promotion 副作用中。写入成功后会立即通过 typed parity inspection
读回。除非目标开启精确的 goal-level `file_v0` shadow opt-in,否则该命令不可执行。
canonical head,并读回 promotion receipt。未传迁移参数时,v0 继续拒绝尚未资格化为
`hard_lease` 的 Goal。显式 `preserve` 可以只切换 authority 并保留 `legacy`/
`soft_claim` policy;显式 `hard_lease` 可以在同一受评审 cutover 中执行唯一获支持的
policy 升级,并保留 claim 与安全的 lease record。它不会伪造 lease:保留 claim 的
owner 必须在下一次受保护写入前走普通原子路径取得 lease。preview 会展示保留 claim、
lease 处置、冲突与精确目标 digest;mode 意图、registered-agent 集合和目标 digest 都
进入 promotion-plan identity。其他 mode 变化仍受普通静止规则约束。写入成功后会立即
通过 typed parity inspection 读回。除非目标开启精确的 goal-level `file_v0` shadow
opt-in,否则该命令不可执行。

promotion 前 rollback 带精确 revision fence,且不删除数据。TypeScript 会把命中的
file-shadow lineage 移入持久 quarantine archive;精确重试复用 archive receipt,revision
Expand Down Expand Up @@ -1854,8 +1868,11 @@ decision authority,并且 caller-visible parity 与 rollback 能在同一有
reader。对应 PR 必须提供字段 inventory、producer/reader/writer 与静态引用调研、
历史和外部兼容性结论、migration/rollback,以及行为等价证明;maintainer 必须在 RFC
decision log 或 PR review 中对点名字段显式批准。没有发现 consumer 不等于批准删除。*
9. v0 promotion 是否只覆盖 `hard_lease` goal?*拟议答案:是。`legacy` 或
`soft_claim` goal 先按附录 B 的静止规则切换模式;promotion 从不隐式改变模式。*
9. v0 promotion 是否只覆盖 `hard_lease` goal?*已决议:向后兼容的默认路径仍要求
源端已资格化为 `hard_lease`。受评审的 operator 可以显式选择 `preserve`,在不改变
policy 的情况下 canonicalize `legacy`/`soft_claim` Goal;也可以显式选择
`hard_lease`,在 fenced cutover 内完成唯一获支持的 claim-preserving 升级。该路径
不伪造 lease;其他 mode 变化继续使用附录 B 的静止规则。*
10. provider-first read flip 后,Markdown 与 lease 文件成为投影,kernel 禁止回退。
哪些数据进入 head,兼容视图如何渲染?*拟议答案:canonical Todo/lease manifest
中的每个字段都持久化在 head,包括 monitor、dependency、resume、decision、
Expand Down
71 changes: 71 additions & 0 deletions docs/reference/handoff-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,55 @@ The unpromoted scan retains its older materialized-state scope: it does not
claim to include event-only Todos. Its quiescence decision and the canonical
transaction now share one typed policy. No default mode changes.

## Preserve claims during authority promotion

The reviewed whole-Goal authority cutover has a narrower migration option for
an active Goal that cannot satisfy the ordinary quiescence rule:

```bash
# Keep legacy or soft_claim while changing only the storage authority.
loopx coordination-shadow promote --goal-id example-goal \
--minimum-operations 3 --require-event-kind todo_claim \
--handoff-mode-migration preserve

# Move legacy/soft_claim directly to hard_lease in the same reviewed cutover.
loopx coordination-shadow promote --goal-id example-goal \
--minimum-operations 3 --require-event-kind todo_claim \
--handoff-mode-migration hard_lease

# Apply only the exact plan returned by preview.
loopx coordination-shadow promote --goal-id example-goal \
--minimum-operations 3 --require-event-kind todo_claim \
--handoff-mode-migration hard_lease --execute
```

This is not a general mode-change bypass. The only explicit choices are
`preserve` and `hard_lease`; omitting the option retains the older requirement
that the qualified source already be `hard_lease`. The TypeScript promotion
transaction preserves every Todo, claim, lease record, receipt and validation
field. It validates live claim owners against the Goal agent registry and
retains an active lease only when its owner, Todo scopes, expiry, version and
epoch are safe. It never invents a lease for a preserved claim. After a direct
move to `hard_lease`, the same claim owner must acquire a fresh lease through
the ordinary atomic claim-and-lease path before protected work; another owner
remains rejected.

Preview reports the source revision/digest, target digest, preserved claims,
lease dispositions and conflicts. The target digest, selected migration and
registered-agent set enter the promotion-plan identity. Therefore an
interrupted cutover can recover only the same reviewed intent. The durable
legacy-writer fence blocks late old-session writes after cutover; a zero active
lease count alone is never treated as proof that no old Turn exists.

The CLI is the only mutation surface for this reviewed administrative action.
Managed Turns invoke that same CLI contract. Dashboard delegation preflight and
Lark/Chat remain read-only here: they already project `promotion_required` or
the promoted canonical authority and direct an operator to the reviewed
preview. The migration choice is one-shot operation intent, not Goal
configuration, so adding it to the capability editor would create a second
source of truth. After apply, all ordinary Todo/lease actions and receipts on
those surfaces read the same promoted projection.

## Recover a canonical request

Choose an operation ID before a canonical set if a lost response must be retried:
Expand Down Expand Up @@ -73,6 +122,28 @@ provider 失败明确报错,不回退旧文件。现有 Todo-section 投影不
并发修改使 CAS 冲突,不能在旧检查结果上继续切换。原 Todo、lease 和摘要不变。
未晋升路径仍仅扫描物化状态,不宣称覆盖 event-only Todo;两条路径共用 TS 切换规则。

对于无法清空活跃 claim 的 Goal,整 Goal authority 晋升提供一个更窄的显式迁移入口:
`--handoff-mode-migration preserve` 只切换存储权威并保留 `legacy`/`soft_claim`;
`--handoff-mode-migration hard_lease` 在同一受评审事务中直接迁到 `hard_lease`。
未传该参数时,继续沿用“源端已经是 `hard_lease`”的旧门禁。它不是通用 mode 绕过,
也不开放降级。

TypeScript 事务会原样保存 Todo、claim、lease record、receipt 与验证字段;校验活跃
claim owner 是否仍在 Goal agent registry 中,并且只有 owner、Todo scope、expiry、
version 与 epoch 都安全时才保留活跃 lease。迁到 `hard_lease` 不会为 claim 伪造
lease:原 owner 下一次受保护写入前,必须走正常的原子 claim+lease 路径取得新 lease,
异主仍被拒绝。preview 会给出源 revision/digest、目标 digest、保留 claim、lease
处置与冲突;这些内容进入 promotion-plan identity,所以中断后只能恢复完全相同的
评审意图。持久 legacy-writer fence 负责拦截旧 Turn 的迟到写入,不能用“当前 0 条
active lease”推断没有在途 Turn。

该受评审管理动作只有 CLI 一个写入口,managed Turn 也调用同一 CLI contract。
Dashboard 的 delegation preflight 与 Lark/Chat 在这里保持只读:它们已经投影
`promotion_required` 或晋升后的 canonical authority,并把 operator 引导到受评审
preview。migration choice 是单次 operation intent,不是 Goal 配置;把它再放进
capability editor 会制造第二个 truth source。apply 之后,各入口的普通 Todo/lease
动作与回执统一读取同一份 promoted projection。

需支持丢响应恢复时,在首次 canonical set 前指定 `--operation-id`,重试沿用同一
目标 mode 和 ID。不同 mode 复用 ID 会被拒绝;即使最初 mode 未变,也记录耐久回执。
若后来已切到其他 mode,旧请求重放只返回原回执,不把 mode 改回去;用 `show` 读当前值。
Expand Down
26 changes: 26 additions & 0 deletions loopx/cli_commands/coordination_shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from collections.abc import Callable
from pathlib import Path

from ..agent_registry import registered_agent_ids_for_goal

# The projection builder and lease loader are reached through this module by
# tests that seed and read the shadow through the command surface; keep them
# importable here even when the command does not call them directly.
Expand Down Expand Up @@ -108,6 +110,15 @@ def register_coordination_shadow_command(
default=[],
help="Required verified outbox write class; repeat for multiple classes.",
)
action.add_argument(
"--handoff-mode-migration",
choices=("preserve", "hard_lease"),
help=(
"Explicitly preserve the source handoff mode or migrate it to hard_lease "
"inside the reviewed authority cutover. Omit to retain the v0 requirement "
"that the source already uses hard_lease."
),
)
if name == "read-candidate":
action.add_argument(
"--todo-id",
Expand Down Expand Up @@ -329,12 +340,21 @@ def handle_coordination_shadow_command(
and read_candidate.get("decision_read_from_shadow") is False
)
if args.coordination_shadow_command == "promote":
registered_agents = registered_agent_ids_for_goal(goal)
operation_digest = _projection_version(
{
"goal_id": args.goal_id,
"projection": projection,
"minimum_operations": args.minimum_operations,
"required_event_kinds": args.require_event_kind,
**(
{
"handoff_mode_migration": args.handoff_mode_migration,
"registered_agents": registered_agents,
}
if args.handoff_mode_migration is not None
else {}
),
}
)
promotion = review_local_coordination_authority_promotion(
Expand All @@ -346,6 +366,12 @@ def handle_coordination_shadow_command(
source_snapshot=source_snapshot,
minimum_operations=args.minimum_operations,
required_event_kinds=args.require_event_kind,
handoff_mode_migration=args.handoff_mode_migration,
registered_agents=(
registered_agents
if args.handoff_mode_migration is not None
else None
),
execute=bool(args.execute),
)
payload["executed"] = bool(args.execute)
Expand Down
Loading
Loading