7Registry location discovery.
11Resolve the SQLite registry location in the order specified by the
12orchestration registry design-lock document.
16- Honor ``OC_AGENT_REGISTRY`` when it points at an absolute path.
17- Detect ``.option_c_workspace.json`` and derive the shared registry path.
18- Fall back through git common-dir and project-root locations.
19- Return workspace metadata needed by the init seed path.
31- Discovery is read-only and does not create directories or files.
32- Environment override paths must be absolute.
36- oc_diagnostics (structured debug logging via _dbg)
37- oc_chat.core._paths (workspace root fallback)
39from __future__
import annotations
41from dataclasses
import dataclass
44from pathlib
import Path
46from typing
import Mapping
48from oc_diagnostics
import _dbg
52_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
54REGISTRY_ENV_VAR =
"OC_AGENT_REGISTRY"
55WORKER_ENV_VAR =
"OC_AGENT_WORKER"
56WORKSPACE_MANIFEST =
".option_c_workspace.json"
57DEFAULT_REGISTRY_RELATIVE = Path(
".option_c") /
"orchestration" /
"registry.sqlite"
58DEFAULT_ORCHESTRATION_RELATIVE = Path(
".option_c") /
"orchestration"
59DEFAULT_CHECKPOINT_NAME =
"state.json"
63 """Fail-closed worker registry-discovery boundary error (Plan 142j R7).
65 Raised under ``OC_AGENT_WORKER=1`` when ``OC_AGENT_REGISTRY`` is unset/empty,
66 not absolute, or resolves OUTSIDE the current worktree's
67 ``.option_c/orchestration/`` boundary โ the cross-worktree contamination the
68 per-worktree resolved-path model exists to prevent. NOT a ยง8 source/regenerate
69 envelope condition (discovery runs before any source operation); the CLI
70 surfaces it as exit 2 + a stderr message naming both ``OC_AGENT_REGISTRY`` and
75@dataclass(frozen=
True)
77 """Resolved registry path plus workspace metadata for init seeding."""
86 start: str | os.PathLike[str] |
None =
None,
87 env: Mapping[str, str] |
None =
None,
89 """Resolve the registry SQLite path using spec section 6.1 order."""
95 start: str | os.PathLike[str] |
None =
None,
96 env: Mapping[str, str] |
None =
None,
97 registry_path: str | os.PathLike[str] |
None =
None,
98 workspace_root: str | os.PathLike[str] |
None =
None,
100 """Resolve registry path and metadata needed by ``init``."""
102 mapping = os.environ
if env
is None else env
107 if registry_path
is not None:
110 resolved_registry =
_discover_path(anchor, mapping, resolved_workspace)
112 git_common_dir =
_git_common_dir(anchor)
or (resolved_workspace /
".git").resolve()
113 checkpoint_path = resolved_registry.parent / DEFAULT_CHECKPOINT_NAME
115 registry_path=resolved_registry,
116 workspace_root=resolved_workspace,
117 git_common_dir=git_common_dir,
118 checkpoint_path=checkpoint_path,
123 """Plan 142j R7: in worker context (``OC_AGENT_WORKER=1``), ``OC_AGENT_REGISTRY``
124 MUST be set, absolute, and resolve INSIDE this worktree's
125 ``.option_c/orchestration/`` boundary; else fail closed (``WorkerRegistryError``).
127 A not-yet-created ``registry.sqlite`` **inside** the boundary is VALID (the
128 per-worktree store is created on first use โ location, not existence, is
129 checked). Unset/empty OR resolving OUTSIDE the boundary (main repo / sibling
130 worktree / arbitrary temp) is fail-closed. Non-worker local CLI is unaffected
131 (the existing workspace/git fallback still applies).
133 if env.get(WORKER_ENV_VAR) !=
"1":
135 boundary = (Path(workspace_root) / DEFAULT_ORCHESTRATION_RELATIVE).resolve()
136 raw = env.get(REGISTRY_ENV_VAR)
139 f
"worker context ({WORKER_ENV_VAR}=1) requires {REGISTRY_ENV_VAR} set to an "
140 f
"absolute path inside this worktree's boundary {boundary} (got unset/empty)"
142 path = Path(raw).expanduser()
143 if not path.is_absolute():
145 f
"worker {REGISTRY_ENV_VAR} must be an absolute path (got {raw!r})"
147 resolved = path.resolve()
149 resolved.relative_to(boundary)
150 except ValueError
as error:
152 f
"worker {REGISTRY_ENV_VAR} {resolved} resolves OUTSIDE this worktree's "
153 f
"boundary {boundary} โ refusing to read/write another worktree's store"
159 env: Mapping[str, str],
160 workspace_root: Path,
162 env_value = env.get(REGISTRY_ENV_VAR)
164 path = Path(env_value).expanduser()
165 if not path.is_absolute():
166 raise ValueError(f
"{REGISTRY_ENV_VAR} must be an absolute path")
167 return path.resolve()
170 if manifest
is not None:
172 return (root / DEFAULT_REGISTRY_RELATIVE).resolve()
175 if common_dir
is not None:
176 return (common_dir.parent / DEFAULT_REGISTRY_RELATIVE).resolve()
178 return (workspace_root / DEFAULT_REGISTRY_RELATIVE).resolve()
183 workspace_root: str | os.PathLike[str] |
None,
185 if workspace_root
is not None:
188 if manifest
is not None:
191 return find_workspace_root(anchor).resolve()
192 except FileNotFoundError:
193 return anchor.resolve()
198 data = json.loads(manifest.read_text(encoding=
"utf-8"))
199 except (OSError, json.JSONDecodeError):
200 return manifest.parent.resolve()
201 for key
in (
"workspace_root",
"root",
"workspaceRoot"):
202 raw_value = data.get(key)
if isinstance(data, dict)
else None
203 if isinstance(raw_value, str)
and raw_value.strip():
204 return _absolute_path(Path(raw_value).expanduser(), manifest.parent)
205 return manifest.parent.resolve()
209 current = anchor
if anchor.is_dir()
else anchor.parent
210 for candidate
in (current, *current.parents):
211 manifest = candidate / WORKSPACE_MANIFEST
212 if manifest.is_file():
213 return manifest.resolve()
219 result = subprocess.run(
220 [
"git",
"rev-parse",
"--git-common-dir"],
226 except (OSError, ValueError):
228 if result.returncode != 0:
230 raw_value = result.stdout.strip()
237 return Path.cwd().resolve()
if start
is None else Path(start).expanduser().resolve()
241 if path.is_absolute():
242 return path.resolve()
243 return (base / path).resolve()
Path _anchor_path(str|os.PathLike[str]|None start)
Path _workspace_root_from_manifest(Path manifest)
Path _discover_path(Path anchor, Mapping[str, str] env, Path workspace_root)
Path _resolve_workspace_root(Path anchor, str|os.PathLike[str]|None workspace_root)
Path|None _find_workspace_manifest(Path anchor)
Path|None _git_common_dir(Path anchor)
Path discover_registry_path(str|os.PathLike[str]|None start=None, Mapping[str, str]|None env=None)
RegistryLocation discover_registry_location(str|os.PathLike[str]|None start=None, Mapping[str, str]|None env=None, str|os.PathLike[str]|None registry_path=None, str|os.PathLike[str]|None workspace_root=None)
Path _absolute_path(Path path, Path base)
None _worker_registry_guard(Mapping[str, str] env, Path workspace_root)