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.
resolver.py: an artifact hit must BE a match — it prints 'forge artifacts: config/vm/noble-arm64-base.img' and 'no match — the collection is empty' in the same output. The match shape is source-only; VMs can be reached but not counted.
goal
The universal resolver FINDS an artifact and then reports "no match" in the same breath. It can REACH
a VM but cannot COUNT one. Fix the contradiction; do not rebuild the resolver.
MEASURED, re-runnable today (the registry is populated — 21 rows, 20 forge-vm + 1 forge-source):
python3 tools/resolver.py cafa1a965b591b7c4184b484ffd8e625981a79d48f9b4ae8a4adf7b4c5ade927
identifier: 'cafa1a96...'
kind: sha256
probes: content-sha256, forge-artifacts, git-object-db
forge artifacts (content-addressed by sha256): config/vm/noble-arm64-base.img <-- FOUND IT
resolved: 0 blob(s) -> 0 de-overlapped match(es) <-- DENIES IT
(no match — the collection is empty) <-- LIES
That sha is config/vm/noble-arm64-base.img, 586M, registered by #455 as kind='forge-vm', and ALSO
present in config/forge/large-blobs under that exact filename. The forge-artifacts probe prints the
path. The match collection stays empty. Both statements are in the same output.
WHY (diagnosis — verify it before acting): #192 specifies every match normalizes to
{repo, path, line-range, identifier-kind, associated tickets, score} — a SOURCE-LOCATION shape. A VM
image has no repo and no line-range, so an artifact hit apparently cannot become a match and is
demoted to a side-note. That is a reasonable original design (the resolver was built to find SOURCE)
that became wrong the moment ticket_artifacts started holding non-source artifacts.
DELIVERABLE: checkouts/current/congruency/tools/resolver.py
1. AN ARTIFACT HIT IS A MATCH. If the forge-artifacts probe resolves a sha256 to a registered
artifact, it MUST appear in the normalized match collection and be counted — not printed as a
footnote while the summary says "no match". A resolver that prints the answer and then denies
finding it is worse than one that finds nothing: it teaches the reader to distrust the summary.
2. NORMALIZE HONESTLY, DO NOT FAKE A SOURCE SHAPE. Do not invent a repo or a line-range for a VM.
Extend the match shape so a non-source artifact is representable: identifier-kind='artifact'
(or the registered `kind`, e.g. forge-vm / forge-source), path = the registered location(s),
repo = NULL/absent where there is none, line-range absent, tickets = the minting ticket_id from
ticket_artifacts, score as the other probes score an exact content hit. State the shape in the
docstring. Absent fields must be ABSENT, never zeros or empty strings pretending to be data.
3. ONE SHA, MANY LOCATIONS, ONE ARTIFACT. The same forge_sha registered at several locations is ONE
artifact with several locations — that is the point of content addressing, and #455 already
registers it that way. Do not emit N matches for N locations; emit one match carrying its
locations. Keep de-overlapping consistent with how the source probes already de-overlap.
4. DO NOT BREAK THE SOURCE PATH. Every existing identifier mode (name / file-name / uuid / git-hash /
similarity / ticket-ref) must behave EXACTLY as before. This is additive: artifacts become
representable; source matches are untouched. Prove it — the same query for a source identifier
must return the same collection as before the change.
5. READ-ONLY, rebuild nothing. resolver.py's own docstring: "Read-only: it reuses omni-git and the
#189 vector index and rebuilds nothing." Preserve that exactly. No writes to any db.
6. OMNI IS BLACK-BOXED. omni-git is a deduplication DEVICE and was a TEST, not an archive and not a
git — every ordinary git command against it fails (rev-parse HEAD prints the literal "HEAD";
ls-tree/log are fatal). Do NOT start walking it. If the content-sha256 probe already consults
omni's blob index, LEAVE THAT AS IT IS — this ticket touches the forge-artifacts probe and the
match normalization only.
ACCEPTANCE (all live, all re-runnable):
* `resolver.py cafa1a965b591b7c4184b484ffd8e625981a79d48f9b4ae8a4adf7b4c5ade927` returns >= 1 match
naming config/vm/noble-arm64-base.img, with identifier-kind reflecting the artifact and the
minting ticket present. It MUST NOT print "no match" — today it prints both.
* `--json` includes that match in the collection (not only in prose).
* A sha registered at MULTIPLE locations returns ONE match carrying all its locations.
* A sha256 that is registered NOWHERE still returns 0 matches and says so — no false positives.
* REGRESSION: pick any source identifier that resolves today, run it before and after, and show the
match collection is IDENTICAL. Source behaviour is untouched.
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