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
224 changes: 224 additions & 0 deletions examples/bootstrap-command-pack-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@


REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))

from loopx.control_plane.testing.continuation_verb_guard import ( # noqa: E402
assert_no_continuation_verb,
)


def run_json(*args: str, env: dict[str, str] | None = None) -> dict[str, object]:
Expand Down Expand Up @@ -420,6 +426,220 @@ def test_start_goal_guided_previews_transaction_without_mutation() -> None:
assert_fixture_unchanged(snapshot)


def test_start_goal_guided_blocks_orphaned_goal_state() -> None:
"""A reset that deleted the registry entry must not reopen the same goal."""

with tempfile.TemporaryDirectory() as tmp:
project = Path(tmp) / "reset-project"
project.mkdir()
goal_id = "reset-goal"
snapshot = write_connected_goal_fixture(
project, goal_id=goal_id, agent_id="codex-retired"
)
state_file = project / ".codex" / "goals" / goal_id / "ACTIVE_GOAL_STATE.md"
registry = project / ".loopx" / "registry.json"
registry.write_text(
json.dumps({"schema_version": "0.1", "goals": []}, indent=2) + "\n",
encoding="utf-8",
)

payload = run_json(
"start-goal",
"--guided",
"--project",
str(project),
"--goal-id",
goal_id,
"--host-surface",
"codex-app",
"--goal-text",
"Continue the interrupted refactor",
)

connection = payload["project_connection"]
assert connection["connection_state"] == "orphaned_goal_state", connection
assert connection["goal_found"] is False, connection
assert connection["bootstrap_continuation_allowed"] is False, connection
assert connection["orphaned_goal_state"]["state_file_routes"] == [
f".codex/goals/{goal_id}/ACTIVE_GOAL_STATE.md"
], connection

transaction = payload["guided_transaction"]
assert transaction["blocked_by"] == "orphaned_goal_state", transaction
assert [step["id"] for step in transaction["ordered_steps"]] == [
"inspect_connection",
"resolve_orphaned_goal_state",
], transaction

gate = transaction["orphaned_goal_state_gate"]
assert gate["schema_version"] == "loopx_orphaned_goal_state_gate_v0", gate
assert gate["forbidden_until_resolved"] == [
"bootstrap",
"agent_registration",
"todo_write",
"quota_spend",
"host_loop_activation",
], gate
for route in gate["resolution_routes"]:
assert route["mutates"] is False, route
assert "--execute" not in route["command"], route
assert route["command"].splitlines()[-1].startswith("loopx "), route

commands = payload["command_pack"]["commands"]
for key in (
"goal_start_connect_if_needed",
"bootstrap_after_user_confirmation",
"goal_start_plan_prompt",
):
assert commands[key] is None, key

safety = payload["safety_contract"]
assert safety["force_bootstrap_allowed"] is False, safety
assert safety["writes_state_file"] is False, safety
assert safety["orphaned_goal_state_blocks_continuation"] is True, safety
assert_packet_summary_refs(
payload,
packet_kind="guided_start_goal",
compact_projection_default=True,
)
assert_fixture_unchanged({registry: registry.read_text(), state_file: snapshot[state_file]})


def test_start_goal_guided_fences_orphaned_state_for_every_absence_route() -> None:
"""No registry authority plus surviving state must fence, however that came about.

One real CLI run per absence shape: a deleted registry file, a registry that
declares no goal, and a registry that no longer parses each reach the fence
through a different return in the inspection.
"""

for shape, registry_text, absence in (
("missing", None, "not_connected"),
("empty", '{"schema_version": "0.1", "goals": []}\n', "registry_without_goal"),
("invalid", '{"schema_version": "0.1", "goals": [', "registry_invalid"),
):
with tempfile.TemporaryDirectory() as tmp:
project = Path(tmp) / "reset-project"
state_file = project / ".codex" / "goals" / "reset-goal" / "ACTIVE_GOAL_STATE.md"
state_file.parent.mkdir(parents=True)
state_text = "# Orphaned goal state written by a retired lane\n"
state_file.write_text(state_text, encoding="utf-8")
if registry_text is not None:
registry = project / ".loopx" / "registry.json"
registry.parent.mkdir(parents=True)
registry.write_text(registry_text, encoding="utf-8")

payload = run_json(
"start-goal",
"--guided",
"--project",
str(project),
"--goal-id",
"reset-goal",
"--host-surface",
"codex-app",
"--goal-text",
"Continue the interrupted refactor",
)

connection = payload["project_connection"]
assert connection["connection_state"] == "orphaned_goal_state", (shape, connection)
assert connection["absent_connection_state"] == absence, (shape, connection)
transaction = payload["guided_transaction"]
assert transaction["blocked_by"] == "orphaned_goal_state", (shape, transaction)
assert [step["id"] for step in transaction["ordered_steps"]] == [
"inspect_connection",
"resolve_orphaned_goal_state",
], (shape, transaction)
commands = payload["command_pack"]["commands"]
for key in (
"goal_start_connect_if_needed",
"goal_start_refresh_state",
"goal_start_host_loop_activation",
"goal_start_quota_should_run",
"goal_start_plan_prompt",
):
assert commands[key] is None, (shape, key)
assert state_file.read_text(encoding="utf-8") == state_text, shape


def test_unparseable_registry_without_orphaned_state_keeps_onboarding() -> None:
"""A broken registry is its own repair action; the fence must not stand in for it."""

with tempfile.TemporaryDirectory() as tmp:
project = Path(tmp) / "reset-project"
registry = project / ".loopx" / "registry.json"
registry.parent.mkdir(parents=True)
registry.write_text('{"schema_version": "0.1", "goals": [', encoding="utf-8")

payload = run_json(
"start-goal",
"--guided",
"--project",
str(project),
"--goal-id",
"reset-goal",
"--host-surface",
"codex-app",
"--goal-text",
"Continue the interrupted refactor",
)

connection = payload["project_connection"]
assert connection["connection_state"] == "registry_invalid", connection
assert "orphaned_goal_state" not in connection, connection
assert connection["reason"], connection
assert payload["command_pack"]["commands"]["goal_start_connect_if_needed"], payload


def test_fenced_project_surfaces_offer_no_continuation() -> None:
"""No real CLI surface over orphaned state may spell out a runnable mutation.

The guided packet with its full command pack, and the standalone command pack
with its rendered message, are both executed by hosts. Each carried
``register-agent --execute`` in nested fields the top-level fence never read.
"""

with tempfile.TemporaryDirectory() as tmp:
project = Path(tmp) / "reset-project"
state_file = project / ".codex" / "goals" / "reset-goal" / "ACTIVE_GOAL_STATE.md"
state_file.parent.mkdir(parents=True)
state_file.write_text(
"# Orphaned goal state written by a retired lane\n", encoding="utf-8"
)
guided = run_json(
"start-goal",
"--guided",
"--include-command-pack-detail",
"--project",
str(project),
"--goal-id",
"reset-goal",
"--host-surface",
"codex-app",
"--goal-text",
"Continue the interrupted refactor",
)
standalone = run_json(
"bootstrap-command-pack",
"--project",
str(project),
"--goal-id",
"reset-goal",
"--host-surface",
"codex-app",
)

assert guided["project_connection"]["connection_state"] == "orphaned_goal_state"
assert standalone["project_connection"]["connection_state"] == "orphaned_goal_state"
assert_no_continuation_verb(guided, source="guided with command pack detail")
assert_no_continuation_verb(standalone, source="standalone command pack")
# Without a surviving read-only route the guard above could pass on a
# packet that tells the operator nothing at all.
assert guided["command_pack"]["commands"]["status"]
assert state_file.is_file()


def test_start_goal_guided_requires_explicit_goal_for_multi_goal_project() -> None:
with tempfile.TemporaryDirectory() as tmp:
project = Path(tmp) / "multi-goal-project"
Expand Down Expand Up @@ -816,6 +1036,10 @@ def main() -> int:
test_missing_project_stops_before_mutation()
test_goal_text_invocation_plans_ranked_todos_before_activation()
test_start_goal_guided_previews_transaction_without_mutation()
test_start_goal_guided_blocks_orphaned_goal_state()
test_start_goal_guided_fences_orphaned_state_for_every_absence_route()
test_unparseable_registry_without_orphaned_state_keeps_onboarding()
test_fenced_project_surfaces_offer_no_continuation()
test_start_goal_guided_requires_explicit_goal_for_multi_goal_project()
test_connected_project_reuses_existing_state()
test_linked_git_worktree_reuses_canonical_source_registry()
Expand Down
77 changes: 43 additions & 34 deletions loopx/bootstrap_command_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
build_issue_fix_goal_command_templates,
)
from .control_plane.effect_program import effect_program_from_ordered_steps
from .control_plane.goals.orphaned_goal_state import (
ORPHANED_GOAL_STATE_CONNECTION,
absent_goal_connection,
fence_command_pack,
fenced_standalone_message,
guided_fence,
render_guided_lines,
)
from .control_plane.goals.start_contract import (
build_goal_start_contract,
build_goal_start_prompt,
Expand Down Expand Up @@ -571,30 +579,24 @@ def inspect_bootstrap_connection(
}

if registry_error:
return {
**base_connection,
"registry_exists": registry_exists,
"goal_id": inferred_goal_id,
"goal_found": False,
"state_file": str(state_file),
"state_file_exists": state_file.exists(),
"connection_state": "registry_invalid",
"mutation_confirmation_required": True,
"reason": registry_error,
}
return absent_goal_connection(
base_connection=base_connection,
goal_id=inferred_goal_id,
state_file=state_file,
registry_exists=registry_exists,
absence_connection="registry_invalid",
absence_reason=registry_error,
)

if not registry:
return {
**base_connection,
"registry_exists": False,
"goal_id": inferred_goal_id,
"goal_found": False,
"state_file": str(state_file),
"state_file_exists": state_file.exists(),
"connection_state": "not_connected",
"mutation_confirmation_required": True,
"reason": "project-local .loopx/registry.json is missing",
}
return absent_goal_connection(
base_connection=base_connection,
goal_id=inferred_goal_id,
state_file=state_file,
registry_exists=False,
absence_connection="not_connected",
absence_reason="project-local .loopx/registry.json is missing",
)

goals = registry_goals(registry)
selected_goal_id, selected_goal = _select_goal(goals, goal_id)
Expand All @@ -608,18 +610,15 @@ def inspect_bootstrap_connection(
state_file = goal_state_file or fallback_state_file

if selected_goal is None:
return {
**base_connection,
"registry_exists": True,
"goal_id": resolved_goal_id,
"goal_found": False,
"known_goal_ids": [str(goal.get("id")) for goal in goals],
"state_file": str(state_file),
"state_file_exists": state_file.exists(),
"connection_state": "registry_without_goal",
"mutation_confirmation_required": True,
"reason": "registry exists but no matching goal entry was found",
}
return absent_goal_connection(
base_connection=base_connection,
goal_id=resolved_goal_id,
state_file=state_file,
registry_exists=True,
absence_connection="registry_without_goal",
absence_reason="registry exists but no matching goal entry was found",
known_goal_ids=[str(goal.get("id")) for goal in goals],
)

if not selected_goal.get("state_file"):
return {
Expand Down Expand Up @@ -1076,6 +1075,7 @@ def build_loopx_bootstrap_command_pack(
"host_loop_activation_allowed": activation_allowed,
},
}
fence_command_pack(payload, command_prefix=command_prefix)
if normalized_thread_id:
payload["thread_id"] = normalized_thread_id
payload["thread_agent_binding"] = thread_binding_projection
Expand Down Expand Up @@ -1709,6 +1709,10 @@ def rerun_start_goal(selected_agent_id: str) -> str:
detail_command=detail_command,
)
)
orphaned_gate = command_pack.get("orphaned_goal_state")
if isinstance(orphaned_gate, dict):
guided_transaction.update(guided_fence(orphaned_gate))
guided_transaction.pop("identity_selection_gate", None)
payload = {
"ok": True,
"schema_version": GUIDED_START_SCHEMA_VERSION,
Expand All @@ -1731,6 +1735,7 @@ def rerun_start_goal(selected_agent_id: str) -> str:
"spends_quota": False,
"mutation_commands_are_previewed": True,
"force_bootstrap_allowed": False,
"orphaned_goal_state_blocks_continuation": isinstance(orphaned_gate, dict),
},
}
if command_pack.get("thread_id"):
Expand Down Expand Up @@ -1887,6 +1892,7 @@ def actionable_shell_command(value: Any) -> str:
+ "\n".join(choices)
+ "\n"
)
orphan_gate_lines = render_guided_lines(transaction)
host_gate = transaction.get("host_surface_selection_gate")
host_gate = host_gate if isinstance(host_gate, dict) else {}
host_gate_lines = ""
Expand Down Expand Up @@ -1916,6 +1922,7 @@ def actionable_shell_command(value: Any) -> str:
{chr(10).join(step_lines)}
{host_gate_lines}
{goal_gate_lines}
{orphan_gate_lines}
{identity_gate_lines}

## Todo Preservation
Expand All @@ -1927,6 +1934,8 @@ def actionable_shell_command(value: Any) -> str:
def render_loopx_bootstrap_command_pack_message(payload: dict[str, Any]) -> str:
connection = payload.get("project_connection")
connection = connection if isinstance(connection, dict) else {}
if connection.get("connection_state") == ORPHANED_GOAL_STATE_CONNECTION:
return fenced_standalone_message(payload)
commands = payload.get("commands")
commands = commands if isinstance(commands, dict) else {}
next_step = payload.get("recommended_next_step")
Expand Down
Loading
Loading