# 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 `anonymous`
  category (`hmnc` — n included), so members file local tickets freely.
- **Open intake to strangers**: grant `n` to the `nobody` category 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 `n` from the categories, as fossil admins
  always have.

Filing *directly into the fossil* is unchanged: `ticket.new` still
requires a signed-in account with `n`, and the web form offers it only
to those who hold it.

## 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 `assigne` column forever. The
  vocabulary is read from the tickets themselves (`ticket.config`
  reports it as `fields`): 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_contact`
  and `contact` are 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.
