fix(delegation): the sibling rule pairs with the registry's real path (Issue #57, Mac R2)
A registry directory that is itself a symlink got two audit logs: submit resolved --registry-dir with pwd -P and wrote next to the target, while logs derived the sibling from the link path. sibling_logs_dir() now takes the registry's realpath; an explicit DELEGATE_JOB_LOGS_DIR still wins. Also: forwarded_keys() normalises both sides of its comparison, and the design doc's status row lists the audit log next to the registry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c598bb546
commit
a7cb90e63a
4 files changed
+45
-5
No files matched your search
@@ -79,7 +79,8 @@ def forwarded_keys(desc: dict) -> list:
|
|||||||
when it is the sibling of the registry and so derivable there (Issue #57).
|
when it is the sibling of the registry and so derivable there (Issue #57).
|
||||||
An explicit, non-sibling root is forwarded as before."""
|
An explicit, non-sibling root is forwarded as before."""
|
||||||
keys = list(KEYS)
|
keys = list(KEYS)
|
||||||
if desc.get("delegate_job_logs_dir") == _abs(sibling_logs_dir(desc["mam_jobs_dir"])):
|
logs = desc.get("delegate_job_logs_dir")
|
||||||
|
if logs and _abs(logs) == _abs(sibling_logs_dir(desc["mam_jobs_dir"])):
|
||||||
keys.remove("delegate_job_logs_dir")
|
keys.remove("delegate_job_logs_dir")
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,16 @@ def default_registry_dir() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def sibling_logs_dir(registry_dir: str) -> str:
|
def sibling_logs_dir(registry_dir: str) -> str:
|
||||||
"""The audit-log root that pairs with ``registry_dir``: its sibling
|
"""The audit-log root that pairs with ``registry_dir``: the
|
||||||
``delegate_job_logs``. Pure; reads no environment. Issue #57."""
|
``delegate_job_logs`` next to it. Pure; reads no environment. Issue #57.
|
||||||
return os.path.join(os.path.dirname(os.path.abspath(registry_dir)), "delegate_job_logs")
|
|
||||||
|
The registry is taken by its real path: a registry directory that is itself
|
||||||
|
a symlink pairs with the audit log next to its target, which is what the
|
||||||
|
wrapper (``pwd -P``) and the control-plane binding (``realpath``) already
|
||||||
|
see, so submit and logs agree (Mac review R2). A relative registry resolves
|
||||||
|
against the cwd, as registry.py opens it.
|
||||||
|
"""
|
||||||
|
return os.path.join(os.path.dirname(os.path.realpath(registry_dir)), "delegate_job_logs")
|
||||||
|
|
||||||
|
|
||||||
def get_logs_dir(registry_dir: Optional[str] = None) -> str:
|
def get_logs_dir(registry_dir: Optional[str] = None) -> str:
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ bash "$MAIN/.agents/skills/multi-agent-mux-stop/scripts/worktree_remove.sh" --pa
|
|||||||
| create | env 에서 결정 → 행 `control_plane` 에 항상 영속. `--workspace` 기본값과 다르면 새 pane 의 생성 호출(`pane split`/`workspace create`)에 바인딩(유도 가능한 감사 로그 루트 제외)을 `--env` 로 전파한다. 전파가 필요하면 idle pane 을 재사용하지 않는다(`agent start -- <args>` 는 `--` 뒤를 에이전트 인자로 넘기므로 `env K=V` 를 쓸 수 없다). 전파가 필요한데 경로에 공백이 있으면 exit 2. `~/.local/bin` wrapper 경로는 기록만 하고 경고 | pane.cwd = `--workspace`. 바인딩 계산 실패 시 워크트리면 exit 2, 제어 루트면 경고 | — (루프 A: 즉시 reconcile·watchdog 은 제어 루트 코드와 `WORKSPACE_ROOT` 로 기동, prepare 가 `MAM_NO_SUBSCRIBER_DAEMON=1`) |
|
| create | env 에서 결정 → 행 `control_plane` 에 항상 영속. `--workspace` 기본값과 다르면 새 pane 의 생성 호출(`pane split`/`workspace create`)에 바인딩(유도 가능한 감사 로그 루트 제외)을 `--env` 로 전파한다. 전파가 필요하면 idle pane 을 재사용하지 않는다(`agent start -- <args>` 는 `--` 뒤를 에이전트 인자로 넘기므로 `env K=V` 를 쓸 수 없다). 전파가 필요한데 경로에 공백이 있으면 exit 2. `~/.local/bin` wrapper 경로는 기록만 하고 경고 | pane.cwd = `--workspace`. 바인딩 계산 실패 시 워크트리면 exit 2, 제어 루트면 경고 | — (루프 A: 즉시 reconcile·watchdog 은 제어 루트 코드와 `WORKSPACE_ROOT` 로 기동, prepare 가 `MAM_NO_SUBSCRIBER_DAEMON=1`) |
|
||||||
| resume | 행 바인딩 우선(호출자 env 와 다르면 stderr 경고) 재전파. 바인딩 없는 기존 행은 호출자 바인딩으로 채움. 이미 살아 있는 세션에도 바인딩을 영속한다. 계산 실패 시 워크트리면 exit 2 | 원래 cwd 로만: 경로 불일치·부재 시 exit 2 | 워크트리 삭제 시 재개 불가(대화 보존, 같은 경로에 다시 만들면 재개) |
|
| resume | 행 바인딩 우선(호출자 env 와 다르면 stderr 경고) 재전파. 바인딩 없는 기존 행은 호출자 바인딩으로 채움. 이미 살아 있는 세션에도 바인딩을 영속한다. 계산 실패 시 워크트리면 exit 2 | 원래 cwd 로만: 경로 불일치·부재 시 exit 2 | 워크트리 삭제 시 재개 불가(대화 보존, 같은 경로에 다시 만들면 재개) |
|
||||||
| stop | `MAM_JOBS_DIR` > 행의 `mam_jobs_dir` > `$WORKSPACE_ROOT/.mam/jobs` 순으로 잡 조회·발행. 감사 로그도 `DELEGATE_JOB_LOGS_DIR` > 행의 `delegate_job_logs_dir` 순 | 행 cwd 로 uuid 캡처·purge. 워크트리 해체는 `worktree_remove.sh` 가 판정(잠금·세션·잡·청결·서브모듈) 뒤에만 정지·제거, 서브모듈 워크트리는 검증 후 `--force` 1회 | — |
|
| stop | `MAM_JOBS_DIR` > 행의 `mam_jobs_dir` > `$WORKSPACE_ROOT/.mam/jobs` 순으로 잡 조회·발행. 감사 로그도 `DELEGATE_JOB_LOGS_DIR` > 행의 `delegate_job_logs_dir` 순 | 행 cwd 로 uuid 캡처·purge. 워크트리 해체는 `worktree_remove.sh` 가 판정(잠금·세션·잡·청결·서브모듈) 뒤에만 정지·제거, 서브모듈 워크트리는 검증 후 `--force` 1회 | — |
|
||||||
| status | `MAM_JOBS_DIR`·행 `mam_jobs_dir`·감사 로그(`DELEGATE_JOB_LOGS_DIR`·행 `delegate_job_logs_dir`)·기존 후보 순 조회 | 행 cwd 로 대화 키 | — |
|
| status | `MAM_JOBS_DIR`·행 `mam_jobs_dir`·감사 로그(`DELEGATE_JOB_LOGS_DIR`·행 `delegate_job_logs_dir`)·레지스트리 옆 감사 로그(`MAM_JOBS_DIR`·행 `mam_jobs_dir` 의 형제 `delegate_job_logs`, #57)·기존 후보 순 조회 | 행 cwd 로 대화 키 | — |
|
||||||
| monitor/reconcile | `MAM_JOBS_DIR` 우선(정적 가드로만 검증) | 기존 행은 행 cwd 로 검증 | 루트 밖 cwd 의 미등록 세션은 자동 등록 안 됨 → create/prepare 로만 등록 (남은 제한) |
|
| monitor/reconcile | `MAM_JOBS_DIR` 우선(정적 가드로만 검증) | 기존 행은 행 cwd 로 검증 | 루트 밖 cwd 의 미등록 세션은 자동 등록 안 됨 → create/prepare 로만 등록 (남은 제한) |
|
||||||
| delegate-job | `--registry-dir` > `MAM_JOBS_DIR` > `.mam/jobs`(`--workdir` 기준), 모두 절대화. 발행 명령이 감사 로그 루트(`DELEGATE_JOB_LOGS_DIR`)를 고정 | 문구·명령·리포트 경로 절대 | 작은따옴표가 든 경로는 지원 범위 밖 |
|
| delegate-job | `--registry-dir` > `MAM_JOBS_DIR` > `.mam/jobs`(`--workdir` 기준), 모두 절대화. 발행 명령이 감사 로그 루트(`DELEGATE_JOB_LOGS_DIR`)를 고정 | 문구·명령·리포트 경로 절대 | 작은따옴표가 든 경로는 지원 범위 밖 |
|
||||||
| loop | `MAM_JOBS_DIR` 존중, 래퍼에 `--registry-dir` 명시. `--worktree` 로 소스 루트 분리 | diff·base·head 만 소스 루트. **구현이 루프 밖이면 `--base <구현 전 sha> --review-only` 필수**. `--worktree`/`--base` 가 있으면 빈 변경분 거부 | 루프 하나(8). 원격 리뷰어는 identity 모드에서 거부(기본) 또는 diff-only(`--remote-review diff-only`); 원격 리뷰어가 트리를 검증하게 하는 스냅샷 공유(공유 원격의 `refs/mam/review/<loop_id>/<n>`)는 범위 밖. 레거시 셸 루프(`MAM_LOOP_PY=0`)는 새 옵션 거부 |
|
| loop | `MAM_JOBS_DIR` 존중, 래퍼에 `--registry-dir` 명시. `--worktree` 로 소스 루트 분리 | diff·base·head 만 소스 루트. **구현이 루프 밖이면 `--base <구현 전 sha> --review-only` 필수**. `--worktree`/`--base` 가 있으면 빈 변경분 거부 | 루프 하나(8). 원격 리뷰어는 identity 모드에서 거부(기본) 또는 diff-only(`--remote-review diff-only`); 원격 리뷰어가 트리를 검증하게 하는 스냅샷 공유(공유 원격의 `refs/mam/review/<loop_id>/<n>`)는 범위 밖. 레거시 셸 루프(`MAM_LOOP_PY=0`)는 새 옵션 거부 |
|
||||||
|
|||||||
@@ -599,3 +599,35 @@ def test_disk_fallback_rejects_a_terminal_that_belongs_to_a_later_dispatch(moved
|
|||||||
pending, terminal = {jid}, {}
|
pending, terminal = {jid}, {}
|
||||||
job_subscriber._check_disk_fallback(pending, str(reg), terminal, {jid: tok_a}, {jid: 1})
|
job_subscriber._check_disk_fallback(pending, str(reg), terminal, {jid: tok_a}, {jid: 1})
|
||||||
assert pending == {jid} and terminal == {}
|
assert pending == {jid} and terminal == {}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mac review R2: a registry directory that is itself a symlink has one audit log
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_a_symlinked_registry_dir_resolves_to_one_audit_log(tmp_path, monkeypatch):
|
||||||
|
"""submit resolves --registry-dir with pwd -P, so its audit log lands next
|
||||||
|
to the link's target; the resolver must pair the same place for logs."""
|
||||||
|
from lib_py.delegation import logroot
|
||||||
|
for k in DROP:
|
||||||
|
monkeypatch.delenv(k, raising=False)
|
||||||
|
ws = tmp_path / "ws"
|
||||||
|
(ws / ".mam").mkdir(parents=True)
|
||||||
|
monkeypatch.setenv("WORKSPACE_ROOT", str(ws))
|
||||||
|
actual = tmp_path / "actual" / "jobs"
|
||||||
|
actual.mkdir(parents=True)
|
||||||
|
alias = tmp_path / "alias"
|
||||||
|
alias.mkdir()
|
||||||
|
(alias / "jobs").symlink_to(actual)
|
||||||
|
real_actual = Path(os.path.realpath(tmp_path / "actual"))
|
||||||
|
assert logroot.sibling_logs_dir(str(alias / "jobs")) == str(real_actual / "delegate_job_logs")
|
||||||
|
res = _wrapper(ws, "submit", "--dry-run", "--agent", "human", "--registry-dir", str(alias / "jobs"),
|
||||||
|
"--workdir", str(ws), "--prompt", "t")
|
||||||
|
assert res.returncode == 0, res.stdout[-800:] + res.stderr[-800:]
|
||||||
|
jid = _only_job_id(actual)
|
||||||
|
assert res.stdout.strip().splitlines()[-1] == str(real_actual / "delegate_job_logs" / jid)
|
||||||
|
assert (real_actual / "delegate_job_logs" / jid / "meta.json").is_file()
|
||||||
|
assert not (tmp_path / "alias" / "delegate_job_logs").exists()
|
||||||
|
res = _wrapper(ws, "logs", "--registry-dir", str(alias / "jobs"), jid)
|
||||||
|
assert res.returncode == 0 and "registered" in res.stdout, res.stdout + res.stderr
|
||||||
|
assert not (ws / ".mam" / "delegate_job_logs").exists()
|
||||||
Reference in new issue
Block a user