eb01eb0172
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>
76 lines
2.6 KiB
Python
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())
|