Artifact 44b287ad9cddf487363bdc16f5088ffee1fd0e163b459bada4845fa2c30fa57f:
- File docs/local-tickets.md — part of check-in [2c511157d8] at 2026-09-16 22:45:34 on branch ticket-door — every door into the artifact log takes the same letter (user: dmestas, size: 5929) [annotate] [blame] [check-ins using] [more...]
Local tickets
Fossil's tickets are artifacts, and artifacts are forever: every typo, duplicate and piece of spam a tracker accepts becomes part of the repository's permanent, synchronized history. That is the right property for the tickets that matter — provenance you can trust decades later — and the wrong one for an open intake door.
Quarry adds a second tier in front of the artifact log. A local ticket lives in the forge's own database (oltp), never in the zit store. It renders in the same tracker views, wears the same fields and vocabulary, and can be triaged like any other ticket — but it does not sync, it can be deleted outright, and it enters the fossil only when an admin promotes it.
The two tiers
| local ticket | fossil ticket | |
|---|---|---|
| lives in | forge database (oltp) | zit store (artifacts) |
| filed by | anyone holding cap n |
signed-in accounts, n |
| deletable | yes — discard, gone | no (shun is the only out) |
| travels with clone | no | yes |
| promoted | by an admin, one click | is the promotion target |
Who may file
The gate is fossil's own capability system — no new settings surface.
ticket.intake (the local door) requires capability n, resolved
through the category closure:
- Stock behaviour: any signed-in account holds the
anonymouscategory (hmnc— n included), so members file local tickets freely. - Open intake to strangers: grant
nto thenobodycategory for that repository in settings. Visitors then file local tickets without an account. The web form adds a honeypot field and a per-address rate limit; a discarded spam ticket vanishes without trace. - Close intake: blank
nfrom the categories, as fossil admins always have.
Filing directly into the fossil takes a — the same letter
ticket.promote and ticket.discard take, because all three touch the
permanent record. The web form has always worked this way: it offers
the "record in the fossil now" box only to a caller holding a, and
files everybody else through intake.
ticket.new itself used to take n, so the API disagreed with the
form beside it: any Reader could POST straight into the artifact log
and bypass the tier entirely. That is how eleven tickets from an
account with no check-in letter came to be permanent history on this
forge. The two doors carry the same letter now.
Nothing is taken away. n still files — through intake — and an admin
promotes. A forge with no local tier has no oltp, and so no accounts
and no capability enforcement at all, which is exactly where the old
direct door has to stay open.
The fields a tracker defines
A fossil tracker's schema is the repository's own: it may define
whatever columns it likes, and across the stores this forge mirrors
they do — every one carries type, severity, priority,
subsystem and foundin, and several add assignee, closer,
submitter and closedate. Quarry has always READ all of them. It
could write only fossil's defaults, which made it a second-class
client of its own stores: a tracker with an assignee could be read here
and edited only in fossil.
Both write ops — ticket.new and ticket.set, and the local
ticket.intake — take a generic fields map beside the named inputs,
and the forms render one text input per column under
fields.<name>. Three rules bound it:
- The tracker's vocabulary is the gate. A name the tracker does not
already carry is refused. Fossil creates a column on demand, so an
unchecked map would make every ticket write a schema migration, and
one typo would leave a tracker with an
assignecolumn forever. The vocabulary is read from the tickets themselves (ticket.configreports it asfields): there is no verb that reports a schema, so what the tickets carry is the answer, and a column nobody ever set is one no form needs to offer. - A field with its own input is refused here. One value with two doors is a disagreement waiting for its first save.
- What fossil withholds, quarry does not author.
private_contactandcontactare labelled "Not publicly visible" in fossil's own template; writing one through a forge form would put an address into a permanent, synchronized record from a page that never showed it.
An empty value CLEARS the field — the only way to unset one somebody set by mistake. A local ticket is gated on the same vocabulary as the fossil tier, because promotion lands it in that tracker.
Identity and links
Local tickets mint random 40-hex ids, so /r/{repo}/ticket/{id} and
[/tktview/…] references work identically across tiers. Listings mark
local rows with a local chip and filter on is:local.
Promotion
Promotion is deliberate distillation, not replay. An admin promoting a
local ticket writes two artifacts: one ticket new carrying the
folded field state and description, with provenance recorded in the
text (reported by whom, when, promoted by whom); and, when discussion
exists, one ticket set whose comment collates it verbatim, each entry
prefixed with its author and date. The change-by-change history of the
local tier is working noise and stays out of the permanent log — that
is the point of the tier.
After promotion the local id becomes a tombstone: the old URL redirects to the fossil ticket forever, and the mapping is the only thing kept.
What to know before relying on it
Local tickets live in one forge's data directory. They do not clone, do not sync, and are not recovered by rebuilding a forge from the store — back up the oltp database if the intake queue matters to you. Anything whose loss would hurt belongs in the fossil: promote it.