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
Port ONE unit of congruency to Go. ONE. This ticket is deliberately SMALL: porting a whole
layer at once is what #230 corrected (~150 units exist; a layer-sized tournament fails at that
size). Do not port anything outside this unit.
UNIT KEY : go-port:tags:tag:TicketList
(the decomposer's idempotency marker — it lives in this ticket's TITLE. Do not edit
the title, or the unit will be re-filed as a duplicate.)
LAYER : tags — ticket #226 (the tag engine + invocators rendering identical HTML (execute_all_tags in Go))
PHP SOURCE: invocators/tags/dev/TicketList.php
THE PHP ORIGINAL — the contract:
invocators/tags/dev/TicketList.php
It is a `Tag_Interface` tag; the engine substitutes
it wherever <<<TicketList>>> appears in a template, and the gallery renders it standalone at
?page=tags&tag=TicketList (TagList::scan finds it under TAGS_DIR, so that URL resolves). Port THIS
tag only — its arguments, its SQL, its markup, and its empty/error state (a tag that degrades
gracefully against a missing dependency must degrade IDENTICALLY in Go; see
tools/tag_selfcheck.py for what each tag depends on).
PAGES WHOSE TEMPLATE EMBEDS <<<TicketList>>> (read out of Document_Templates with the
engine's own pattern). These are CONTEXT, not a gate — they tell you which argument
forms the live site actually uses, so you port the ones that matter:
tickets
DEPENDS ON (build these first — each is its own unit ticket unless marked):
- go-port:tags:engine:engine_tag_wrapper
- go-port:tags:engine:engine_tag_parser
- go-port:tags:engine:engine_tagarguments
- go-port:tags:engine:engine_tag_interface
THE BUILD — small, and ITERATIVE:
1. Implement ONLY this tag in the Go port, and REGISTER it (see THE GATE below).
2. Run the harness command in THE GATE below.
3. If this tag's line is a BYTE MISMATCH, fix the Go side and go back to 2 — the
harness prints the first differing lines. The PHP is the spec: when they disagree,
the Go port is wrong; never 'improve' the behaviour here. If the PHP is genuinely
defective, file a separate bug ticket and port the defect faithfully.
THE GATE — THE ONLY THING THAT SETTLES THIS TICKET. Run the #282 tag-equivalence harness
from the repo root (the directory holding registry.json):
cd congruency-go && go test ./tags/ -run TestTagEquivalence -count=1 -v
SETTLE WHEN THAT COMMAND PASSES with TicketList registered. The harness answers exactly this
unit's question — it renders THIS tag through tags.RenderTag (the same entry server.go's
handleTagsPage calls) and compares it byte-for-byte against the live PHP's bytes for
?page=tags&tag=TicketList. Paste this tag's own lines as the evidence:
--- PASS: TestTagEquivalence/TicketList
equivalence_test.go:NNN: IDENTICAL: <N>B byte-for-byte
A SKIP IS NOT A PASS. A line reading `--- SKIP: TestTagEquivalence/TicketList` with "no Go port
yet" means the tag is NOT registered and NOTHING was measured — and the sweep still exits 0,
green. Registering it (below) is what turns that skip into a measurement. Read this tag's own
line; never the exit code alone.
DO NOT WHOLE-PAGE-DIFF ?page=tags. THIS IS NOT THE GATE, AND A NON-EMPTY DIFF HERE IS NOT A
DEFECT IN YOUR PORT:
diff <(curl -s 'http://127.0.0.1:8899/?page=tags&tag=TicketList') <(curl -s 'http://127.0.0.1:8900/?page=tags&tag=TicketList') # <-- WRONG GATE
That diff is STRUCTURALLY NON-EMPTY for EVERY tag — including tags already merged as
byte-perfect: #323 measured ~6205B of difference on CategoryView, TreeView and TicketList,
all three of them correct. The two sides are not the same kind of document: the PHP URL returns a
full CMS page (Controller::control's chrome, then the gallery, then the tag's bytes, then
the template tail), while the Go server wraps the tag engine's render in its OWN frame and
says so in its source — congruency-go/server.go:820-823 emits the comment "the
doctype/title/h1 frame around it is this server's own: the CMS Controller::control() page
chrome is still not reproduced here." The difference is that missing chrome. It is a known,
separate, un-ported piece of the server; it is not this tag, it is not yours to fix here,
and no correct port can ever make that diff empty. Settling on it is what stalled the tag
fan-out and forced #310/#311/#312 to be hand-harvested (#323).
The harness exists precisely because the tag's bytes are a SLICE of that page: it cuts the
slice with TagList.php's own anchors (:62 and :76) and a page tail it MEASURES at run time,
fetches PHP twice around the Go render so a concurrent write shows up as VOLATILE instead of
a false failure, and compares only the tag. That is the comparison this ticket is asking for.
Read congruency-go/tags/equivalence_test.go before arguing with its verdict.
REGISTERING TicketList (the gate cannot pass until you do this):
Add an init() to your tag's own file in congruency-go/tags/, exactly as every ported tag does
(congruency-go/tags/contenttag.go:151-153 is a one-tag example, and engine.go:1374-1385
registers the ten #226 landed together):
func init() {
Register("TicketList", func(e *Engine, a *TagArguments) Tag { return &TicketListTag{e: e, args: a} })
}
Register is the registry's ONLY writer (engine.go:1342-1368) and it PANICS on a duplicate
name, so register in exactly ONE place. Registering is what flips tags.IsPorted("TicketList") to
true, and THAT is what makes the harness measure this tag instead of skipping it — no edit
to congruency-go/tags/equivalence_test.go is needed, or wanted.
TAGS THIS TAG DEPENDS ON: none. Its own output embeds no other tag's invocation (derived
from its PHP source, comments excluded — see the decomposer's tag_emits()), so nothing
else has to be registered before the gate can measure it.
WHAT THE HARNESS DOES NOT MEASURE — port these anyway, do not bolt them onto the gate:
* ARGUMENTS. The gallery constructs the selected tag standalone (`new $sel(null)`,
TagList.php:67-68), so the sweep measures the ARGUMENT-LESS render only. If this tag takes
arguments, port them faithfully against the PHP source and the argument forms the pages
above use; a Go unit test of your own is the right place to pin them.
* THE IN-PAGE SUBSTITUTION SITES. Diffing ?page=<id> on both servers is the same broken
whole-page comparison as above and fails for the same chrome reason. Those pages settle on
their own page units, once the chrome is ported.
IF THE HARNESS ITSELF SKIPS EVERYTHING ("no API key", "state db absent", "server
unreachable", "TAGS_DIR lists no tags") it measured NOTHING and this ticket does not settle.
Its four guards are deliberate — it never false-fails in CI — so a green run that skipped is
not evidence. Fix the environment: boot the PHP original the way tools/serve.py does
( cd checkouts/current/congruency && /Users/stevenpeterson/code/installed/jazz-project/congruency/tooling/congruencey-harness/php/php -S 127.0.0.1:8899 boot/router.php )
and re-run. The harness resolves the db, the app root and the URL itself; its overrides are
$CONGRUENCY_API_KEY, $CONGRUENCY_APP_ROOT, $CONGRUENCY_EQUIV_DB, $CONGRUENCY_TAGS_URL
and NOTHING ELSE — in particular CONGRUENCY_SQLITE is NOT one of them, so prefixing the gate
with it changes nothing and would quietly measure the default database.
SETTLING: this ticket settles when — and only when — the harness command in THE GATE
above PASSES for this tag. Paste the command you ran and this tag's own PASS/IDENTICAL
line as the evidence. Do NOT settle on a whole-page diff of ?page=tags, and do NOT
refuse a correct port because that diff is non-empty: it is non-empty for every tag,
always, for a reason THE GATE explains and no port can fix. A SKIP is not a PASS.
Kind is `build` (a pair: coder + adversarial tester). This unit gets a tournament only if
someone judges it genuinely worth one — that is the exception on #230, not the default.
Filed mechanically by checkouts/current/congruency/tools/go_port_decomposer.py under ticket #230.