Files
hq/00-META/how-we-build.md
T
jschoubben 333356cff3 Order the records the way the system is learned
Jochen asked whether the order made sense. It did not -- it followed when
things happened to be decided, which after consolidation is fictional anyway
since record 5 alone folds decisions taken across a week.

Concretely wrong before: the domain statement sat at 8, after five engineering
rules; the constitution was scattered across 5, 12 and 17; the tiers landed at
15, 16, 21 and 22 with process records in between.

Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what
runs on them and how it gets there (9-10), how it is built (11-16), how it is
checked (17-18), how we work (19-23).

Two things made this safe rather than free. It is a permutation, not a
compaction, so the renames go through temporary names -- otherwise two files
want one slot and one is lost. And the reference rewrite is a single
simultaneous pass, because almost every number moved into a slot another number
was vacating; replacing one at a time would have cascaded and pointed things at
the wrong record while still resolving.

Verified: 284 [ADR NNNN](path) links across the repository, all with matching
text and target.

The ordering principle is now stated in 19 rather than left implicit -- the
repository already said "the numbering is the flow" about its folders, and
there was no reason for the records to be the exception.
2026-08-28 23:30:42 +02:00

287 lines
15 KiB
Markdown

---
status: canonical
updated: 2026-08-23
derives: knowledge-base constitution page
decisions:
- 02-DECISIONS/0020-the-mesh-is-governed-by-a-constitution.md
---
# How we build
The rules that hold across the mesh. Short, and each one earned.
**This document is the source of the mesh constitution.** The governed page the mesh injects
into design sessions is *derived* from it, section for section, and carries the rules without
the reasoning. Never edit that page directly — an edit there survives until the next sync and
then vanishes, taking its reasoning with it. The sync is playbook
[`process/05-constitution-sync.md`](process/05-constitution-sync.md), and it is how the claim
"HQ is the source" is checked.
Section numbers are stable. The orchestrator and the review fragments cite them.
---
## 1. Purpose
These are the principles and guardrails every piece of work in the mesh is checked against —
design sessions, analysis gates, reviews, and agents acting on their own. A rule stated here is
non-negotiable unless amended per §6.
They exist because each was violated first. Where a rule reads as arbitrary, that is a sign the
incident behind it is not written down, and the fix is to write it down, not to relax the rule.
---
## 2. Non-negotiables
| Rule | What it means |
|---|---|
| **Never write to a production database directly** | No insert, update, delete or schema statement executed against production by hand. Schema changes go through numbered migrations; data changes go through application code or the module's own capabilities. Raw statements skip every side effect the proper path has — events, audit, cache invalidation, fan-out. |
| **Every schema change is a migration** | Numbered, in the module's own language, compiled with it. Both a baseline for a fresh installation *and* an incremental migration for installations that already exist. If code references a column, the migration creating it must exist. [ADR 0013](../02-DECISIONS/0013-schema-changes-are-numbered-migrations.md) |
| **Never bypass the pipeline** | No manual database edit, no manual restart as a workaround. Fix the cause and deploy. A workaround that works is a workaround that is never removed, and the next person cannot tell the node from its declaration. |
| **Never create a symlink** | A hand-made link caused production data loss through container volume resolution, and the judgement needed to make a safe exception is exactly the judgement unavailable at the moment it matters. **The mesh creates none at all** ([ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md), which supersedes [ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md)). The links the installer still reconciles are a migration, not a permission. |
| **Never push directly to the main branch** | Branch, push, review, merge. Every merge is a human checkpoint, without exception — **including in this repository**. A documentation repository is not a lower tier of care; a decision record lands the same way a service does. |
| **One change per pull request, and never merge unapproved work** | Unrelated improvements bundled together cannot be reviewed or reverted separately. And the checkpoint is **a person deciding, not a person clicking** — work may be merged by whoever wrote it once a human has explicitly approved *that merge*, and never on a standing permission, an instruction to do the work, silence, or the author's own judgement that it is ready. [ADR 0023](../02-DECISIONS/0023-approval-is-the-checkpoint.md) |
| **Never open a pull request unprompted** | A permissions list saying it is allowed is not a request. |
| **A failed step fails the job** | A sequence that continues past a failure does the next thing in the wrong place. Gate each step on the last. [ADR 0010](../02-DECISIONS/0010-delivery.md), and §5. |
### A failed step must stop the steps after it — how it was earned
A worktree creation failed because the branch name collided with an existing namespace. The
change into that worktree failed too. The copy, the staging and the commit that followed all
ran in the shared checkout and committed to a local main. The error was printed and scrolled
past.
This is the same shape as the faults the mesh's whole refactor exists to remove: a step
reported failure, nothing stopped, and the damage happened somewhere nobody was looking.
---
## 3. Module and infrastructure rules
### Manifests
- **Never bump a version by hand.** The builder owns versioning. A version change in a diff is
a defect; revert it.
- **Features are detected, not declared.** The installer discovers what a module carries from
what its directory contains. A declared list and the directory it describes drift, and the
directory is the one that is true.
- **Every runtime variable is declared.** A variable the module reads and the manifest does not
declare is invisible to the mesh: it will not be generated, injected, or audited.
- **Provisioned credentials arrive through declared requirements**, never hardcoded in code,
compose files or scripts. [ADR 0009](../02-DECISIONS/0009-modules-and-the-graph.md)
- **Never install a package by hand.** A package is declared in the manifest and arrives the
way every other package does. A hand-installed package is invisible to the mesh: it is not
declared, not reproduced on the next node, and not present after a rebuild — and the node
works until it doesn't.
### Placement
- **Core modules belong to the monorepo** — the runtime, delivery, provisioning, configuration,
knowledge, and the shared infrastructure the mesh provisions against.
- **Every standalone application gets its own repository**, with a manifest at its root,
registered as a build source. Creating an application directory in the monorepo is a
convention violation and reviewers reject it.
[ADR 0015](../02-DECISIONS/0015-applications-live-in-their-own-repository.md)
### Migrations
- Numbered, idempotent, and safe to re-run. Guard every statement.
- The initial migration is **frozen** once it has run anywhere. Change is a new number.
- Numbers are unique. A duplicate prefix is a defect, not a style question.
### Managed files
**A file edited on a node is a bug with a delay on it.** Everything under the mesh's managed
surface is regenerated from the mesh database; a local edit survives one synchronisation and is
then silently overwritten, bringing back whatever it fixed. Use the mesh operation that owns
the value. If unsure whether a file is managed, ask the tooling — the answer is not visible
from the file. [ADR 0011](../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)
---
## 4. Naming and boundaries
### Name a context after its aggregate, not after a metaphor
A context named `agents` owns **Agent**. A *brain* — memory, thoughts, cognition — is something
an agent **has**: a concept inside the aggregate, not a module.
The cost of getting this wrong is visible today. An anatomy name points at the node runtime, so
the most evocative word in the system names infrastructure; another describes itself as "mesh
messaging" in its manifest while the anatomy documentation calls it the interactive runtime —
and it runs on no node at all.
Anatomy makes attractive names and poor boundaries. Name the thing the domain calls it.
### Things that change together share an authority, not a package
When several modules always change together under one intent, name the **context** that decides
for them. Do not merge them into one module: they are delivered to different nodes, and a module
that must be assigned where half of it is unwanted is not a boundary either.
Coherence is a context. Delivery is a module. Relationships are edges, not folders.
[ADR 0009](../02-DECISIONS/0009-modules-and-the-graph.md)
### Contexts integrate through the record, never through a shared schema
Publish to the stream; do not join across a boundary. Today several domains share one
forty-five-table schema, which is why work belonging to one context keeps having to be
implemented in another.
---
## 5. Evidence and verification
### Ubiquitous language is checked, not assumed
**If a document states a rule about the mesh, it says how the rule is verified.** This
repository has a documented requirement that every capability-exposing module declare the core
runtime as a dependency. Zero modules do.
An unenforced rule is indistinguishable from a wrong one, and costs more, because people
believe it.
### Behavioural criteria require runtime evidence
A criterion of the form *"the script runs"*, *"the endpoint answers"*, or *"the migration
applied"* is satisfied only when the change has actually been exercised: a real run, a real
request, a pipeline log, a live query showing the expected result.
**Marking a runtime criterion verified from a diff is itself a violation.** A reviewer who
finds one names the evidence required and returns the work.
The reason is the mesh's most consistent failure shape: a green result proves transport, not
effect. Absence reads as success unless something looked.
### A test defends a decision
The rule above applies to prose. It applies to **decisions** too: a decision record states
something that must be true, and a test asserts it. A decision with no test is one that will
quietly stop being true, and nobody will learn that from a document.
- Structure and logic — what is accepted, what is refused, how a value is derived — is tested
**first**, because the behaviour is knowable before the code.
- Behaviour against a real system is tested **alongside**, because it is discovered rather than
known.
- **Mocking the boundary is forbidden.** A test that fakes the system under integration asserts
that the fake behaves as expected.
- **The gate is blocking.** Green is the definition of done; a change that has not run its tests
is not finished, whatever the diff looks like.
Not test-driven development as a blanket rule — a test written first against undiscovered
behaviour asserts a guess. The obligation is that every decision has a defender.
### A report is read from the system, never from what asked for it
The same rule as the two above, pointed at reporting rather than at verification. **Anything
that describes the state of the mesh — a status view, an inventory, a diagram, a health
check — is assembled from the running system.** Assembling it from the intended state produces
a report that always agrees with itself and can never disagree with reality, which is not a
report.
Where the system does not natively hold a fact the report needs, **the thing that applied the
fact records it** — and:
> **A record of behaviour is written after the behaviour works, never when the resource is
> created.**
Written up front it restates the request in a new place and inherits none of the authority of
having happened. A failed run leaves its wreckage standing, and a report of that wreckage must
not describe what the wreckage was supposed to be.
This is the production form of the mesh's most expensive fault: a firewall key declared in five
manifests and read by no code
([04-ISSUES/003](../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md)). The
declaration was never wrong. Nothing ever asked the system.
### Search the record before forming a hypothesis
The first action on any error message, failing service or unexpected behaviour is to search the
operational memory for the literal error text — before a hypothesis, not after one fails. The
knowledge base is indexed on symptoms.
This fires hardest on *familiar* ground, where a confident trail feels like progress. Two
entries were each rediscovered from scratch over several hours in a single session because the
search was skipped. Both were already written down.
---
## 6. Amendment
This document is governed. It does not change by commit message or unilateral decision.
1. **Propose** — a change stating what rule is changing, why the current wording is
inadequate, and what reviewed it.
2. **Review** — sign-off by reviewers who are not the proposer.
3. **Record** — the change is a decision and gets a record in [`02-DECISIONS/`](../02-DECISIONS/), because a
rule the mesh enforces is architecturally significant.
4. **Sync** — playbook [`process/05-constitution-sync.md`](process/05-constitution-sync.md)
publishes the derived page. An unsynced rule is a rule the mesh does not enforce, whatever
this document says.
No drive-by edits. Every change traces to a recorded decision.
**Review means a person who is not the proposer.** The enforced page carried a stricter bar —
a design meeting with at least two node operators — which has never been met and cannot be, as
there is one operator. A rule that cannot be satisfied is not a high standard; it is a rule
everything silently violates. Recorded here as resolved in favour of what is achievable, and
what has in fact been practised ([ADR 0022](../02-DECISIONS/0022-the-constitution-absorbs-what-is-enforced.md)).
---
## 7. Overrides
A team or product may define additional constraints that **narrow or tighten** these rules.
They may never relax them.
An override says which rule it tightens, or which gap it fills, and follows the same amendment
process. Absence of an override means these rules apply unmodified.
---
## 8. Code quality
*Absorbed 2026-08-26 from the enforced page, which carried these rules while this document did
not — [ADR 0022](../02-DECISIONS/0022-the-constitution-absorbs-what-is-enforced.md).*
**These rules are recorded because they are enforced, not because this repository earned them.**
Every other rule here states the incident or measurement behind it. These state nothing,
because nothing is written down. That is a gap, not a style choice, and it is marked rather
than dressed up: a rule whose reasoning nobody recorded is one nobody can argue with correctly,
which is the condition §2 exists to avoid.
### Structure
- **One reason to change** per module, class or function.
- **Extend by composition**, not by editing what already works.
- **Depend on abstractions**, and inject the concrete thing rather than reaching for it.
- **Small, focused interfaces** over one large one.
- A substitute for a type must not break the behaviour its users rely on.
### Layering
Data access, business logic and the interface layer are separate.
- No queries in route handlers, tool definitions or daemon loops — those belong in repositories.
- No orchestration or validation in repositories — that belongs in services.
- Shared logic belongs in the SDK. Duplicating it into a surface is how two answers to one
question start to exist.
### Types
*Scope: the mesh's services and surfaces. Tier 0 is a statically linked binary that must depend
on nothing installed first, and is written in Go —
[ADR 0005](../02-DECISIONS/0005-the-node-host.md).*
- TypeScript throughout; no new untyped JavaScript.
- Strict, with no implicit `any` and no unchecked index access.
- Public functions state their return type. Prefer `unknown` with a guard over `any`. Errors
are typed, never thrown as strings.
### Restraint
- **Do not over-abstract.** Three similar lines beat a premature abstraction.
- **Build what is needed now.** A hypothetical future is not a requirement.
- Names reveal intent, functions fit on a screen, and boundaries fail fast.