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.
VM registry -> DIRECTORY SERVICES (v2, supersedes #454): register every VM image as kind='forge-vm' via artifact.record so the universal resolver (#193, DONE) can FIND it. Gate: resolver.py <sha256> must resolve noble-arm64-base.img, where today it returns 0.
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.
7. DIRECTORY SERVICES — THIS IS THE POINT, NOT A SIDE EFFECT. The universal resolver (#193, DONE,
checkouts/current/congruency/tools/resolver.py) ALREADY probes 'forge-artifacts' when handed a
sha256. Verified today:
resolver.py cafa1a965b591b7c4184b484ffd8e625981a79d48f9b4ae8a4adf7b4c5ade927
-> kind: sha256 | probes: content-sha256, forge-artifacts, git-object-db
-> resolved: 0 blob(s) (no match — the collection is empty)
That sha IS config/vm/noble-arm64-base.img, 586M, sitting in config/forge/large-blobs under that
exact filename. The lookup path exists; the registry is empty, so a live 586M VM is INVISIBLE to
the directory service. Registering it fixes that with no new query surface.
NOTE the resolver is READ-ONLY and rebuilds nothing; do not modify it. Just make the artifacts
exist so its existing probe finds them. (source_dedup.py, #194 DONE, then clusters those matches.)
ALSO: forge_refactor_ticketer.py's docstring claims "#193 (the universal resolver) and #194 (the
dedup engine) are both still REQUESTED — no resolver module exists to import today." That is
STALE — both are DONE and on disk. Do not repeat that claim anywhere.
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. AND — THE DIRECTORY-SERVICE GATE — after the run,
`resolver.py cafa1a965b591b7c4184b484ffd8e625981a79d48f9b4ae8a4adf7b4c5ade927` MUST resolve to a
match naming config/vm/noble-arm64-base.img (and its large-blobs copy) via the forge-artifacts probe,
where today it returns 0. A populated table that the resolver still cannot see is a FAILED ticket.
RUN ON CLAUDE.
patch
none
approval
unapproved
runs
no runs recorded
mandate (clauses)
not yet recorded — lands with the ticket-class build
manifestations
not yet recorded — lands with the ticket-class build
guarantees
not yet recorded — lands with the ticket-class build
journey (blunders & successes)
not yet recorded — lands with the ticket-class build