From the review: a taken key that cannot be read back, or a write never confirmed, now tells the caller to get the number before opening again; an owner entry naming a module is owned only by that module registered; link targets get the secret guard; line separators are refused in one-line fields; the acceptance test now proves the counter's own updates were contended.
mesh-issues
The issue tracker: the mesh numbers and tracks its issues in a module of its own (novox/hq ADR 0275,
to-be 53). An issue is a question about the mesh that someone must answer; this module holds the
mesh's issues and hands out their numbers. It holds the seat issue-tracker, once for the mesh, on the
control-node.
Built on the model of hits, a headless issue tracker on NATS, studied at release v0.7.0: its statuses, fields, append-only notes, typed links and compare-and-set numbering are taken as ideas. It carries none of its code, under a licence that is not an open-source one.
Verbs
Called as issue-tracker.<verb> through the mesh's tools; each says in its description what it does and
what it refuses.
| verb | takes | does |
|---|---|---|
open |
title, report, optionally located-in, record |
checks the issue, then takes the next number and keeps it under that number; answers the number |
get |
number |
the issue whole: fields, notes, links |
list |
optionally status, owner, phrase, limit |
short rows, newest first: number, status, owner, title |
move |
number, status, and what that status needs |
changes the status under the development cycle's rules |
note |
number, text |
appends a note; a note is never edited |
link |
number, kind, target, optionally remove |
adds or removes a typed link |
The cycle's rules, at the write (hq 00-META/checks/cycle.py): a status is one of open,
diagnosing, located, resolved, wontfix; located needs located-in; resolved needs
located-in and fixed-by; a core issue (located in the controller, the node-engine, the tool runner,
the SDK, or the catalogue's bus, forge or build agent) resolves with replay or replay-none; wontfix
needs a reason. A refused change changes nothing and says which rule refused it.
An issue with a folder in hq (record at open: the folder's name, without the number) keeps its
status in that folder's report until the import of slice 3. The tracker holds its number, title, folder,
notes and links, and move refuses it, saying where its status lives.
Every argument is read strictly. One a verb does not take is refused, never dropped. Every field has a
size limit, a one-line field takes no control character, a folder's name is no path, and text shaped like
a credential (a private key, an API key, a forge or bus token, password=…) is refused: an issue is read by
every agent on the mesh. The shapes catch the ordinary mistake, not every secret.
Numbers
One counter, the key issue of the state counters, advanced only by compare-and-set: the SDK's
State(...).Increment reads it and writes the next number only at the revision it read, so two openers —
in one instance or two — never take one number. A write another opener won is read and tried again, a
bounded number of times with a short random wait; past the bound the open is refused and takes no number.
- A refused open takes no number: an issue is checked before its number is taken.
- A number is never handed out twice, and may be skipped. A number taken and then not kept — the bus
failing between the counter and the issue — stays unused, and the answer says which. A write whose answer
was lost is recognised as its own when tried again, so it leaves no gap; when it cannot be confirmed either
way, the answer says the issue may have been written and to
getthat number before opening it again, never that nothing was opened. - An issue is never overwritten. It is kept by a write that is refused when the key exists; if the counter ever hands out a number already kept, the open is refused loudly, the number stays unused, and nothing is overwritten.
- The counter starts once, the first time a number is taken: at the setting
first-number, or after the highest issue already kept, whichever is higher, made only if it does not exist. The setting is never read again, so a number never moves.
State and events
Two states, declared in the manifest and made by the controller; the module never makes a bucket
(ADR 0201): issues, one key per issue by number, keeping 8 values per key; counters, one value per key.
Neither expires. One issue is kept in at most 256 KiB; a note that would pass it is refused.
Every change is an event: mesh-issues.opened, .moved, .noted, .linked, each with the number, what
changed, who changed it and when. The events are the audit trail. A change whose event the bus did not take
is kept, and its answer says the event was not said (announced: false).
Who acted
ADR 0275 §5 records the caller from the bus credential, never from an argument. The runtime does not yet
tell a module who called it: a tool call reaches a bundle as a name and arguments, and the tool runner is
not told who called it either (hq issue 340). Until it does, every change is recorded as by unproven, no
verb takes an argument naming a caller, and none can be given one.
Settings
{
"first-number": 365,
"owners": ["hq", "mesh-catalog", "mesh-catalog modules/nats", "mesh-controller", …]
}
first-number— the number the first issue gets: the one after the highest issue number on hq's main and every open branch at the moment the module is switched on. Read once.owners— the registered listlocated-inmay name: repositories, and modules asmesh-catalog modules/<module>. An entry oflocated-inis an owner, or an owner followed by a space or a slash and where in it (mesh-controller internal/broker,mesh-catalog modules/nats/module.json). A module must itself be registered:mesh-catalog modules/<module>is never taken as a place inmesh-catalog. It starts with the mesh's repositories and the modules the catalogue held when this module was written; a module added since is added here through the controller's settings verb.
Tests
go test ./... in this directory. The acceptance — an agent opens an issue and gets the next number; two
agents opening at the same moment never get the same one — is
TestFiftyOpensAtOnceFromTwoInstancesGetFiftyDistinctConsecutiveNumbers: a JetStream server linked into
the test, two instances of this bundle (the test binary started again) speaking to a runtime played by the
test against the buckets the manifest declares, fifty opens at once, fifty distinct numbers from the first
in a row. It counts the writes lost to another opener, so it fails when the openers never collided. With
the counter written without its revision check, it fails.