Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
goal
Maintain a REGISTRY of virtual machines, tagged. Use the registry that ALREADY EXISTS — do not invent
one. This is a small, honest tool: it records what is actually on disk so the next person (or agent)
does not have to guess.
USE WHAT IS BUILT. `ticket_artifacts` already has exactly the right shape and has ZERO rows:
ticket_artifacts (id, ts, ticket_id, name, version, forge_sha, kind)
routes: artifact.record (POST) artifact.list (GET)
* kind -> THE TAG. VMs register as kind='forge-vm'.
* name -> the domain / image name (e.g. 'workstation', 'noble-arm64-base').
* forge_sha -> the sha256 content address (this is ALREADY the filename in
config/forge/large-blobs/<sha256> and the shard key in
config/forge/artifacts/blobs/<xx>/...).
* version -> the image's identifying version where one exists, else the short sha.
Record THROUGH the governed route (artifact.record). NEVER hand-write the db.
WHY THIS EXISTS (measured today, all four are the reason):
1. All 6 libvirt domains are 'shut off' and EVERY disk path is DEAD: the xmls point at
/Users/stevenpeterson/bin/config/vm/<disk>, but the lab actually lives at
<project>/config/vm/. One directory move darkened the whole lab and nothing recorded it.
appliance -> appliance-run.qcow2 DISK-EXISTS: NO
fabric-root -> fabric-root.qcow2 DISK-EXISTS: NO
pi-imaging -> overlay.qcow2 DISK-EXISTS: NO
workstation -> workstation.qcow2 DISK-EXISTS: NO
2. QCOW2 backing chains are broken by the same move: large-blobs/ec281818... is a 6.4G delta whose
base IS present (config/vm/noble-arm64-base.img, 586M) but is referenced at the pre-move path.
3. 3.46G of large-blobs are BYTE-COPIES of the live lab (verified by sha256: noble-arm64-base.img,
raspios-trixie-arm64-lite.img, appliance-run.qcow2 each exist twice).
4. 7.24G are deltas whose bases reference /Users/steve/... — a pre-crash machine/username. Dead.
DELIVERABLE: checkouts/current/congruency/tools/vm_registry.py — standalone, stdlib + virsh + qemu-img.
1. SCAN (read-only) and register one artifact per VM image found, kind='forge-vm':
* libvirt domains: `virsh -c qemu:///session list --all`, then dumpxml for each disk path.
* the image lab: config/vm/*.{qcow2,img}
* the blob stores: config/forge/large-blobs/<sha256>, config/forge/artifacts/blobs/<xx>/...
Dedup by forge_sha — the SAME bytes in two places is ONE artifact with two locations, not two
artifacts. That is the whole point; record every location it was seen at.
2. RECORD, per artifact: name, forge_sha, size, format (qcow2/raw/iso), and — this is the part whose
absence caused every problem above — the RESOLVED STATE:
* backing_file (from `qemu-img info`), and whether that base EXISTS (by path, and by sha if the
base is itself registered)
* which libvirt domain(s) reference it, and whether the xml's path resolves
* status: LIVE (domain + disk resolve) | ORPHANED-DELTA (base missing) | STALE-PATH (file exists
but the referrer points elsewhere) | COPY (byte-identical to another registered artifact) |
UNREFERENCED (no domain, no chain)
3. REPORT the lab honestly: a table of every VM, its status, and for STALE-PATH the path it WANTS vs
the path where the bytes ACTUALLY ARE. That report alone answers "which VM is which".
4. --json for machines; a readable table by default.
5. READ-ONLY. Do NOT rebase, do NOT edit any libvirt xml, do NOT move or delete a byte, do NOT write
to the db except via artifact.record. This tool DESCRIBES the lab; it does not repair it.
Repair is a separate, operator-authorized ticket.
6. --selftest against a THROWAWAY temp dir: synthesize a qcow2 with a present base (-> LIVE), one
with a missing base (-> ORPHANED-DELTA), two byte-identical files (-> ONE artifact, two
locations, COPY), and a file whose referrer points at a moved path (-> STALE-PATH). Skip cleanly
with a NAMED reason if qemu-img is absent. No network, no live db, no writes outside the temp dir.
ACCEPTANCE: --selftest green; a live read-only run registers every image in config/vm and both blob
stores as kind='forge-vm' artifacts via artifact.record; `artifact.list` returns them (the table goes
from 0 rows to populated); the report correctly marks all 4 domains STALE-PATH naming
~/bin/config/vm vs config/vm, marks ec281818 ORPHANED-DELTA-with-recoverable-base, marks the 3
verified duplicates COPY, and marks the /Users/steve/ deltas ORPHANED-DELTA (unrecoverable); nothing
on disk is modified. RUN ON CLAUDE.