Skip to content

fix(sdlc): provision coord SSOT tree so R2 event-log + R3 escape grant go LIVE#3822

Merged
ryanklee merged 1 commit into
mainfrom
beta/reform-improve-coord-ssot-provisioning-20260601
Jun 1, 2026
Merged

fix(sdlc): provision coord SSOT tree so R2 event-log + R3 escape grant go LIVE#3822
ryanklee merged 1 commit into
mainfrom
beta/reform-improve-coord-ssot-provisioning-20260601

Conversation

@ryanklee
Copy link
Copy Markdown
Collaborator

@ryanklee ryanklee commented Jun 1, 2026

What

Provision the coordination SSOT + grant tree so the R2 event log and the R3 daemon-independent escape grant are actually LIVE (reform-improve, CASE-SDLC-REFORM-001).

Root cause (confirmed live)

DEFAULT_COORD_DIR=/var/lib/hapax/coord is root-owned; hapax (uid 1000) cannot mkdir into /var/lib/hapax. Consequences:

  • R2 coord_event_log SSOT never materialized there — path.parent.mkdir raised PermissionError, swallowed.
  • R3 escape grant was INERT — escape_grant_allows() always returned 1 (no grant dir/key), leaving only the deprecated HAPAX_*_OFF off-switch.
  • coord-grant-mint could not create the dir/key.

Fix (no root — parent spec remediation #1)

Make ~/.cache/hapax/coord the canonical default: user-writable, already where the live ledger sits, still one fixed location outside every worktree (NEW-4). Centralized in shared/coord_event_log.py so the Python minter and the bash shim resolve identically (precedence: explicit grant override → $HAPAX_COORD_DIR$XDG_CACHE_HOME~/.cache).

  • coord_base_dir / default_grant_dir / default_grant_key; default_event_log() now dynamic.
  • provision_coord_tree() — daemon-independent provisioner: idempotently materializes {base, spool/, grants/} + the 0600 grant-key and proves the base writable, raising LOUDLY on failure (no silent swallow — satisfies the fail-loud AC in-scope, without touching the out-of-scope daemon swallow).
  • coord-boot-reconcile --provision — boot entry (provision then reconcile); exit 2 on an unwritable SSOT.
  • systemd/units/hapax-coord-provision.service — oneshot, After=hapax-secrets, # Hapax-Auto-Enable: true so the deploy (fix(sdlc): deploy auto-enables marked timers/services + activate FM-11 lane supervisor #3817) enables it on merge → provisioned at boot, no manual intervention.
  • coord-grant-mint + escape-grant.sh consume the shared cache default; load_or_create_key centralized in coord_capabilities.

Live verification (real host)

  • Tree + 0600 grant-key provisioned at ~/.cache/hapax/coord (spool/, grants/, grant-key created; ledger replayed=1).
  • A minted cc-task-gate grant flips escape_grant_allows 1→0 (block→allow) with NO env override — R3 escape LIVE.
  • Oneshot installed + enabled.
  • ruff clean · pyrefly 0 errors · 348 coord/capability/hook/importer tests green.

Acceptance criteria

  • coord SSOT dir (ledger.db + ledger.jsonl + spool/ + grants/ + grant-key 0600) exists + writable on the live host without manual intervention
  • a minted grant unblocks a blocked cc-task-gate call on the real host (escape live)
  • coord_event_log writes succeed; an unwritable SSOT fails loud (no silent swallow)
  • ruff + tests pass

Task: reform-improve-coord-ssot-provisioning-20260601

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added provisioning command to initialize coordination directories and signing keys on startup.
    • Made coordination storage locations configurable via environment variables; no longer tied to root-owned directories.
    • Support for user-writable storage using XDG cache defaults or custom locations.
  • Chores

    • Updated boot reconciliation flow to include provisioning step.
    • Refactored grant minting to use shared governance modules.

…t go LIVE

The coordination tree defaulted to /var/lib/hapax/coord — root-owned and
unprovisionable by hapax (uid 1000). path.parent.mkdir raised PermissionError
(swallowed), so the R2 coord_event_log SSOT never materialized there, the R3
daemon-independent escape grant was INERT (escape_grant_allows always returned
1: no grant dir/key), and coord-grant-mint could not create the dir/key —
leaving only the deprecated HAPAX_*_OFF off-switch.

Fix (no root, per parent spec remediation #1): make ~/.cache/hapax/coord the
canonical default — user-writable, already where the live ledger sits, still one
fixed location outside every worktree (NEW-4). Centralized in coord_event_log.py
(coord_base_dir / default_grant_dir / default_grant_key) so the Python minter and
the bash shim resolve identically. Precedence per surface: explicit grant
override -> $HAPAX_COORD_DIR -> $XDG_CACHE_HOME -> ~/.cache.

- shared/coord_event_log.py: coord_base_dir resolver + grant helpers;
  default_event_log() now dynamic; provision_coord_tree() — a daemon-independent
  provisioner that idempotently materializes {base, spool/, grants/} + the 0600
  grant-key and proves the base writable, raising LOUDLY (no silent swallow) when
  it cannot. Satisfies the fail-loud AC in-scope, without editing the
  out-of-scope daemon swallow.
- scripts/coord-boot-reconcile --provision: boot entry (provision then
  reconcile); exit 2 on an unwritable SSOT.
- systemd/units/hapax-coord-provision.service: oneshot, After=hapax-secrets,
  `# Hapax-Auto-Enable: true` so the deploy (#3817) enables it on merge.
- scripts/coord-grant-mint + hooks/scripts/escape-grant.sh: consume the shared
  cache default (no more /var/lib).
- shared/governance/coord_capabilities.py: load_or_create_key centralized.

Live-verified on host: tree + 0600 grant-key provisioned at ~/.cache/hapax/coord;
a minted cc-task-gate grant flips escape_grant_allows 1->0 (block->allow) with no
env override; the oneshot is installed + enabled. ruff + pyrefly(0 errors) + 348
coord/capability/hook/importer tests green.

Task: reform-improve-coord-ssot-provisioning-20260601
AuthorityCase: CASE-SDLC-REFORM-001

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates Hapax coordination storage from hardcoded /var/lib/hapax/coord/ paths to environment-configurable defaults via HAPAX_COORD_DIR and XDG cache variables, adds a provisioning API to idempotently create the SSOT tree and signing key on boot, and integrates provisioning into systemd and CLI workflows.

Changes

Coordination Provisioning and Directory Resolution

Layer / File(s) Summary
Shared coordination base directory and grant defaults
shared/coord_event_log.py, tests/shared/test_coord_event_log.py
New coord_base_dir() function resolves user-writable base via HAPAX_COORD_DIR or XDG cache precedence chain, new grant env var constants, helpers for grant directory/key paths, and updated exported DEFAULT_* constants derived from the resolved base. Tests validate env var precedence and non-hardcoded paths.
Coordination tree provisioning API and atomic key creation
shared/coord_event_log.py, shared/governance/coord_capabilities.py, tests/shared/test_coord_event_log.py
New ProvisionResult dataclass and provision_coord_tree() function idempotently create coordination directories, verify base writability via probe file, and provision escape-grant signing key. New atomic load_or_create_key() helper uses os.open with O_EXCL and 0600 permissions to avoid race conditions on first key generation. Tests verify provisioning creates expected directories/key, is idempotent, and fails loudly on unwritable paths.
Systemd boot-time provisioning service
systemd/units/hapax-coord-provision.service, tests/systemd/test_coord_provision_unit.py
New hapax-coord-provision.service systemd unit runs coord-boot-reconcile --provision as a oneshot service on boot, ordered after hapax-secrets.service, gated by pyproject.toml existence, and installed on default.target. Tests validate unit configuration, dependencies, auto-enable marker, and absence of hardcoded /var/lib/hapax/coord paths.
CLI provisioning mode in coord-boot-reconcile
scripts/coord-boot-reconcile, tests/scripts/test_coord_boot_reconcile.py
New --provision flag calls provision_coord_tree() before reconciliation, exits with code 2 on CoordEventLogError, records provision metadata, and merges both provision and reconcile receipts in JSON output. Tests verify provision creates expected tree/key with correct permissions, succeeds with both human-readable and JSON output, and fails loudly on unwritable base.
Shell script and module exports updates
hooks/scripts/escape-grant.sh, scripts/coord-grant-mint, shared/coord_event_log.py
escape-grant.sh and coord-grant-mint now compute defaults from shared env vars and functions instead of hardcoded /var/lib/... paths; coord-grant-mint refactored to use shared load_or_create_key() and governance helpers instead of local implementations; module __all__ exports updated to include grant env vars and provisioning API.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hapax-systems/hapax-council#3790: Modifies the same DEFAULT_* path constants and CoordEventLog initialization in shared/coord_event_log.py that this PR extends with env-based directory resolution.
  • hapax-systems/hapax-council#3805: Introduces escape-grant shim and signing key integration that this PR refactors by wiring shared provisioning and load_or_create_key() helpers into scripts/coord-grant-mint and hooks/scripts/escape-grant.sh.

Poem

🐰 A rabbit hops through /home now, not /var/lib,
Where caches and XDG flags dance in harmony!
Atomic keys sprout safely, with 0600 grace,
Boot-time provisioning finds its rightful place.
No more hardcoded paths—just freedom and care! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: provisioning the coordination SSOT tree to make R2 event-log and R3 escape grant functional by moving to a user-writable default location.
Description check ✅ Passed The PR description includes required AuthorityCase fields (Case and Slice), comprehensive 'What' and 'Root cause' sections explaining the problem and solution, a detailed implementation summary, live verification evidence, acceptance criteria checklist, and task reference, but is missing the explicit Test plan section from the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch beta/reform-improve-coord-ssot-provisioning-20260601

Comment @coderabbitai help to get the list of available commands and usage tips.

@ryanklee ryanklee enabled auto-merge June 1, 2026 03:49
@ryanklee ryanklee added this pull request to the merge queue Jun 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e49779d48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread shared/coord_event_log.py
Comment on lines +613 to 616
base = coord_base_dir()
return CoordEventLog(
db_path=base / "ledger.db",
jsonl_path=base / "ledger.jsonl",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the shared coord default for dispatch launches

With the normal mutable dispatch path, scripts/hapax-methodology-dispatch still constructs its injected CoordEventLog from HAPAX_COORD_LEDGER_DB/HAPAX_COORD_JSONL_MIRROR/HAPAX_COORD_SPOOL_DIR with /var/lib/hapax/coord fallbacks (lines 839-852) and passes it into run_atomic_dispatch_launch (line 1384). Since this commit only provisions ~/.cache/hapax/coord, those dispatch events continue to target the old root-owned tree unless three legacy env vars are set, so the core R2 event-log path remains inert in exactly the default environment this change is meant to fix.

Useful? React with 👍 / 👎.

Comment thread shared/coord_event_log.py
Comment on lines +55 to +64
def default_grant_dir() -> Path:
"""Escape-grant directory: ``HAPAX_COORD_GRANT_DIR`` else ``<base>/grants``."""
explicit = os.environ.get(GRANT_DIR_ENV, "").strip()
return Path(explicit) if explicit else coord_base_dir() / "grants"


def default_grant_key() -> Path:
"""Escape signing key: ``HAPAX_COORD_GRANT_KEY`` else ``<base>/grant-key``."""
explicit = os.environ.get(GRANT_KEY_ENV, "").strip()
return Path(explicit) if explicit else coord_base_dir() / "grant-key"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the retro-grant watcher on the new grant store

When an emergency HAPAX_*_OFF obligation is recorded and the operator follows the updated coord-grant-mint defaults, the grant now lands under ~/.cache/hapax/coord/grants; however scripts/coord-retro-grant-watch still falls back to /var/lib/hapax/coord/grants and /var/lib/hapax/coord/grant-key when no per-script env is set (lines 145-146). In that default setup the watcher never sees the covering grant and escalates an already-fulfilled obligation, so this shared default needs to be used there too.

Useful? React with 👍 / 👎.

Comment thread shared/coord_event_log.py
Comment on lines +670 to +674
for directory in (base, spool, gdir):
existed = directory.is_dir()
try:
directory.mkdir(parents=True, exist_ok=True)
except OSError as exc:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Materialize the ledger during provisioning

On a fresh host with no spooled events, coord-boot-reconcile --provision only creates the directories and grant key; boot_reconcile() then replays an absent SQLite file and returns without creating either ledger.db or ledger.jsonl. That leaves the post-merge/boot oneshot green while the promised SSOT artifacts are still absent until some later append, so provision should initialize the SQLite schema and mirror file as part of the same boot path.

Useful? React with 👍 / 👎.

Comment thread shared/coord_event_log.py
Comment on lines +692 to +694
key_existed = gkey.exists()
try:
load_or_create_key(gkey)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject or repair an existing permissive grant key

If grant-key already exists with permissive bits (for example from a manual recovery or a previous bad provision), this path just reads it and reports provisioning success; it never checks or fixes the mode. That leaves the escape-grant signing key world/group-readable while the new boot provisioner claims the key is 0600, so validate the existing file's permissions or chmod it before returning success.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shared/coord_event_log.py`:
- Around line 664-666: The grant paths should be derived from the resolved base
(the local variable base) when grant_dir or grant_key are not provided;
currently gdir and gkey call default_grant_dir() / default_grant_key() which may
use the process environment/coord_base_dir() instead of the caller-provided
base_dir. Change the logic so that after resolving base = Path(base_dir) if
base_dir is not None else coord_base_dir(), you compute gdir = Path(grant_dir)
if grant_dir is not None else default_grant_dir(base) (or otherwise construct
the grant path relative to base) and likewise gkey = Path(grant_key) if
grant_key is not None else default_grant_key(base); if
default_grant_dir/default_grant_key do not accept a base param, update them or
build the grant paths by joining base with the relative default grant filenames
so the grant artifacts inhabit the same base tree.

In `@shared/governance/coord_capabilities.py`:
- Around line 319-329: The code has a TOCTOU race: after path.exists() another
process may create the file so os.open(..., O_EXCL) raises FileExistsError;
change the block around the os.open/os.write to catch FileExistsError from
os.open (and any subsequent failure to create) and in that case read and return
the file contents (path.read_bytes()) instead of propagating the exception;
ensure you only close the file descriptor if os.open succeeded (i.e., open in a
try: fd = os.open(...) except FileExistsError: return path.read_bytes() then
proceed with writing and finally os.close(fd)).

In `@tests/shared/test_coord_event_log.py`:
- Around line 44-64: The test relies on constants DEFAULT_LEDGER_DB,
DEFAULT_JSONL_MIRROR, and DEFAULT_SPOOL_DIR that are evaluated at import time in
shared.coord_event_log, so make the test hermetic by setting/clearing relevant
env vars (e.g. HAPAX_COORD_DIR and XDG_CACHE_HOME) to a controlled temporary
path, then reload the shared.coord_event_log module (via importlib.reload) so
DEFAULT_* are recomputed from the sanitized environment before performing the
assertions; alternatively call the module's dynamic resolver function if
available to obtain fresh paths instead of using the already-imported constants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 31bfcc38-ac01-4bdd-a3fc-6a4e6de3534d

📥 Commits

Reviewing files that changed from the base of the PR and between d194f9b and 2e49779.

📒 Files selected for processing (9)
  • hooks/scripts/escape-grant.sh
  • scripts/coord-boot-reconcile
  • scripts/coord-grant-mint
  • shared/coord_event_log.py
  • shared/governance/coord_capabilities.py
  • systemd/units/hapax-coord-provision.service
  • tests/scripts/test_coord_boot_reconcile.py
  • tests/shared/test_coord_event_log.py
  • tests/systemd/test_coord_provision_unit.py

Comment thread shared/coord_event_log.py
Comment on lines +664 to +666
base = Path(base_dir) if base_dir is not None else coord_base_dir()
gdir = Path(grant_dir) if grant_dir is not None else default_grant_dir()
gkey = Path(grant_key) if grant_key is not None else default_grant_key()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive grant defaults from the explicit base_dir.

If a caller passes base_dir but leaves grant_dir/grant_key unset, Lines 665-666 still resolve the grant paths from the process environment instead of the overridden base. That can provision the ledger under one tree and the escape-grant artifacts under another.

Suggested fix
     base = Path(base_dir) if base_dir is not None else coord_base_dir()
-    gdir = Path(grant_dir) if grant_dir is not None else default_grant_dir()
-    gkey = Path(grant_key) if grant_key is not None else default_grant_key()
+    if grant_dir is not None:
+        gdir = Path(grant_dir)
+    else:
+        explicit_grant_dir = os.environ.get(GRANT_DIR_ENV, "").strip()
+        gdir = Path(explicit_grant_dir) if explicit_grant_dir else base / "grants"
+
+    if grant_key is not None:
+        gkey = Path(grant_key)
+    else:
+        explicit_grant_key = os.environ.get(GRANT_KEY_ENV, "").strip()
+        gkey = Path(explicit_grant_key) if explicit_grant_key else base / "grant-key"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/coord_event_log.py` around lines 664 - 666, The grant paths should be
derived from the resolved base (the local variable base) when grant_dir or
grant_key are not provided; currently gdir and gkey call default_grant_dir() /
default_grant_key() which may use the process environment/coord_base_dir()
instead of the caller-provided base_dir. Change the logic so that after
resolving base = Path(base_dir) if base_dir is not None else coord_base_dir(),
you compute gdir = Path(grant_dir) if grant_dir is not None else
default_grant_dir(base) (or otherwise construct the grant path relative to base)
and likewise gkey = Path(grant_key) if grant_key is not None else
default_grant_key(base); if default_grant_dir/default_grant_key do not accept a
base param, update them or build the grant paths by joining base with the
relative default grant filenames so the grant artifacts inhabit the same base
tree.

Comment on lines +319 to +329
path = Path(path)
if path.exists():
return path.read_bytes()
path.parent.mkdir(parents=True, exist_ok=True)
key = secrets.token_bytes(32)
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, key)
finally:
os.close(fd)
return key
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

TOCTOU race: unhandled FileExistsError when another process wins the create.

If two processes call this concurrently, both pass the exists() check, but only one succeeds at O_EXCL. The loser raises FileExistsError instead of reading the key the winner wrote.

Since the systemd provisioner and coord-grant-mint share this helper, concurrent invocation (boot + manual call, parallel tests) is plausible.

Proposed fix
     path = Path(path)
     if path.exists():
         return path.read_bytes()
     path.parent.mkdir(parents=True, exist_ok=True)
     key = secrets.token_bytes(32)
-    fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
+    try:
+        fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
+    except FileExistsError:
+        # Another process won the race; read their key
+        return path.read_bytes()
     try:
         os.write(fd, key)
     finally:
         os.close(fd)
     return key
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path = Path(path)
if path.exists():
return path.read_bytes()
path.parent.mkdir(parents=True, exist_ok=True)
key = secrets.token_bytes(32)
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, key)
finally:
os.close(fd)
return key
path = Path(path)
if path.exists():
return path.read_bytes()
path.parent.mkdir(parents=True, exist_ok=True)
key = secrets.token_bytes(32)
try:
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
except FileExistsError:
# Another process won the race; read their key
return path.read_bytes()
try:
os.write(fd, key)
finally:
os.close(fd)
return key
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/governance/coord_capabilities.py` around lines 319 - 329, The code has
a TOCTOU race: after path.exists() another process may create the file so
os.open(..., O_EXCL) raises FileExistsError; change the block around the
os.open/os.write to catch FileExistsError from os.open (and any subsequent
failure to create) and in that case read and return the file contents
(path.read_bytes()) instead of propagating the exception; ensure you only close
the file descriptor if os.open succeeded (i.e., open in a try: fd = os.open(...)
except FileExistsError: return path.read_bytes() then proceed with writing and
finally os.close(fd)).

Comment on lines +44 to 64
def test_default_paths_are_a_user_writable_coord_ledger_outside_worktrees() -> None:
# Must NOT be the old root-owned /var/lib/hapax/coord that uid 1000 could never
# provision — that default left R2 unmaterialized and R3 inert (reform-improve
# coord SSOT provisioning).
for path in (DEFAULT_LEDGER_DB, DEFAULT_JSONL_MIRROR, DEFAULT_SPOOL_DIR):
assert not str(path).startswith("/var/lib/"), path

# One fixed `.../hapax/coord` tree; the three artifacts share that base.
coord = DEFAULT_LEDGER_DB.parent
assert coord.name == "coord"
assert coord.parent.name == "hapax"
assert coord / "ledger.db" == DEFAULT_LEDGER_DB
assert coord / "ledger.jsonl" == DEFAULT_JSONL_MIRROR
assert coord / "spool" == DEFAULT_SPOOL_DIR

# Outside every git worktree (NEW-4).
repo_root = Path.cwd().resolve()
for path in (DEFAULT_LEDGER_DB, DEFAULT_JSONL_MIRROR, DEFAULT_SPOOL_DIR):
assert path.is_absolute()
assert not path.resolve().is_relative_to(repo_root)
assert "evidence" not in path.parts
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make this test independent of the runner environment.

DEFAULT_* are snapshotted when shared.coord_event_log is imported at Lines 10-18, so a pre-set HAPAX_COORD_DIR or XDG_CACHE_HOME in CI can change what this test sees. Please clear/reload or assert against the dynamic resolver here so the test stays hermetic.

One way to isolate it
-def test_default_paths_are_a_user_writable_coord_ledger_outside_worktrees() -> None:
+def test_default_paths_are_a_user_writable_coord_ledger_outside_worktrees(
+    monkeypatch: pytest.MonkeyPatch,
+) -> None:
+    import importlib
+    import shared.coord_event_log as coord_event_log
+
+    monkeypatch.delenv("HAPAX_COORD_DIR", raising=False)
+    monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
+    coord_event_log = importlib.reload(coord_event_log)
+
-    for path in (DEFAULT_LEDGER_DB, DEFAULT_JSONL_MIRROR, DEFAULT_SPOOL_DIR):
+    for path in (
+        coord_event_log.DEFAULT_LEDGER_DB,
+        coord_event_log.DEFAULT_JSONL_MIRROR,
+        coord_event_log.DEFAULT_SPOOL_DIR,
+    ):
         assert not str(path).startswith("/var/lib/"), path
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/shared/test_coord_event_log.py` around lines 44 - 64, The test relies
on constants DEFAULT_LEDGER_DB, DEFAULT_JSONL_MIRROR, and DEFAULT_SPOOL_DIR that
are evaluated at import time in shared.coord_event_log, so make the test
hermetic by setting/clearing relevant env vars (e.g. HAPAX_COORD_DIR and
XDG_CACHE_HOME) to a controlled temporary path, then reload the
shared.coord_event_log module (via importlib.reload) so DEFAULT_* are recomputed
from the sanitized environment before performing the assertions; alternatively
call the module's dynamic resolver function if available to obtain fresh paths
instead of using the already-imported constants.

Merged via the queue into main with commit 32ee29d Jun 1, 2026
39 checks passed
@ryanklee ryanklee deleted the beta/reform-improve-coord-ssot-provisioning-20260601 branch June 1, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant