Skip to content
Merged
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 @@ -2560,14 +2560,15 @@ for command inventory, update/monitor transactions and consumer deletion. Do not
repeat that plan in a second implementation or treat a merged read-policy PR
as storage readiness. Its T0 checkpoint is the entry condition for these cards.

Lifecycle admission and the preauthorized terminal fence now share the TS
owner across legacy writers and native terminal transactions; the replaced
Python rules are removed without changing provider defaults or promotion.
This is not full native field-edit support: retain the strict text/note
transaction boundary until update's fields, ownership, validation and
monitor/resume effects close together. Neither an admission result nor a
lease-fence result is a commit receipt. Keep provider CAS/replay and existing
writer lock lifetimes unchanged while collecting this deletion payoff.
Lifecycle admission shares the TS owner across legacy writers and native
transactions. Native text/note edits and terminal transitions reuse the
preauthorized lease fence in-process; the replaced Python rules and the
callerless standalone effect-runtime wire are removed without changing provider
defaults or promotion. This remains a strict text/note transaction boundary,
not general native metadata support, until update's fields, ownership,
validation and monitor/resume effects close together. Neither an admission
result nor a lease-fence result is a commit receipt. Keep provider CAS/replay
and existing writer lock lifetimes unchanged while collecting this deletion payoff.
Waiting/resume lane selection is now one TS read-policy owner shared by quota,
vision-wait, agent-scope and replan. The obsolete Python selector module is
deleted; the adapter accepts the same canonical summary after promotion and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2033,11 +2033,13 @@ D1–D3 资格化和 T1/T2 条件保持不变。
推进,不在这里复制第二套实现路线,也不把 read-policy PR 合并视为存储就绪。
进入本节前先完成 T0 基线核对。

Lifecycle 准入及预授权 terminal fence 现由 legacy writer 与 native terminal
transaction 共用 TS owner;删除对应 Python 规则,不改变 provider 默认或 promotion。
这不是完整 native 字段编辑:在 update 的字段、ownership、validation 和 monitor/resume
effect 一起闭合前,保留严格 text/note 事务边界。准入结果和 lease-fence 结果都不是
commit receipt;兑现删除收益时,provider CAS/replay 与既有 writer 持锁生命周期不变。
Lifecycle 准入由 legacy writer 与 native transaction 共用 TS owner。Native
text/note 编辑与 terminal transition 在进程内复用预授权 lease fence;删除对应
Python 规则和无调用方的独立 effect-runtime wire,不改变 provider 默认或 promotion。
在 update 的字段、ownership、validation 和 monitor/resume effect 一起闭合前,
这仍是严格 text/note 事务边界,不是通用 native metadata 支持。准入结果和
lease-fence 结果都不是 commit receipt;兑现删除收益时,provider CAS/replay 与既有
writer 持锁生命周期不变。
等待/恢复 lane 选择现由 quota、vision-wait、agent-scope、replan 共用一个 TS 读取
策略 owner,删除旧 Python selector 模块。适配层在 promotion 后消费同一 canonical
summary,之前消费 legacy summary;真实 CLI 覆盖容量变化和 promoted display
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,11 @@ An input adapter or external-effect executor may remain Python.

The lifecycle-admission slice now uses `todo_lifecycle_decision.ts` for legacy
claim/update admission, delegated action/reason checks, ownership-holder routing,
and the preauthorized terminal fence, alongside native complete/supersede.
`authority_core.py` projects results rather than retaining those decisions.
The terminal wire contract stays terminal-only; mutation admission cannot complete
a Todo, and a standalone fence neither grants actor authority nor completes it.
and native complete/supersede. Native text/note edits and terminal transitions
reuse the preauthorized lease fence in-process. `authority_core.py` projects only
the live admission and terminal decisions; there is no standalone Python command
or effect-runtime handler for the fence. Mutation admission cannot complete a
Todo, and the in-process fence cannot grant actor authority or commit a change.
This deletes duplicate rules now, **not** the complete legacy update writer.
Field patches, omission/clear semantics, monitor/resume effects and validation
still need one complete update transaction before the writer can retire. Legacy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,12 @@ Markdown renderer 长期保留。
全部改成 TypeScript 或 `loopxd` 落地为前提;输入适配和外部 effect 执行可以保留 Python。

本次 lifecycle-admission 切片将 legacy claim/update 准入、委托 action/reason 检查、
ownership-holder 路由及预授权 terminal fence 统一到 `todo_lifecycle_decision.ts`,
与 native complete/supersede 共用规则;`authority_core.py` 只投影这些决策结果。
Terminal wire 合同仍只接受 terminal 命令;mutation admission 不能完成 Todo,
独立 fence 不能授予 actor 权限或完成 Todo。这立即删除重复规则,**不等于删除完整
legacy update writer**。字段 patch、省略/清空、monitor/resume effect 和 validation
ownership-holder 路由及 native complete/supersede 统一到
`todo_lifecycle_decision.ts`。Native text/note 编辑与 terminal transition 在进程内
复用预授权 lease fence;`authority_core.py` 只投影仍有真实调用方的准入和 terminal
决策,不再暴露独立 Python command 或 effect-runtime handler。Mutation admission
不能完成 Todo,进程内 fence 不能授予 actor 权限或提交变更。这立即删除重复规则,
**不等于删除完整 legacy update writer**。字段 patch、省略/清空、monitor/resume effect 和 validation
仍需收口为完整 update transaction。Legacy 准入及持锁 gate 仍跨 runtime;本次减少
语义 owner,不宣称减少 crossings,native transaction 仍进程内调用。下一步将这些
crossing 一起折叠进完整事务,不能沿着 adapter 逐字段继续加桥。
Expand Down
60 changes: 0 additions & 60 deletions loopx/control_plane/coordination/authority_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,18 +176,6 @@ class LeaseModeGateCommand:
action: LeaseAction


@dataclass(frozen=True)
class TerminalFenceCommand:
"""Verify the lease side of an already-authorized terminal mutation."""

actor_agent_id: str | None
lease_idempotency_key: str | None = None
lease_expected_version: int | None = None
delegated_authority: bool = False
allow_user_gate_auto_acquire: bool = False
require_active_when_fence_supplied: bool = True


@dataclass(frozen=True)
class HandoffModeTransitionCommand:
requested_mode: HandoffMode
Expand All @@ -201,7 +189,6 @@ class HandoffModeTransitionCommand:
| LeaseReleaseCommand
| LeaseOwnerEligibilityCommand
| LeaseModeGateCommand
| TerminalFenceCommand
| HandoffModeTransitionCommand
)

Expand Down Expand Up @@ -489,51 +476,6 @@ def ownership_gate_requirement(
return OwnershipGate(payload["ownership_gate"])


def _typescript_terminal_fence(
snapshot: CoordinationSnapshot,
command: TerminalFenceCommand,
) -> TransitionPlan:
if snapshot.todo is None:
return _result(DecisionOutcome.REJECTED, "todo_not_found")
payload = effect_runtime_result(
"task_lease.terminal_fence.decide",
{
"schema_version": "loopx_coordination_terminal_fence_request_v0",
"todo": _todo_fact_payload(snapshot.todo),
"lease": _lease_fact_payload(snapshot.lease),
"registered_agents": list(snapshot.registered_agents),
"handoff_mode": snapshot.handoff_mode.value,
"actor_agent_id": command.actor_agent_id,
"lease_idempotency_key": command.lease_idempotency_key,
"lease_expected_version": command.lease_expected_version,
"delegated_authority": command.delegated_authority,
"allow_user_gate_auto_acquire": command.allow_user_gate_auto_acquire,
"require_active_when_fence_supplied": command.require_active_when_fence_supplied,
},
)
if not isinstance(payload, dict) or payload.get("schema_version") != (
"loopx_coordination_terminal_fence_result_v0"
):
raise RuntimeError("TypeScript terminal fence result shape mismatch")
outcome = DecisionOutcome(payload["outcome"])
next_snapshot = None
if outcome is DecisionOutcome.APPLY:
next_snapshot = replace(
snapshot,
lease=(
snapshot.lease
if payload["next_lease"] is None
else _lease_fact_from_payload(payload["next_lease"])
),
)
return TransitionPlan(
outcome=outcome,
code=payload["code"],
next_snapshot=next_snapshot,
lease_fence=LeaseFence(payload["lease_fence"]),
)


def _lease_handoff_rejection(snapshot: CoordinationSnapshot) -> str | None:
if snapshot.handoff_mode is HandoffMode.SOFT_CLAIM:
return "handoff_mode_forbids_lease"
Expand Down Expand Up @@ -902,8 +844,6 @@ def decide(
return _decide_lease_owner_eligibility(snapshot, command)
if isinstance(command, LeaseModeGateCommand):
return _decide_lease_mode_gate(snapshot, command)
if isinstance(command, TerminalFenceCommand):
return _typescript_terminal_fence(snapshot, command)
if isinstance(command, HandoffModeTransitionCommand):
return _decide_handoff_transition(snapshot, command)
raise TypeError(f"unsupported coordination command: {type(command).__name__}")
2 changes: 0 additions & 2 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ import { evaluateCoordinationTodoClaimDecision } from "./coordination/todo_claim
import {
evaluateCoordinationTodoTerminalDecision,
evaluateCoordinationTodoMutationDecision,
evaluateCoordinationTerminalFence,
evaluateTodoOwnershipGate,
} from "./coordination/todo_lifecycle_decision.ts";
import { evaluateCoordinationTodoArchiveSelection } from "./coordination/todo_archive_selection.ts";
Expand Down Expand Up @@ -397,7 +396,6 @@ export function createEffectRuntimeHandlers(
],
["todo.terminal.decide", evaluateCoordinationTodoTerminalDecision],
["todo.mutation.decide", evaluateCoordinationTodoMutationDecision],
["task_lease.terminal_fence.decide", evaluateCoordinationTerminalFence],
["todo.ownership_gate.decide", evaluateTodoOwnershipGate],
["todo.archive.select", evaluateCoordinationTodoArchiveSelection],
["todo.successor.derive", evaluateCoordinationTodoSuccessorDerivation],
Expand Down
102 changes: 0 additions & 102 deletions tests/control_plane/test_coordination_authority_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
LifecycleGrant,
OtherLeaseSnapshot,
OwnershipGate,
TerminalFenceCommand,
TodoAction,
TodoMutationCommand,
TodoSnapshot,
Expand Down Expand Up @@ -118,26 +117,6 @@ def test_update_authority_keeps_claim_neutral_edits_separate_from_ownership(
assert result.authority_mode == "registered_peer_actor"


@pytest.mark.parametrize("strict", [False, True])
def test_standalone_fence_does_not_complete_todo_or_reauthorize_the_actor(strict):
state = snapshot(todo=todo(claimed_by=AGENT_B))
result = decide(
state,
TerminalFenceCommand(
actor_agent_id=None,
lease_idempotency_key="stale-key",
require_active_when_fence_supplied=strict,
),
)
assert result.authority_mode is None
if strict:
assert result.code == "lease_not_active"
assert result.next_snapshot is None
else:
assert result.code == "terminal_fence_not_required"
assert result.next_snapshot == state


@pytest.mark.parametrize("clear", [False, True])
def test_delegated_update_requires_the_actual_action_and_never_releases_holder(clear):
state = snapshot(
Expand Down Expand Up @@ -402,75 +381,6 @@ def test_exact_user_gate_can_plan_auto_acquire_but_never_displaces_a_live_lease(
assert foreign_live.code == "lease_fence_required"


def test_terminal_entrypoints_share_user_gate_auto_acquire_policy() -> None:
state = snapshot(
handoff_mode=HandoffMode.HARD_LEASE,
registered_agents=(AGENT_A,),
todo=todo(role="user", task_class="user_gate"),
)
full = decide(
state,
terminal(
lease_idempotency_key="auto-turn-key",
allow_user_gate_auto_acquire=True,
),
)
fence = decide(
state,
TerminalFenceCommand(
actor_agent_id=AGENT_A,
lease_idempotency_key="auto-turn-key",
allow_user_gate_auto_acquire=True,
require_active_when_fence_supplied=False,
),
)

assert full.outcome is fence.outcome is DecisionOutcome.APPLY
assert full.lease_fence is fence.lease_fence is LeaseFence.AUTO_ACQUIRE
assert full.next_snapshot is not None and fence.next_snapshot is not None
assert full.next_snapshot.lease == fence.next_snapshot.lease


def test_preauthorized_terminal_fence_preserves_mode_and_delegation_rules() -> None:
legacy = decide(
snapshot(),
TerminalFenceCommand(actor_agent_id=AGENT_A),
)
assert legacy.outcome is DecisionOutcome.APPLY
assert legacy.lease_fence is LeaseFence.NOT_REQUIRED

hard_missing = decide(
snapshot(handoff_mode=HandoffMode.HARD_LEASE),
TerminalFenceCommand(actor_agent_id=AGENT_A),
)
assert hard_missing.outcome is DecisionOutcome.REJECTED
assert hard_missing.code == "handoff_mode_requires_lease"

delegated = decide(
snapshot(handoff_mode=HandoffMode.HARD_LEASE),
TerminalFenceCommand(
actor_agent_id=ORCHESTRATOR,
delegated_authority=True,
),
)
assert delegated.outcome is DecisionOutcome.APPLY
assert delegated.lease_fence is LeaseFence.DELEGATED_OVERRIDE

verified = decide(
snapshot(handoff_mode=HandoffMode.HARD_LEASE, lease=lease()),
TerminalFenceCommand(
actor_agent_id=AGENT_A,
lease_idempotency_key="execution-a",
lease_expected_version=3,
),
)
assert verified.outcome is DecisionOutcome.APPLY
assert verified.lease_fence is LeaseFence.REQUIRED
assert verified.next_snapshot is not None
assert verified.next_snapshot.lease is not None
assert verified.next_snapshot.lease.status == "released"


@pytest.mark.parametrize(
("target", "owner", "code"),
[
Expand Down Expand Up @@ -734,18 +644,6 @@ def test_contradictory_normalized_lease_state_fails_closed(
assert plan.code == "invalid_lease_snapshot"


def test_active_terminal_fence_requires_current_version_after_key_match() -> None:
plan = decide(
snapshot(handoff_mode=HandoffMode.HARD_LEASE, lease=lease()),
TerminalFenceCommand(
actor_agent_id=AGENT_A,
lease_idempotency_key="execution-a",
),
)
assert plan.outcome is DecisionOutcome.REJECTED
assert plan.code == "version_required"


def test_soft_claim_forbids_lease_mutation_but_allows_release() -> None:
state = snapshot(handoff_mode=HandoffMode.SOFT_CLAIM, lease=lease())
acquire = decide(
Expand Down
11 changes: 11 additions & 0 deletions tests/control_plane_ts/effect_runtime_handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,14 @@ test("completion policy has no standalone runtime handler", async () => {
/unsupported Effect runtime method/,
);
});

test("preauthorized lease fence has no standalone runtime handler", async () => {
await assert.rejects(
dispatchEffectRuntimeMethod(
handlers,
"task_lease.terminal_fence.decide",
{},
),
/unsupported Effect runtime method/,
);
});
2 changes: 1 addition & 1 deletion tests/control_plane_ts/todo_terminal_decision.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ test("executor reclaim remains internal and preserves actor rejection precedence
})));
});

test("standalone fence is preauthorized and never completes or attributes a Todo", () => {
test("in-process preauthorized fence never completes or attributes a Todo", () => {
const base = request({ schema_version: COORDINATION_TERMINAL_FENCE_REQUEST_SCHEMA,
actor_agent_id: null, delegated_authority: false, require_active_when_fence_supplied: false,
lease_idempotency_key: "old-key" });
Expand Down