Files
cleveragents-core/tools/_cache_path.py
T
drew eb01eb0172 feat(controller): dual-mode launcher (fork/prod) + DB-mode validator
Adds the operator surface for switching the controller pipeline between
the personal fork (drew/cleveragents-core) and the canonical repo
(cleveragents/cleveragents-core) via a MODE env + --prod CLI flag,
backed by safety primitives that make a wrong-mode launch loud rather
than silent.

run-controller-state-machine-pipeline.sh: --prod flag and MODE env
(primary home: .devcontainer/.env) select fork vs prod. After resolving
MODE, the launcher auto-sources the matching overlay file
(.devcontainer/.env.{fork,prod}) and asserts MODE didn't drift during
the source step. The drift assertion uses a readonly snapshot under an
obscure variable name so a stray ``MODE=fork`` in .env.prod aborts the
launch with a clear bash error rather than silently demoting the run.
CONTROLLER_RUN_DIR_ROOT now overrides the trial /tmp path so prod can
use a persistent /var/lib/cleveragents/run dir.

tools/launch_prod.sh (new): sibling to launch_fork.sh with the opposite
safety primitive — affirmative GET /repos/{owner}/{repo} that asserts
the target is non-fork, exists, isn't archived, and the bot has push.
On any failure, no env is exported. Honors HAL_* aliases for parity
with launch_fork.sh and prints a hard-to-miss PROD-MODE banner.

tools/controller/deploy/validate_db_mode.py (new): stamps a _mode_marker
table on each SQLite db (controller DB + telemetry cache) on first use,
asserts a match on every subsequent launch, and moves mismatched files
aside as <name>.<prior-mode>.bak.<ts> — never deletes. The --adopt flag
lets an operator grandfather in already-good pre-marker data without
losing history. Wired into the launcher's startup sequence before
OpenCode and the master start.

tools/_cache_path.py (new): single source of truth for the per-(owner,
repo) Forgejo cache file convention. .opencode/telemetry/server.py and
the launcher both delegate here so the dual-source-truth drift risk is
eliminated. tools/_pipeline_cache.py and tools/controller/db/models.py
documented as not owning the _mode_marker table so future migrations
leave it alone.

.opencode/telemetry/server.py: hosts the llm_activity scraper as a
background subprocess thread (60s cadence, --since-hours 1 in steady
state, full backfill on first tick). Re-homes the cost-telemetry data
path after the pr_state_warmer was retired by the controller migration
— without this the Cost tab freezes when the warmer's loop is gone.
Subprocess (not in-process) for isolation; failures swallowed.

opencode.json: local-claude provider's baseURL now reads
{env:LOCAL_PROXY_URL} instead of the literal http://127.0.0.1:3456/v1,
matching the apiKey pattern already in use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 08:25:24 -04:00

76 lines
2.6 KiB
Python

"""Compute the per-(owner, repo) Forgejo cache file path.
Single source of truth for the cache-file convention shared between
three callers that must agree on the path:
- ``.opencode/telemetry/server.py`` — reads the cache (Cost / PR tabs).
- ``tools/_pipeline_cache.py`` — writes the cache (the scrapers).
- ``tools/run-controller-state-machine-pipeline.sh`` — validates the
db-mode marker before the writers open it.
Convention
----------
The canonical pair ``(cleveragents, cleveragents-core)`` uses the legacy
filename ``tools/.cache/forgejo.sqlite`` — kept for historical
compatibility with caches the dispatcher era wrote before this
partitioning existed. Any other (owner, repo) is stored as
``tools/.cache/forgejo.<owner>.<repo>.sqlite`` with non-alphanumeric
characters folded to ``-``.
The function is intentionally pure (no env reads, no filesystem checks)
so it can be unit-tested trivially and called from any context.
CLI
---
Invoked from the controller launcher as::
python3 tools/_cache_path.py --repo-root <repo> --owner <o> --repo <r>
Prints the absolute path to stdout. No newline-trimming gotchas — caller
uses ``$(…)``.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
# Kept here, NOT in server.py, so the helper has no import-time
# dependency on the telemetry module's heavy startup machinery.
CANONICAL_OWNER = "cleveragents"
CANONICAL_REPO = "cleveragents-core"
def cache_path_for(repo_root: Path, owner: str, repo: str) -> Path:
"""Return the SQLite cache file path for ``(owner, repo)``.
Pure function. Does not check whether the file or directory exists.
"""
cache_dir = repo_root / "tools" / ".cache"
if owner == CANONICAL_OWNER and repo == CANONICAL_REPO:
return cache_dir / "forgejo.sqlite"
safe_owner = re.sub(r"[^a-zA-Z0-9._-]+", "-", owner)
safe_repo = re.sub(r"[^a-zA-Z0-9._-]+", "-", repo)
return cache_dir / f"forgejo.{safe_owner}.{safe_repo}.sqlite"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description=(
"Print the per-(owner, repo) Forgejo cache file path. Single "
"source of truth shared by server.py, _pipeline_cache.py, and "
"the controller launcher."
),
)
parser.add_argument("--repo-root", required=True, type=Path)
parser.add_argument("--owner", required=True)
parser.add_argument("--repo", required=True)
args = parser.parse_args(argv)
print(cache_path_for(args.repo_root.resolve(), args.owner, args.repo))
return 0
if __name__ == "__main__":
raise SystemExit(main())