feat(registry): format version 2, migrated by the writer (Issue #71)
- lib_py/registry_format.py: a pure, idempotent migrate() drops kill_command/start_command and rebuilds attach_command on every row (stopped/terminated included) and sets the top-level registry_format key. - atomic_dump_yaml migrates an older registry under its lock, before the caller's mutation, and then rewrites the YAML as well as the DB (the YAML write is otherwise skipped when only fields change). load_state_json stays read-only. New registries are written with the current version. - Entry point for #72: `python -m lib_py.registry_format --yaml <path>` runs the migration through the writer and prints `registry-format: <from> -> <to>, rows changed: <n>`. Refs #71 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
50e2998a20
commit
9c21fa4e4f
3 files changed
+117
-1
No files matched your search
@@ -0,0 +1,92 @@
|
||||
"""Registry format version and its migration (Issue #71).
|
||||
|
||||
The session registry (agent-sessions.yaml + agent-sessions.db) carries a
|
||||
top-level `registry_format` key; it lives in the DB `state` row like every
|
||||
other top-level key. A registry without it is format 1.
|
||||
|
||||
1: rows may carry `kill_command` and `start_command`, and `attach_command`
|
||||
reads `HERDR_SESSION_NAME=<server> herdr agent attach <original name>`.
|
||||
2: no `kill_command` / `start_command`; `attach_command` is
|
||||
lib_py.agents.sanitize.attach_command(<row server>, <row name>).
|
||||
|
||||
The writer (lib_py.atomic_yaml, the only code that writes the registry)
|
||||
migrates an older registry under its lock before the caller's mutation, and
|
||||
then rewrites the YAML as well as the DB. Readers (lib_py.state) never
|
||||
migrate; they see the old rows until the first write.
|
||||
|
||||
Entry point (for #72, the update skill):
|
||||
|
||||
python -m lib_py.registry_format --yaml <agent-sessions.yaml>
|
||||
|
||||
runs that migration through the writer (same lock, YAML rewritten), prints one
|
||||
line `registry-format: <from> -> <to>, rows changed: <n>` and exits 0 when the
|
||||
registry was migrated or already current, nonzero on error. Run it with the
|
||||
skills directory on PYTHONPATH (as lib.sh does), e.g.
|
||||
`PYTHONPATH=.agents/skills python3 -m lib_py.registry_format --yaml .mam/agent-sessions.yaml`.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib_py.agents.sanitize import attach_command
|
||||
|
||||
FORMAT_KEY = "registry_format"
|
||||
CURRENT = 2
|
||||
_DROPPED = ("kill_command", "start_command")
|
||||
|
||||
|
||||
def stored_version(d):
|
||||
"""The format a registry dict says it has (1 when it says nothing)."""
|
||||
v = d.get(FORMAT_KEY) if isinstance(d, dict) else None
|
||||
return v if isinstance(v, int) and not isinstance(v, bool) else 1
|
||||
|
||||
|
||||
def _row_server(s):
|
||||
return s.get("herdr_session") or s.get("herdr_server") or "default"
|
||||
|
||||
|
||||
def migrate(d):
|
||||
"""Return (new registry dict, rows changed). Pure and idempotent: `d` is
|
||||
not modified, and migrating the result again changes nothing. Every row is
|
||||
migrated, `stopped` and `terminated` ones included."""
|
||||
out = copy.deepcopy(d) if isinstance(d, dict) else {}
|
||||
changed = 0
|
||||
for s in out.get("herdr_sessions") or []:
|
||||
if not isinstance(s, dict) or not s.get("name"):
|
||||
continue
|
||||
before = dict(s)
|
||||
for k in _DROPPED:
|
||||
s.pop(k, None)
|
||||
s["attach_command"] = attach_command(_row_server(s), s["name"])
|
||||
if s != before:
|
||||
changed += 1
|
||||
out[FORMAT_KEY] = CURRENT
|
||||
return out, changed
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(prog="python -m lib_py.registry_format",
|
||||
description="Migrate the session registry to the current format.")
|
||||
ap.add_argument("--yaml", required=True, help="path of agent-sessions.yaml")
|
||||
a = ap.parse_args(argv)
|
||||
yaml_path = os.path.abspath(a.yaml)
|
||||
db_path = os.path.splitext(yaml_path)[0] + ".db"
|
||||
if not (os.path.exists(yaml_path) or os.path.exists(db_path)):
|
||||
print("registry-format: error: no registry at %s" % yaml_path, file=sys.stderr)
|
||||
return 1
|
||||
from lib_py.atomic_yaml import atomic_dump_yaml_main
|
||||
os.environ["YAML_PATH"] = yaml_path
|
||||
os.environ["AGENT_SESSIONS_MUTATION"] = "pass"
|
||||
try:
|
||||
frm, to, n = atomic_dump_yaml_main()
|
||||
except (Exception, SystemExit) as exc: # the writer refuses with SystemExit
|
||||
print("registry-format: error: %s" % (exc,), file=sys.stderr)
|
||||
return 1
|
||||
print("registry-format: %d -> %d, rows changed: %d" % (frm, to, n), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user