fix(delegation): the path rules live in a stdlib-only module; the package imports without paho (Issue #57, Mac F1)

control_plane and status.sh imported sibling_logs_dir from mqtt_common,
which imports paho at module level, so create/resume's binding and status
died on an interpreter without paho-mqtt (_delegate_py_bin's last fallback
is the PATH's python3). The registry/audit-log path rules move to
lib_py/delegation/logroot.py (standard library only) and mqtt_common
re-exports them. The package's own import chain also reached paho through
service -> publish_event -> publisher, so mqtt_common and publisher import
paho tolerantly and make_client() refuses with a clear error when a broker
is actually needed. control_plane, status.sh, lib.sh's delegate_publish_cmd
and delegate_logs_dir, and the wrapper's _logs_dir_abs import from logroot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-10-15 00:02:42 +00:00
1 parent 63d816468e
commit 75c004222c
8 files changed
+145 -60

No files matched your search

@@ -0,0 +1,63 @@
"""Where the job registry and its audit log live (Issue #15, Issue #57).
Standard library only, on purpose: create/resume's control-plane binding,
status.sh and lib.sh resolve these paths through ``_delegate_py_bin``, whose
last fallback is the PATH's ``python3``, which may not have paho-mqtt.
``mqtt_common`` re-exports every name here, so existing callers are unchanged.
"""
from __future__ import annotations
import os
from typing import Optional
DEFAULT_REGISTRY_DIR = ".mam/jobs"
def _workspace_mam_dir(leaf: str) -> Optional[str]:
"""`$WORKSPACE_ROOT/.mam/<leaf>` when lib.sh has told us which workspace we
serve; None otherwise."""
root = os.environ.get("WORKSPACE_ROOT")
if root and root.strip() and os.path.isdir(root):
return os.path.join(root, ".mam", leaf)
return None
def default_registry_dir() -> str:
"""Registry root, resolved at call time (Issue #15).
``MAM_JOBS_DIR`` wins; otherwise the workspace lib.sh exported as
``WORKSPACE_ROOT``; otherwise the historical cwd-relative ``.mam/jobs``.
Anchoring on the workspace is what keeps a test sandbox (or any caller
whose cwd is not the workspace) from registering jobs into whichever
``.mam/jobs`` happens to sit under the current directory - the pytest
suite used to leave hundreds of onboarding records in the real registry.
"""
env = os.environ.get("MAM_JOBS_DIR")
if env and env.strip():
return env
return _workspace_mam_dir("jobs") or DEFAULT_REGISTRY_DIR
def sibling_logs_dir(registry_dir: str) -> str:
"""The audit-log root that pairs with ``registry_dir``: its sibling
``delegate_job_logs``. Pure; reads no environment. Issue #57."""
return os.path.join(os.path.dirname(os.path.abspath(registry_dir)), "delegate_job_logs")
def get_logs_dir(registry_dir: Optional[str] = None) -> str:
"""Audit-log root, resolved at call time (B-9).
``DELEGATE_JOB_LOGS_DIR`` when set: it means the audit log was moved to a
place that cannot be derived from the registry. Otherwise the audit log
follows the registry (Issue #57): the ``delegate_job_logs`` next to
``registry_dir``, or next to ``default_registry_dir()`` when the caller
does not know its registry. The default registry is
``$WORKSPACE_ROOT/.mam/jobs`` (Issue #15), otherwise ``<cwd>/.mam/jobs``,
so the usual roots are unchanged. Resolved per call rather than at import
so a chdir after import cannot strand the audit trail in the old tree —
the same reason ``DEFAULT_REGISTRY_DIR`` stays a relative string.
"""
env = os.environ.get("DELEGATE_JOB_LOGS_DIR")
if env and env.strip():
return env
return sibling_logs_dir(registry_dir or default_registry_dir())