control roomdocs/GLOSSARY.md

Glossary

Terms this project uses in a specific way

Glossary

Terms this project uses in a specific way, plus a few that were used loosely and should not have been.

If a term is not here and it is not ordinary English, it should be. Add it or stop using it.

docs/LEXICON.md is the companion for somebody reading reports rather than editing the repo: fewer terms, each with why the distinction exists and a real example. This file stays the reference.

This file is for words this repository coined or bent. Terms owned by somebody else - an RFC, a vendor, an industry - live in docs/BORROWED-TERMS.md, and the rule there is different: an entry says what the term means for us and cites the real definition rather than paraphrasing it, because a glossary that restates a standard is wrong the day the standard moves and wrong in a way nobody notices. The owner asked for the split on 2026-09-15 after three email-authentication terms were used at him.

A term that already means something else

If a word is already in use outside this repository and we use it differently, the entry must say so explicitly, and the divergence has to be checked with the owner before it is adopted.

His instruction, 2026-09-17, and the instance that produced it: he looked up P0 and found that our definition is not the standard one. Everywhere else, P0 is a severity a person assigns - often "the site is down, drop everything". Here it is derived arithmetic, value + urgency, and nothing about it means drop everything. Same token, different referent, and the entry below did not say so.

That is a worse failure than undefined jargon. An undefined term makes a reader ask. A term that quietly disagrees with its common meaning makes a reader think they already know, and they carry the wrong model until something breaks. docs/BORROWED-TERMS.md exists for words we take from elsewhere and use as they are meant; this rule is for the opposite case, where we keep the word and change the meaning.

So an entry for a diverging term carries three things: what it means here, what it means elsewhere, and why we diverged. And the divergence is a decision the owner makes, not one an agent makes on his behalf while writing a checker.

Candidates worth auditing against this rule, none of them checked yet: unit, lane, quest, walk, lens, gate, frontier, epic. Each is a common word in software with an established meaning, and each is used here in a way that may or may not match it.

P0, P1, P2, P3

A ticket's priority, and it is derived, never declared. docs/TICKETS.md uses the labels and defines them nowhere, which is why the owner had to ask on 2026-09-17.

points = value + urgency high = 2, medium = 1, low = 0

pointsbandmeans
4P0both value and urgency are high
3P1one is high, the other medium
2P2
0-1P3

Ask the tool rather than the table: python3.12 scripts/score.py high medium prints 3 points -> P1. scripts/score.py --check fails a ticket whose declared priority disagrees with its derivation, which has caught the coordinator twice.

Which population you count decides the answer, and this entry got it wrong once. "Live" means three different things here: 242 unbuilt and dispatchable, 460 not withdrawn or merged or accepted, 484 not withdrawn. The P0 count over those is 16, 75 derived, and 67 as the declared field on the 460 pool.

This entry first said "67 live tickets were P0 - a sixth of the backlog, so the scale saturated". That was the declared field over a pool including work already built, and the figure that matters is 16 of 242 dispatchable, about seven percent - an ordinary P0 rate. OPS-143 caught it. The saturation claim is withdrawn.

What survives is narrower and still true. Both inputs are self-reported at write time and nothing corrects optimism or revises a score when the world changes, so P0 is a claim about a ticket's author as much as about the ticket. security_impact: high discriminates better because it is a claim about consequence rather than importance.

Always name the population. docs/state.json does - it carries its note and its derivation, and every count here should be read from it rather than recomputed by hand, which is how this entry went wrong.

tokens per unit

The cost of a merged unit in tokens, and the primary measure of whether a way of working is efficient. A unit is what it already means here: one PR-sized, independently verifiable step - an atomic branch.

Deliberately not given a fancier name. docs/PROCESS.md forbids adding a second name for something that already has one, and "unit" was already this repository's word for the thing being counted.

Measured on 2026-09-16, which is the first day anybody counted:

shapetokens per unit
flat - coordinator dispatches one agent per unit299,345 over 8 units
two-tier - a team lead dispatches members, see docs/DELEGATION.md~279,000 per unit-equivalent

The second figure carries a caveat that belongs with the number: the two-tier unit landed about five units of scope in one, so its per-unit figure is a division rather than a direct observation.

The mechanism that moves it is brief width, not delegation shape. Members under a team lead averaged 200,487 tokens where agents under a coordinator averaged 299,345, because a narrower brief needs less context to execute. An agent's cost is roughly orientation plus work, orientation is close to fixed, and docs/ORIENTATION.md exists to lower the fixed half for everybody.

turns per unit

The twin of tokens per unit, and the one that decides how long a session can run. Every coordinator turn resends its whole context, so turns are what grow the context that eventually ends a run.

The two move in opposite directions and that is the whole tension. Working inline is cheapest in tokens and most expensive in turns. Two-tier delegation is the reverse. Measured the same day: roughly fifteen coordinator turns per flat unit, against about eleven for five units of work under a team lead.

A shape is worth keeping when it improves one without ruining the other. Quoting either number alone is how a shape that merely moved cost gets reported as a saving.

The pipeline

Unit - one piece of work that goes through the seven stages of docs/PIPELINE.md. Sized so it lands before its worktree goes stale.

Stage - one of the seven steps a unit passes through: request, contract, build, review, integrate, verify, hand back. Every stage boundary is an agent boundary.

Contract - contracts/<id>.md. What a unit must do, expressed as acceptance criteria that are executable commands. Written before the builder starts.

Criterion - one Given / When / Then with an exact command, its working directory, its environment, and a Source: line of confirmed or plausible. A criterion phrased as prose is not a criterion.

Signature - integrations/<id>.md. The integrator's record of having wired a unit into the running system and run the contract against it, with real pasted output. May be a rejection. Goes stale when a file the unit owns changes after signed_at.

Verdict - what a verifier appends to a signature. One of CONFIRMED, CONFIRMED WITH GAPS, SIGNATURE REJECTED (the work is sound, the claim was overstated - returns to stage 4), or UNIT REJECTED (returns to stage 2).

Gate - an executable check that fails the build. See docs/VERIFICATION.md. A rule with no gate is a comment.

Lens - lenses/<group>/<id>.md. One reviewing perspective, with a mandatory non-empty runs list of commands it must execute. A lens that only reads cannot detect that code does not run, which is lesson 5.

Path - a long-lived workstream in docs/PATHS.md. A path does not finish, it advances. Units live inside paths.

Handover - an entry in HANDOVERS.md: a change an agent could not make because another agent owned the file, with the exact lines and the owner.

Role card - components/<name>/README.md. Frontmatter stating a component's role, owns, does_not_own, depends_on, provides and wires_into.

Slots, and the surfaces that have them

The owner asked what "nine slots over seven surfaces" meant on 2026-09-15, after it appeared in a message to him. It was internal shorthand and it should not have been.

A surface - a place a person meets the product without meaning to. Seven of them: the 404 page, the 405, the invite email, an empty list, an error under a form, the account-deleted page, the beta banner, and the sign-out message. Each exists in the kit as a mechanism - the code that decides when to show it - and each needs words, which the mechanism cannot supply because the words are the product's and the mechanism is the template's.

A slot - a labelled blank the founder fills. A heading in the product's own src/app/VOICE.md, with a sentence under it. Write the sentence and the page uses the founder's words; leave it and the page uses the template's default, which is deliberately usable rather than a placeholder, because a founder who has not written any words yet is the normal case.

Nine slots over seven surfaces, because the count is per slot and not per surface: the 404 and the 405 are two pages with two sentences, and an empty list needs two things - a sentence, and the name of the control that fills it. That second one is the only slot in the kit with no default at all, because no template can name a button in a feature it has never seen, and a wrong name sends a person to press something that is not on the page.

Why a slot rather than just editing the template. A slot is checkable. A flow declares the headings its output must fill, accept refuses an output that left one empty, and make surface-slots counts how many of a product's nine carry its own words. Editing a template file is a change nothing can count.

The one invariant

The one invariant - the single sentence that, if it is false, means the product is broken rather than incomplete. The owner asked what it meant on 2026-09-15 and it had been used at him repeatedly without being written down.

It is not the feature list and it is not a goal. docs/PRODUCT-PATH.md step 2 gives the test for whether you have found one: it contains a WHERE clause, or it names a permission. Usually both.

"Users can save notes" is a feature. "The WHERE is the authenticated caller's own id, and no route takes a row id" is an invariant.

Three real ones from this repository:

Why one and not several. A product with five invariants has none, because nothing ranks them when they conflict. The single sentence is what a reviewer attacks first and what a builder may not trade away for a feature.

Why it is the first item on the minimum handoff bar. It is simultaneously the product's purpose and its security model, so a builder who has it can derive the access-control matrix, and a builder who lacks it invents one. It is also the thing a founder can answer in a sentence and an agent cannot guess.

The industry's specification documents

Seven artifacts the product-management literature recommends. The owner had not met the acronyms and they were used at him in an agent brief on 2026-09-15, so they are written down. Each entry says what it is and what, if anything, of it is worth keeping for one founder whose builder is an agent.

PRD, product requirements document - what the product does, for whom, and why. The least ceremonial of the seven and the closest to what the interview in PROD-042 produces. The useful kernel is most of it.

BRD, business requirements document - business objectives, stakeholders, expected return. An artifact of procurement: it exists so one department can justify spending money on another's behalf. There is no second department here.

MRD, market requirements document - market size, segments, competition, positioning. Written to persuade someone to fund a bet. The useful kernel is narrow and the owner already derived it independently: what similar products exist, what do you like about them, what do you dislike. The dislikes are where uniqueness comes from. That question survives. The market-sizing does not.

SRS, software requirements specification - numbered, individually testable requirements, formalised by IEEE 830 (1998, withdrawn 2011). Heavy, and its weight is the point in avionics and medical devices. The useful kernel is already in docs/PRODUCT-PATH.md step 4: write the criteria as attacks. An attack is a testable requirement that names its own failure.

TDD, technical design document - how it will be built: architecture, data model, interfaces. For this repo the template is most of the TDD, decided once and inherited. The per-product remainder is small, which is a property of having a template and not a reason to skip it.

Journey map - the steps a user takes toward a goal, with what they need and what they feel at each one. Distinct from a user story, which says what a user does. A story can be entirely satisfied while the journey is bad. The owner's own first-use findings were journey findings rather than story findings: "after sign in you get to a dead end", "page looks too long", "no logout". Nothing in the current document set would have caught them, and DOC-065 checked that story by story: six stories against three findings is eighteen cells and no hits. docs/PRODUCT-JOURNEYS.md is the proposed format, and it drops the feelings field - an adjective per step that nothing can check is prose that rots - keeping the founder's own "feels sloppy" in each step's prose instead.

Click resolution - the owner's phrase, 2026-09-15: "well-defines user stories I think to the CLICK resolution". A story at feature resolution ("a child spends points") is satisfiable by an implementation he would reject on sight. At click resolution a step names its surface, one string that must be on the screen, the control that is pressed by the words on it, and which step that press lands on. The last one is what makes the resolution checkable rather than merely finer: a dead end is a step with no successor, which is arithmetic. docs/PRODUCT-JOURNEYS.md.

RACI - a matrix of who is responsible, accountable, consulted and informed for each task. An org-chart instrument. One founder, so every cell is his name.

The product lifecycle

אפיון - Hebrew, the Israeli software industry's term for the requirements-definition phase: the work of deciding what a thing must do before anyone builds it. The owner uses it for the state between scaffold and built. Spelled spec in code and frontmatter, because this repo is ASCII throughout, and both spellings mean the same state in docs/PRODUCT-LIFECYCLE.md.

The interview - the conversation at spec that turns a founder's intent into committed documents a coding agent can read cold. For the builder, factory-side, never ships. Not the invite.

The invite - the mechanism by which somebody who is not the founder gets into a built product. For a reviewer or a client, kit-side, ships. Runs at review and later. Not the interview.

The minimum handoff bar - the eight things that must exist before the interview hands off to a builder, each one something an agent in this repo has previously had to guess. Listed in docs/PRODUCT-LIFECYCLE.md.

Measurement

The walk - the path from an empty directory to a serving application. Currently 15 steps. Asserted by tests/skeleton/test_scaffold_contents.py and the scaffold benchmark, not typed into a document.

Mutation - a deliberate, named break in the code, made to find out whether a test would notice. You change one thing that ought to matter, run the suite, and require something to go red. If nothing does, the test does not check what its name says it checks - it passes whether the code is right or wrong, and it will go on passing after somebody breaks that behaviour for real.

The word is used here in the sense of mutation testing, and it is a property of the test, not of the code. "Watched fail under a named mutation" in a report means: here is the break I made, and here is the test that died. A report that lists tests without that is listing things that ran, not things that would have caught anything.

Named is load-bearing. "I mutated the code" is unfalsifiable; "I removed the WHERE owner_id = :user clause from the signups lookup and test_signups_are_scoped_to_the_owner failed" is a claim a reader can re-run.

A surviving mutant - a break no test noticed. It is a finding, not a failure, and it gets reported rather than quietly fixed: it names the property that is asserted and unguarded. Some are equivalent mutants, where the change genuinely cannot alter behaviour, and those are reported as such rather than counted as coverage.

Three things learned about this the hard way, all on 2026-09-13:

Step versus workaround - a step is work a founder must do. A workaround is a defect wearing work's clothing. Tracked separately, because workarounds trending to zero is the real signal. 32 steps with nine workarounds became 15 with none.

Layer - one of the four things needed for a first dollar: a URL, a signup surface, email that arrives, money. Any claim to advance revenue must name its layer.

Confirmed versus plausible - confirmed means a command produced output demonstrating it, and that output was quoted. Plausible means it was reasoned about. Reading finds real things and may not claim confirmation.

Pinned - a reproduction recorded with its exact command, working directory, environment and commit. An unpinned reproduction is not one: two reviews reported mypy at 20 errors and at 28, both honestly, because neither had pinned the invocation.

Spending the quota

Point - one percent of the weekly token quota. scripts/budget.py says "this half may spend 12.3 points" rather than "12.3%" to keep two different numbers apart: the percentage already used, which is a fact, and the allowance still available to spend, which is an arithmetic answer from the owner's own rule. They are both percentages of the same thing and confusing them is easy.

The coordinator coined it and used it for a day before the owner asked. It is this repository's word, not an industry one, and plain "percent of the week" would have done.

Deep agent - one given a long window and told to reproduce, break and understand before fixing. Two to three hours, two or three at a time.

Wide dispatch - the opposite shape: many short agents, each on a narrow task, optimised for wall-clock throughput. Five or six at once, thirty to sixty minutes each.

The distinction is about what the work needs, not about generosity. On 2026-09-13 the wide shape was right while the owner was away and produced sixteen merged units in an afternoon. The same day proved where it is wrong: the findings that mattered most came from agents that constructed a failure rather than reading for one - four concurrency overlaps the repository had recorded as unreproducible, a guard emptied of the audit table it was built to protect, a health check that answered 200 with every table dropped. None of those is found by an agent working to a forty-minute rhythm.

Cost follows the same split, and it is per agent-run rather than per hour: three agents working three hours cost roughly what three working forty minutes cost, so depth is nearly free and width is not.

Under load

Three different questions, often said as one. None of them has been asked of this template: nothing here has served two requests at once except by accident, and every timing in the repository is a single request on an idle laptop.

Load testing - does it work at the volume you expect. Fixed rate, realistic mix, and the answer is a latency distribution rather than a yes.

Stress testing - where does it break, and how. Push past the expected volume until something gives, and the finding is which thing gives first and whether it fails closed. A connection pool exhausting is a different answer from a worker queue growing without bound.

Soak testing - does it still work in six hours. Same modest load, held. This is where leaks, unbounded tables and log volume show up, and it is the one most often skipped because it is the only one you cannot run in a minute.

Related and separate: contention, two agents or requests wanting the same thing at once, which is what FOR UPDATE SKIP LOCKED and the advisory lock in alembic/release.sh are for. A property under contention is not tested by running the same test faster.

Failure directions

Fail closed - when a control cannot do its job, deny. A door that locks when the power dies. An authorization check that refuses when it cannot reach the identity provider. Safe, and it can take a working system offline.

Fail open - when a control cannot do its job, allow. A door that unlocks when the power dies. An entitlement check that grants access when the payment provider is unreachable. Available, and it is a security hole the moment the control breaks.

Neither is universally right, and choosing is a real decision rather than a default. A fire exit fails open on purpose, because being trapped is worse than being burgled. A vault fails closed for the opposite reason.

This project has made the choice explicitly in two places: billing entitlement fails open with a staleness window, because a payment provider outage must not lock out paying customers, and RATE_LIMIT_REDIS_URL fails closed with a named error rather than starting an app whose rate limiting silently does nothing.

Fail loud - the property that is not optional either way. Whichever direction a control fails in, the failure is reported somewhere that stops work. A control that fails open and silently is the dangerous combination, because its presence is the reason nobody looks. See docs/PROCESS.md.

Shapes of defect

False green - a check that passes for a reason unrelated to the thing it names. Not a bug in the code under test, a bug in the test: it would pass whether the code were right or wrong, so it reports health it never measured. grep -q 'get_logger' health.py satisfied by an unused import. A deploy step called "confirm the schema is at head" passing because head=none and current=none really are equal. A self-test asserting max(taken) + 1 == 10, which is arithmetic and true of every implementation including the one it was written to reject. The standard way to find one is a mutation; the standard way to ship one is to write the check before the thing it checks exists.

Declared but unwired - configuration or metadata that exists, parses correctly, and reaches nothing. rate_limit_auth read by nobody while routes hardcoded the value. A config field looks identical whether or not anything reads it.

Green on unverified state - a check reporting success over something it did not examine. saas check passing on a project that could not import.

Two conventions for one thing - parallel work diverging on a name and nobody choosing. Four instances, including a registry reached by eight names for four operations under a false "for compatibility" comment.

Seam - the interface where two pieces of work meet. Two agents on one seam is how this repo got two registries. Fan out on files, never on a seam.

Carve-out - a deliberate exception to a rule, stated in the rule itself. A CLAUDE.md carrying no frontmatter is a carve-out from the schema.

Gap marker - GAP(TICKET-ID): one sentence in a comment, at the place a reader meets the problem rather than only in backlog/. Listed by scripts/list_gaps.py, which fails if the ticket id does not resolve. A founder hits a gap while running a command, not while reading a backlog item, so the marker puts the explanation where the surprise happens.

Dispatch ledger - DISPATCH.md. What agent work is in flight, on which branch, cut from what, holding which files, and told not to do what - plus the queue behind it. It exists because on 2026-09-12 the coordinator stopped while it still had work it wanted to do, and on 2026-09-13 it found a three-deep branch stack by accident: both times the state of the work lived in one session's context and nowhere else. A file makes the session disposable. scripts/gen_roadmap_status.py reads its Files column, so the roadmap stops offering work somebody already holds.

It is the name the owner asked about, and it turns out to name two things, so the pair is worth stating together:

The ledger - docs/ledger.jsonl, written by scripts/ledger.py. One append-only line per event that has already happened: a request in his own words, with whether it is open, partly or answered, or a dispatch with its subject, the tickets it touched, how many agents, how many tokens and what came of it.

The distinction from DISPATCH.md is tense, and it is the whole reason there are two files. DISPATCH.md is present - what is in flight right now - so it is rewritten whenever that changes and it is stale within a day by design. The ledger is past, so no line in it is ever edited, which is what makes it safe for five agents to append to at once and what makes git log on it a history.

Requests are in it because the owner asked for them on 2026-09-17: "if we're building a product, we need to keep a very good log of, like, what did we ask, what did we do, like, in the pretty high level". Before that the only record of what he asked was a chat transcript, and recovering 70 requests from it took an agent a whole session - reviews/owner-request-trace.md, which is that pass and which nothing updates. A line written when the request arrives costs seconds; an archaeologist costs a session and loses his exact words on the way.

scripts/gen_state.py reads it into docs/state.json, so anything that wants to show what is open, what was answered and what it cost reads one document.

Watchdog - a timer that resets on every sign of life and fires if nothing arrives before it expires, on the assumption the thing it watches is hung. Ordinary computing, not ours. The agent harness runs a stream watchdog over each subagent's output: 600 seconds with no progress and it declares the agent stalled and kills it. That happened once on 2026-09-13 and cost nothing, because the agent had committed nine times and left a clean tree - which is why CLAUDE.md says to commit the skeleton before building on it.

Capability URL

Capability URL - a URL that is itself the credential. Holding it is the authorisation: there is nothing to sign in to, and the secret is a path segment or a query parameter.

The owner asked on 2026-09-14 whether this is an anti-pattern and whether it is a common thing to do. It is common, it has a name, and it is not an anti-pattern. The W3C TAG published guidance on it in 2014 rather than a warning against it, and it is what these all are:

It exists because it solves a real problem: the person who needs the link often cannot sign in, by definition. Somebody resetting a password has lost the only credential they had. That is the case this template's own components/auth/services/email_token.py makes, and it is right.

The three hazards, which are the reason it has guidance

A URL is not treated as a secret by anything that handles URLs.

Where it went wrong here, which is the useful half

Ati's Toy Shop used one as a child's standing identity rather than for one short-lived action. All three hazards then arrived, two of them in one night:

So the pattern was not the mistake. Using it outside the range where it is correct was, and the range is: short-lived, single-purpose, and for somebody who cannot sign in yet. A durable identity is none of those three. PROD-040 replaces it with a password the shopkeeper sets.

See also declared but unwired for the other defect class this repository names, and docs/LESSONS.md lesson 22.

Conventions inherited from elsewhere

/healthz and /readyz, the trailing z - a Google-ism, inherited through Kubernetes. Google's internal services exposed /statusz, /varz and /healthz, and the z existed to namespace operational endpoints away from product URLs on the reasoning that no real page would ever be called that. Kubernetes adopted /healthz and it became the de facto probe convention, so an operator recognises it on sight.

It is not about infrastructure-as-code, which is a reasonable guess and not the history. The two endpoints differ in what they promise: /healthz answers without a database and says the process is up, /readyz reaches the database and says the process can serve. A load balancer wants the second.

Renaming them now would touch the contracts, the tests, the gates and the OpenTofu health checks, so the cost is real rather than cosmetic.

Words used loosely, and what they meant

Prong - one branch of a multi-part argument. Borrowed from a sub-agent's report and then repeated without defining it, which is the thing this glossary exists to stop. Ritchie's review argued the adapter registry was wrong on two counts, types and size: the typing work answered the first and left the second untouched. Say "the size half of the argument" instead.

Wince - to flinch. Used to describe a habit of prefacing corrections with self-criticism. The corrections are useful, the flinching is not. Plain English would have been "apologising".

Blind spot - used for a review system that cannot see absence. Accurate but vague. The precise statement is lesson 9: a correct check over an input that omits the subject.

Fan out - launch several agents in parallel. Ordinary usage, kept, because the rule attached to it matters: fan out on files, not on features.

Accrete - to accumulate by gradual addition, with no single decision responsible. Used for what happens to a template that adds a capability because it might be wanted: components/core/ accreted models, schemas, routes, config, middleware, logging and adapters, and no one commit did it. The plain word is "pile up", and the reason it needs naming at all is that accretion has no author to argue with.

Theatre - process performed for its appearance rather than its effect. Seven pipeline stages for a typo is theatre, and theatre gets skipped, and then the pipeline protects nothing.