feat(curator): vendor the application backend as the scenario's tracked source

The curator Python backend (package, tests, systemd units, config templates, scripts) now lives under scenarios/curator/backend and is the single source of truth; the live checkout at the workspace path is a runtime copy. Exported from the app repo's tracked tree via git archive (no history, .pi/venv/caches excluded). 149 unit tests pass from the new location.

profile.toml backend is now repo-relative (scenarios/curator/backend); verify-generated.sh resolves a relative backend against REPO_ROOT. verify-no-secrets ASSIGN heuristic now requires value entropy so vendored kwargs like token=extraction_token no longer false-positive. README documents the backend/ layout and the operator-owned app rollout step.
This commit is contained in:
Kai
2026-08-30 18:49:10 -07:00
parent 14c97d88cf
commit 88b06d782f
57 changed files with 14236 additions and 9 deletions
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import shutil
import sqlite3
from datetime import UTC, datetime
from pathlib import Path
from curator.epub import inspect_epub
from curator.library import safe_component, safe_filename
def main() -> None:
parser = argparse.ArgumentParser(description="Merge one Curator book work into another")
parser.add_argument("database", type=Path)
parser.add_argument("target_work_id", type=int)
parser.add_argument("source_work_id", type=int)
parser.add_argument("--apply", action="store_true")
args = parser.parse_args()
connection = sqlite3.connect(args.database)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
target = connection.execute("SELECT * FROM works WHERE id=?", (args.target_work_id,)).fetchone()
source = connection.execute("SELECT * FROM works WHERE id=?", (args.source_work_id,)).fetchone()
if not target or not source or target["media_type"] != "book" or source["media_type"] != "book":
raise SystemExit("target and source must both be existing book works")
rows = connection.execute(
"""SELECT a.*, e.id AS source_edition_id, e.language, e.variant, e.isbn
FROM assets a JOIN editions e ON e.id=a.edition_id WHERE e.work_id=?""",
(args.source_work_id,),
).fetchall()
if not rows:
raise SystemExit("source work has no assets")
changes: list[dict[str, object]] = []
for row in rows:
old_path = Path(row["path"])
info = inspect_epub(old_path) if row["format"] == "epub" else None
language = info.language if info else row["language"]
edition_dir = safe_component(f"{language}-{row['variant']}", "und-original")
destination_dir = old_path.parents[3] / safe_component(target["author"], "Unknown Author") / safe_component(target["title"], "Untitled") / edition_dir
filename = safe_filename(row["filename"], f"book.{row['format']}")
destination = destination_dir / filename
metadata = json.loads(row["metadata_json"] or "{}")
if info:
metadata.update({
"display_title": info.display_title,
"title_aliases": list(info.title_aliases),
"declared_language": info.declared_language,
"identifiers": list(info.identifiers),
"source_identifiers": list(info.source_identifiers),
})
changes.append({
"asset_id": row["id"], "edition_id": row["source_edition_id"], "old": old_path,
"new": destination, "filename": filename, "language": language,
"isbn": info.isbn if info else row["isbn"], "metadata": metadata,
})
print(json.dumps([{**change, "old": str(change["old"]), "new": str(change["new"])} for change in changes], ensure_ascii=False, indent=2, default=str))
if not args.apply:
return
backup = args.database.with_name(f"{args.database.stem}-before-merge-{datetime.now(UTC).strftime('%Y%m%dT%H%M%SZ')}.sqlite3")
with sqlite3.connect(backup) as backup_connection:
connection.backup(backup_connection)
moved: list[tuple[Path, Path]] = []
try:
connection.execute("BEGIN IMMEDIATE")
for change in changes:
old_path = change["old"]
destination = change["new"]
assert isinstance(old_path, Path) and isinstance(destination, Path)
destination.parent.mkdir(parents=True, exist_ok=True)
if destination.exists() and destination != old_path:
raise FileExistsError(destination)
os.replace(old_path, destination)
moved.append((destination, old_path))
connection.execute(
"UPDATE editions SET work_id=?, language=?, isbn=? WHERE id=?",
(args.target_work_id, change["language"], change["isbn"], change["edition_id"]),
)
connection.execute(
"UPDATE assets SET filename=?, path=?, metadata_json=? WHERE id=?",
(change["filename"], str(destination), json.dumps(change["metadata"], ensure_ascii=False, sort_keys=True), change["asset_id"]),
)
connection.execute("DELETE FROM works WHERE id=?", (args.source_work_id,))
connection.execute(
"UPDATE works SET updated_at=? WHERE id=?",
(datetime.now(UTC).replace(microsecond=0).isoformat(), args.target_work_id),
)
connection.commit()
except Exception:
connection.rollback()
for current, original in reversed(moved):
original.parent.mkdir(parents=True, exist_ok=True)
os.replace(current, original)
raise
finally:
connection.close()
print(f"backup={backup}")
if __name__ == "__main__":
main()