fix(reset): refuse every registration during a reset and purge only ids owned by the row's folder (#78)

Review round 2 (codex):
- R1: register_job reads .mam/reset-in-progress under the registry lock and
  refuses while a reset of the workspace owning <ws>/.mam/jobs is live, so the
  public registry.py register CLI and the API no longer bypass the gate. The
  reset's precheck takes that lock once after publishing its marker, so a
  registration is either seen by it or refused. The reset's own create passes
  MAM_RESET_OWNER_PID; the remote runner refuses the dispatch.
- R2: before each purge the reset re-runs stop's revalidate check
  (verify_session_uuid) for a post-discovered id against the row's folder and
  skips the target (id not verified) when it does not verify.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-30 03:45:27 +09:00
1 parent 18ea5f6184
commit 548c6854ea
7 files changed
+312 -33

No files matched your search

+33
View File
@@ -22,7 +22,9 @@ from typing import Dict, List, Mapping, Optional, Tuple
from lib_py.agents.base import AGENT_SESSION_ENV, POST_DISCOVERED
from lib_py.agents.registry import agent_of_row, get_adapter, own_key
from lib_py.delegation.mqtt_common import registry_lock
from lib_py.update import open_jobs, row_server
from lib_py.verify_session import verify_session_uuid
SPEC_VERSION = 1
SPEC_REL = os.path.join(".mam", "reset-spec.json")
@@ -482,6 +484,24 @@ def target_open_jobs(workspace: str, state: dict, env: Mapping[str, str], sessio
return out
def target_id_owned(state: dict, session_name: str) -> bool:
"""The row's recorded post-discovered id still verifies (revalidate) as a
conversation of the row's cwd - the check stop makes before it keeps such
an id. stop's own --purge-conversation also removes an id that no longer
verifies (a hand-run orphan cleanup); the reset purges only an owned one.
stop looks a pre-assigned id up in the cwd itself, so those pass."""
row = _rows_by_name(state).get(session_name)
agent = agent_of_row(row) if row else None
if not agent:
return False
if get_adapter(agent).id_lifecycle != POST_DISCOVERED:
return True
raw = row.get(own_key(agent))
own = raw.strip() if isinstance(raw, str) else ""
cwd = str((row.get("pane") or {}).get("cwd") or "")
return bool(own and cwd and verify_session_uuid(cwd, agent, own, row, mode="revalidate"))
# --------------------------------------------------------------------------- D1: entries shell lines
def format_entry_line(entry: dict, row: Optional[dict]) -> str:
@@ -737,6 +757,10 @@ def main(argv: Optional[List[str]] = None) -> int:
s.add_argument("--workspace", required=True)
s.add_argument("--session", required=True)
s = sub.add_parser("id-owned")
s.add_argument("--workspace", required=True)
s.add_argument("--session", required=True)
s = sub.add_parser("record-id")
s.add_argument("--workspace", required=True)
s.add_argument("--session", required=True)
@@ -790,6 +814,12 @@ def main(argv: Optional[List[str]] = None) -> int:
return 1 if sel["errors"] else 0
if a.cmd == "precheck":
# A registration that read no reset marker finishes writing under this
# lock before the open jobs are listed (registry.reset_refusal).
jobs_dir = os.path.join(ws, ".mam", "jobs")
if os.path.isdir(jobs_dir):
with registry_lock(jobs_dir):
pass
lines = precheck(ws, state, os.environ)
for line in lines:
print(line)
@@ -822,6 +852,9 @@ def main(argv: Optional[List[str]] = None) -> int:
print(line)
return 1 if lines else 0
if a.cmd == "id-owned":
return 0 if target_id_owned(state, a.session) else 1
if a.cmd == "record-id":
try:
record_old_id(ws, state, a.session)