Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29ac1c7e52 | ||
|
|
9ffa60924e | ||
|
|
c6d36b2f32 | ||
|
|
f6311a66bf | ||
|
|
c04c0ebb76 | ||
|
|
491de157da | ||
|
|
bea864af8b | ||
|
|
7ed83f4965 | ||
|
|
71759e83f8 | ||
|
|
3174d5aa6e | ||
|
|
e7c8ce2740 | ||
|
|
5816ca669f | ||
|
|
afe83b9124 | ||
|
|
f269e83f5d | ||
|
|
6c607504de | ||
|
|
20c857443c | ||
|
|
7735cfd107 | ||
|
|
7e1c0f4c15 | ||
|
|
19a8e92801 | ||
|
|
59c6954b0c | ||
|
|
95bf5cbf20 | ||
|
|
f0bcc75fcf | ||
|
|
550487fc90 | ||
|
|
022392a22b | ||
|
|
643ec2b1bf | ||
|
|
ee862d8e22 | ||
|
|
292fae8863 | ||
|
|
f9139feb30 | ||
|
|
d10965a341 | ||
|
|
f7915f7f29 | ||
|
|
86e66dd51f | ||
|
|
a353ed21c0 | ||
|
|
4a090df25a | ||
|
|
ea7c831196 | ||
|
|
d81c23b65a | ||
|
|
0f8d778ec2 | ||
|
|
cd9336d05a | ||
|
|
5c81f1170c | ||
|
|
ba127e29b7 | ||
|
|
d0a2950bb0 | ||
|
|
8e76098412 | ||
|
|
42ecaa7ac4 | ||
|
|
cec72d1dc9 | ||
|
|
b98b93efc8 |
+1
-1
@@ -15,7 +15,7 @@ works — plus the engineering practice that holds across everything Novox build
|
||||
|
||||
## Rules
|
||||
|
||||
- Markdown only.
|
||||
- Markdown only, but for the checks and one drawn overview of the playbooks (`process/playbooks.html`).
|
||||
- **Stable by nature.** Changes here reflect a genuine shift in intent, not iteration. The one
|
||||
exception is `how-we-build.md`, which changes whenever a rule is earned — and only through
|
||||
its amendment process.
|
||||
|
||||
+80
-33
@@ -227,7 +227,7 @@ every other domain has something to run on. **Recorded by** the controller and t
|
||||
the two and raises `mirror-drifted` on any difference
|
||||
(ADR 0289, ADR 0356). Builds, rollouts and assignments are no mirrors: they are
|
||||
the chain, kept on the bus as their only source (ADR 0408). Shortened to *mirror* where the
|
||||
text is about the controller's records; the builder's *mirrored* `upstream` image (Change and delivery) is
|
||||
text is about the controller's records; the builder's *mirrored* `upstream` image (Delivery) is
|
||||
a different thing, a copy of an outside image in the artifact store.
|
||||
|
||||
## Placement — what runs on which node
|
||||
@@ -343,7 +343,7 @@ and vault (Identity and access).
|
||||
|
||||
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents
|
||||
and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
|
||||
**Upstream of** Provisioning, Change and delivery, and Operator and conversation.
|
||||
**Upstream of** Provisioning, Delivery, and Operator and conversation.
|
||||
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414.
|
||||
**Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation).
|
||||
|
||||
@@ -397,19 +397,29 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
|
||||
agent can run as that account: the control-node, and a node whose agents run under an account of their own
|
||||
(ADR 0272).
|
||||
|
||||
## Change and delivery — a commit on its way to the nodes
|
||||
## Delivery — a commit on its way to the nodes
|
||||
|
||||
**Purpose.** To take one commit from its pull request to every node that should run it, and to put it
|
||||
back when it fails. **Recorded by** the `mesh-delivery` module (deliveries) and the controller (the
|
||||
planner, the rollout). **Downstream of** Module, Core, Health and repair and Data.
|
||||
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408.
|
||||
back when it fails. The domain is continuous integration and delivery, named **delivery**, and its parts live in
|
||||
the repository `mesh-delivery` (ADR 0429 §2). **Recorded by** the `mesh-delivery` module (deliveries) and the
|
||||
controller (the planner, the rollout) until release 6 of ADR 0429; from each release on, by the parts it builds. **Downstream of** Module, Core, Health and repair and Data.
|
||||
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408, 0428, 0429, 0444–0447.
|
||||
**Uses:** module, node, declaration and send (Placement), health (Health and repair), person.
|
||||
|
||||
- **merge check** — the two statuses a pull request carries before it may merge: the **merge gate**
|
||||
(`mesh/merge-gate`, the build seat's composed check of the modules the change touches) and the
|
||||
**repository check** (`mesh/repo-check`, the repository's own `merge-check.sh`)
|
||||
(ADR 0238). Both judge the merge of the pull request's head into a named **trunk commit**, made in the
|
||||
build seat's own clone and never pushed (ADR 0323).
|
||||
- **merge check** — what a pull request carries before it may merge, judging the merge of its head into a
|
||||
named **trunk commit**, made in the builder's own clone and never pushed (ADR 0323). Until release 4 of
|
||||
ADR 0429 is delivered, two statuses: the **merge gate** (`mesh/merge-gate`, the build seat's composed check of
|
||||
the modules the change touches) and the **repository check** (`mesh/repo-check`, the repository's own
|
||||
`merge-check.sh`) (ADR 0238). From that release, one status, `mesh/check`, written by the checker: the
|
||||
change's tests, run by the language's build module, and the **composition check**; for a delivery group, one
|
||||
verdict on every member's head (ADR 0445, ADR 0447).
|
||||
- **composition check** — whether the machines a change touches still compose with it, run by the assigner
|
||||
from the modules the indexer and the builder hold, with the composition the controller sends with
|
||||
(ADR 0446). It replaces the merge gate from release 4 of ADR 0429; the old word is struck once that release
|
||||
is delivered and every design that still says it is reworded (issue 1303).
|
||||
- **kept verdict** — a package's passing verdict at one test level, kept by the checker under the digest of
|
||||
everything its tests read, written once and read-only; a check takes it instead of running an unchanged
|
||||
package (ADR 0444).
|
||||
- **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when
|
||||
the build seat cloned it. The verdict holds for that commit only, and goes out of date when the trunk moves
|
||||
(ADR 0323).
|
||||
@@ -473,24 +483,58 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
|
||||
- **delivery plan** — what a delivery does to the mesh: its **build plan** (the modules it moves and
|
||||
their dependents, in tiers), its **deploy plan** (per node, what it receives and what waits for a
|
||||
person) and its verdict (the composed nodes, the replays). Computed by the controller's planner from a
|
||||
diffset; the controller's verb `delivery-plan` shows it. *Deploy* is used in this one place.
|
||||
diffset; the controller's verb `delivery-plan` shows it. *Deploy* is used in this one place. Deleted at
|
||||
release 6 of ADR 0429 (§8): the word stays in use until that release is delivered, and then retires.
|
||||
*Not:* ~~change plan~~, ~~release plan~~
|
||||
- **merge window** — the rolling wait in which the controller collects merges before it plans them: each
|
||||
merge restarts it, and it closes when none arrived for its length (90 seconds by default) or at the latest
|
||||
10 minutes after its first merge (ADR 0276). Kept as two keys with a time to live on the bus; the first to
|
||||
expire closes the batch (ADR 0407).
|
||||
expire closes the batch (ADR 0407). Goes at release 6 of ADR 0429, when nothing collects merges (§12).
|
||||
- **batch** — the merges one merge window collected, planned once as one rollout. For a repository merged more
|
||||
than once in it, the rollout takes the latest commit, and the earlier merges are answered by it. Not *group*,
|
||||
which is a delivery group.
|
||||
- **mesh-delivery** — the module that owns deliveries and delivery groups, holding the mesh-scoped seat
|
||||
of the same name. It records and decides; the controller sends, judges and rolls back when it is asked.
|
||||
which is a delivery group. Goes at release 6 of ADR 0429 (§11, §12).
|
||||
- **mesh-delivery** — the repository of the delivery domain, holding its parts as modules prefixed
|
||||
`mesh-delivery-` (ADR 0429 §2), made by the forge module from its config (ADR 0428). Until release 3 of
|
||||
ADR 0429 also the module in the catalogue that owns deliveries and delivery groups, holding the mesh-scoped
|
||||
seat of the same name: it records and decides, and the controller sends, judges and rolls back when it is
|
||||
asked. Release 3 splits that module into the parts, and then the name means the repository alone.
|
||||
- **part** — one independent process of the delivery domain with its own bus credential, which watches keys
|
||||
and writes keys and never calls another part (ADR 0429 §1). Each key has one writer; the operator's
|
||||
credential may also write a want, the one manual escape. The parts: the **forge bridge** (forge events in,
|
||||
the only caller of the forge), the **indexer** (fingerprints, the candidates of a group, the modules of the
|
||||
trunk), the **builder** (claims and builds one fingerprint once), the **checker** (the merge check on built
|
||||
artifacts), the **gate** (merges a group and moves the source revision; *gate part* where it could be read
|
||||
as another gate, see [Homonyms](#homonyms)), the **teller** (the operator's phone, and the only writer of an
|
||||
ask's answer), the **assigner** (writes each node's wants by its six rules), the **node agent** (the
|
||||
node-engine's own process, applying its wants), the **watchman** (presence and pulses) and the **status
|
||||
view** (`mesh-delivery-cli` and a web view, writing only its answers to asks). The reviewer is the review
|
||||
agent's session, not a part. Built over releases 1 to 6 of ADR 0429.
|
||||
- **fingerprint** — what identifies a module's build by its content: a hash over its source files, its
|
||||
manifest and the fingerprints of its build-time inputs, so equal content gives an equal fingerprint whatever
|
||||
the commit (ADR 0429 §5). It is today's source fingerprint with a base entering by its module's
|
||||
fingerprint, the builder by its own module's fingerprint, and a build resolving packages by its lock. From
|
||||
release 4 a build's key, so one source is never built twice.
|
||||
- **source revision** — one revision of the key `source.main`, written only by the gate: every repository's
|
||||
merged head and the group that moved it. A group reaches the mesh as one source revision (ADR 0429 §11); a
|
||||
source revision is **delivered** when every want equals its module's fingerprint and every have equals its
|
||||
want and is healthy, derived from the keys and recorded by no part (§1).
|
||||
- **canary** — the first node of a module's nodes, ordered by ring then name: a new fingerprint reaches it
|
||||
first and the module's other nodes only once it runs healthy there (ADR 0429 §6). Replaces sending to every
|
||||
machine at once from release 6 (§15).
|
||||
- **want** and **have** — a want (`want.<node>.<module>`) is the fingerprint a node should run, written by the
|
||||
assigner; a have (`have.<node>.<module>`) is what the node runs, its phase and health, written by that node's
|
||||
agent (ADR 0429 §4, §6). A node's facts that no module owns are its node key. They replace assignments and
|
||||
rollouts at release 6 (§17); haves are written from release 1.
|
||||
- **pulse** — a key the watchman writes for each key whose next move a machine owes, with a time to live; its
|
||||
expiry is a stall, raised as a condition, so no part needs a timer (ADR 0429 §1, research 063). A person's
|
||||
wait carries none.
|
||||
- **rollout** — the controller's sending of one batch's builds across nodes, at most one trunk commit per
|
||||
repository, tier by tier, to every node that runs each module at once (ADR 0276, ADR 0360). Rollouts that share a
|
||||
repository, a module or a graph edge run one after another; others run side by side (ADR 0407). It names every
|
||||
merge it answers, and its record is one key of the bus's rollouts bucket, a link of the chain (ADR 0408). A primitive the delivering stage
|
||||
asks for, not an object anyone manages. A **tier** is one step of a rollout: a module, then the modules
|
||||
built against it. The controller's verb `plans` lists the rollouts. A module is rolled out by its `roll`
|
||||
policy.
|
||||
policy. Rollouts and tiers go at release 6 of ADR 0429 (§8, §15), replaced by wants and haves (§17).
|
||||
*Not:* ~~walk~~ (hq)
|
||||
- **first-node gate** — the judgement on a delivery's first node: healthy three times, no witness put it
|
||||
back, no condition raised since the send (ADR 0236 §2). Not *the gate* bare (see [Homonyms](#homonyms)).
|
||||
@@ -520,7 +564,7 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
|
||||
|
||||
**Purpose.** To notice what is wrong with the mesh, say it once, repair what may be repaired unattended,
|
||||
and record what was done by hand. **Recorded by** the controller's condition store.
|
||||
**Upstream of** Change and delivery (the first-node gate) and Operator and conversation (a condition
|
||||
**Upstream of** Delivery (the first-node gate) and Operator and conversation (a condition
|
||||
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
|
||||
**Uses:** node, module, node-engine (Core).
|
||||
|
||||
@@ -562,7 +606,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
|
||||
|
||||
**Purpose.** To know every item of data a module holds, how precious it is, and to keep it recoverable.
|
||||
**Recorded by** the manifests' `data` declarations and every node's `node-backup` seat.
|
||||
**Upstream of** Change and delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235.
|
||||
**Upstream of** Delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235.
|
||||
**Uses:** module, node, provider and consumer (Provisioning), person.
|
||||
|
||||
- **data class** — how precious an item of data is, ranked by the operator: `irreplaceable`,
|
||||
@@ -609,10 +653,10 @@ it should not. **Recorded by** the controller. **Upstream of** Provisioning and
|
||||
|
||||
**Purpose.** To let the mesh and its operator talk: tell, ask, answer, and act only on an answer it can
|
||||
trust. **Recorded by** the router (asks, warrants and the operator's identities).
|
||||
**Downstream of** Health and repair, Change and delivery, and Identity and access. An authorising
|
||||
**Downstream of** Health and repair, Delivery, and Identity and access. An authorising
|
||||
answer acts in whichever domain asked, through that domain's own verb; the conversation owns the
|
||||
asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 0384, 0385, 0390.
|
||||
**Uses:** operator, person, node, module, seat, tool (Module), proof and identity (Identity and access), release (Change and delivery).
|
||||
**Uses:** operator, person, node, module, seat, tool (Module), proof and identity (Identity and access), release (Delivery).
|
||||
|
||||
- **mesh MCP server** — the endpoint on a node's loopback through which every agent and person on that
|
||||
node reaches the mesh: the MCP server named `mesh`, with its five tools `mesh_overview`,
|
||||
@@ -804,22 +848,24 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| Word | Sense | Say | Domain |
|
||||
|---|---|---|---|
|
||||
| plan | what the controller would send one node | that node's **declaration** (verb `plan`) | Placement |
|
||||
| | what a delivery would do to the mesh | **delivery plan** | Change and delivery |
|
||||
| | the sending of one batch of commits across nodes | **rollout** (verb `plans`) | Change and delivery |
|
||||
| | what a delivery would do to the mesh | **delivery plan** | Delivery |
|
||||
| | the sending of one batch of commits across nodes | **rollout** (verb `plans`) | Delivery |
|
||||
| | a step a person starts, like the bus's | **planned step** | Core |
|
||||
| membership | the subjects a bus account is issued | **membership** | Provisioning |
|
||||
| | an account in a group of the machine | **account group** | Placement |
|
||||
| push | the controller giving nodes their declarations | **send** (verb `push`) | Placement |
|
||||
| | a git push | **git push** | The record |
|
||||
| | a phone notification | **push notification** | Operator and conversation |
|
||||
| release | a person letting a held delivery go on | **release** | Change and delivery |
|
||||
| release | a person letting a held delivery go on | **release** | Delivery |
|
||||
| | one milestone built, checked and delivered as one (playbook 07; releases 1 to 6 of ADR 0429) | **release** of a milestone | The record |
|
||||
| | taking a consumer off a licence | **unbind** (verb `release`, to rename) | Identity and access |
|
||||
| | a copy of a work an engine can fetch | **media release** | Media |
|
||||
| gate | the judgement on a delivery's first node | **first-node gate** | Change and delivery |
|
||||
| | the pull request status | **merge gate** | Change and delivery |
|
||||
| gate | the judgement on a delivery's first node | **first-node gate** | Delivery |
|
||||
| | the pull request status, until release 4 of ADR 0429 | **merge gate**; from release 4, **composition check** | Delivery |
|
||||
| | the part that merges a group and moves the source revision | **gate part** (`mesh-delivery-gate`) | Delivery |
|
||||
| | a failed step stopping its module's later steps (ADR 0136) | *a failed step holds its module* | Placement |
|
||||
| tier | a level of the mesh | **layer** | Core |
|
||||
| | a step of a rollout | **tier** | Change and delivery |
|
||||
| | a step of a rollout | **tier** | Delivery |
|
||||
| | how much proof an ask needs | **assurance level** | Identity and access |
|
||||
| harness | the part of `mesh-sdk` that serves a tools bundle | **SDK harness** | Module |
|
||||
| | the program that runs an agent's sessions | **agent harness** | Operator and conversation |
|
||||
@@ -827,9 +873,9 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| role | what a seat names, at a scope | **seat**; the bare word in older designs is tracked by issue 872 | Module |
|
||||
| | what an identity may call | **role** | Identity and access |
|
||||
| ask | a request for the operator's input | **ask** | Operator and conversation |
|
||||
| | an entry in the build queue | **build request** | Change and delivery |
|
||||
| | an entry in the build queue | **build request** | Delivery |
|
||||
| store | the one database server | **store** | Core |
|
||||
| | the OCI registry | **artifact store** | Change and delivery |
|
||||
| | the OCI registry | **artifact store** | Delivery |
|
||||
| | a node's own copy of its last declaration | **last declaration** | Placement |
|
||||
| record | a numbered decision | **decision record** | The record |
|
||||
| | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation |
|
||||
@@ -838,19 +884,20 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| | the operator's terminal command, `mesh-cli` | **the `nox` command** | Operator and conversation |
|
||||
| knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record |
|
||||
| | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — |
|
||||
| check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery |
|
||||
| | a delivery group's verdict | **composed check** | Change and delivery |
|
||||
| check | a pull request's two statuses, one from release 4 of ADR 0429 | **merge check**, **merge gate**, **repository check**; from release 4, `mesh/check` | Delivery |
|
||||
| | whether the machines a change touches still compose, the assigner's | **composition check** | Delivery |
|
||||
| | a delivery group's verdict | **composed check** | Delivery |
|
||||
| | the controller's own examination | **self-check** | Health and repair |
|
||||
| | a file in `00-META/checks/` | **hq check** | The record |
|
||||
| agent | a coding agent | **agent** | Operator and conversation |
|
||||
| | the build seat's holder | **builder** | Change and delivery |
|
||||
| | the build seat's holder; from release 4 of ADR 0429 the part `mesh-delivery-builder` | **builder** | Delivery |
|
||||
| the user | an account of a wrapped program, or of the bus server | **its account**, **bus account** | Identity and access |
|
||||
| | a human | **operator** or **person** | words every domain uses |
|
||||
| firewall | what the mesh enforces | **packet filter** | Connectivity |
|
||||
| | what was found on a machine | **found firewall** | Connectivity |
|
||||
| ticket | an answer's one-time token (ADR 0258) | **answer ticket** | Operator and conversation |
|
||||
| | a question someone must answer | **issue** | The record |
|
||||
| deploy | the per-node part of a delivery plan | **deploy plan** | Change and delivery |
|
||||
| deploy | the per-node part of a delivery plan | **deploy plan** | Delivery |
|
||||
| | a wrapped program's own deploy | its own word | — |
|
||||
| managed | a placed file of which only named keys or a marked block are the mesh's | **managed** (`owner-at-runtime`) | Provisioning |
|
||||
| | a file the mesh writes, such as the agent's instruction file or `managed_settings` | the file's own name, or *a file the mesh places* | — |
|
||||
|
||||
@@ -48,6 +48,9 @@ the expensive half.
|
||||
|
||||
## The playbooks
|
||||
|
||||
A drawn overview of them, the cycle, a feature's steps, the review loop and an issue's life, is
|
||||
[`playbooks.html`](playbooks.html): a picture of these playbooks, never their authority.
|
||||
|
||||
| # | Playbook | Trigger |
|
||||
|---|---|---|
|
||||
| [01](01-research.md) | Research | An idea from the operator, or one worth investigating before committing to design: setup questions, findings, a direction the operator confirms |
|
||||
@@ -57,7 +60,7 @@ the expensive half.
|
||||
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces |
|
||||
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
|
||||
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
|
||||
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one push, one delivery |
|
||||
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one try, one review, one delivery |
|
||||
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
|
||||
|
||||
## The cycle is checked
|
||||
|
||||
@@ -94,7 +94,10 @@ about an hour, and said to the operator if it runs past.
|
||||
deferral no issue tracks. The knowledge base then reviews the proposal itself, through a judgement that
|
||||
sees only the record, the records it links and the rules, and answers `approve` or `changes`. On
|
||||
`changes` the record stays proposed with the findings: revise it by proposing it again, whole, with `why`;
|
||||
at most three rounds, then the findings go to the operator with a recommendation.
|
||||
after three rounds the lead does not stop to ask, but fixes each further finding itself and proposes again
|
||||
(the operator, 2026-10-18: ADR 0429 went past three rounds, each finding narrower than the last, and asking
|
||||
first only added a wait). Only a finding that would change what was decided, rather than how the record
|
||||
says it, goes to the operator, with a recommendation.
|
||||
On `approve` it is accepted, or, when it says `needs-operator`, it waits for the operator's Approve on a
|
||||
channel that proves who answered. Nothing proposed is in force. `knowledge-base.get` shows a record's
|
||||
reviews and the operator's ask when it waits for one.
|
||||
@@ -164,3 +167,5 @@ when one fails. In this repository, `records.py` checks that a design's `decisio
|
||||
order's six topics, which the knowledge base reads. The three habits of graduating step 2 (side by side, clauses
|
||||
from the direction, the pre-check) are the lead's, read by the reviewer; `propose` refuses a link to nothing,
|
||||
a deferral no issue tracks and a retired word, and the review returns a record named but not linked.
|
||||
The rounds are read in `knowledge-base.get` (its reviews, one per version): a question to the operator about a
|
||||
record's wording after its third round is the failure of step 4 of *Writing a decision record*.
|
||||
|
||||
@@ -17,7 +17,11 @@ A design becomes **milestones**, and each milestone becomes **issues**:
|
||||
- **Milestones are cut by release order.** A milestone ends where one repository's change must be released before
|
||||
another can use it (a new manifest word before the catalogue reads it, an SDK change before its users, until
|
||||
design 47's milestones 3 and 6 lift those orders); the lead checks this when cutting, so no milestone needs a
|
||||
second release.
|
||||
second release. The orders to look for are those playbook [07](07-feature-branches.md) step 0 item 6 names (an SDK
|
||||
change before its users, a new wire key before the node that reads it, the first module of a repository the catalogue
|
||||
never built, a part that needs a decision first); a milestone cut by such an order may be usable only by the next one,
|
||||
and says so in its outcome. A milestone the prepare step finds holding such an order is split into milestones in the
|
||||
design, each one release, never carried as parts of one (2026-10-18: design 47's release 1-2 became six).
|
||||
- **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the
|
||||
milestone's development time. Issues that must land in order are linked `depends-on`.
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ A small fix (one repository, one issue, about an hour of work) follows
|
||||
- **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited
|
||||
12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14). There is no ready mark since ADR 0421:
|
||||
the operator's Approve is asked as soon as the checks pass.
|
||||
- **Review rounds spent on what a run or a search would have shown.** On 2026-10-19 milestone R3b of design 47
|
||||
took two review rounds and one more cycle after a failed try. A test that "failed first" only by not compiling
|
||||
proved nothing, and dropping the field it was for passed every test. A reader of the changed record that the
|
||||
survey never listed ended a module's conditions at each sync. The reviewer judged the bus tests by reasoning, and
|
||||
wrongly, because they ran only after the review.
|
||||
- **A module proven only after it reached the machines.** The mail modules were delivered with unit tests
|
||||
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
|
||||
|
||||
@@ -42,13 +47,13 @@ opened **once**, after the review. It runs in five steps, and its time is bounde
|
||||
| Step | What happens | Bound |
|
||||
|---|---|---|
|
||||
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
|
||||
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit | the milestone's issues, 30 minutes each |
|
||||
| 2. Review | one reviewer reads all of it; reports go both ways; at most 3 rounds | 15 minutes |
|
||||
| 3. Check | push; the forge's merge checks run the integration tests; no further review | one check run |
|
||||
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit; then each branch is pushed and tried | the milestone's issues, 30 minutes each, and one try |
|
||||
| 2. Review | one reviewer reads all of it with the tries' results; reports go both ways; at most 3 rounds | 15 minutes |
|
||||
| 3. Check | push the reviewed heads; a head the review changed is tried again; then the pull requests are opened and the forge's merge checks run the integration tests again; no further review | at most one more try and one check run |
|
||||
| 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan |
|
||||
|
||||
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, reviewed once,
|
||||
pushed once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
|
||||
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, tried before its review, reviewed once,
|
||||
opened once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
|
||||
merge check, an Approve and a delivery plan of waiting, so the fewest cuts is the fastest. Milestones are cut by
|
||||
release order when the design is broken up (playbook [04](04-build-handoff.md)); a release-order dependency found
|
||||
inside a milestone is a fault of that cut, said to the operator, never a silent extra step.
|
||||
@@ -87,13 +92,16 @@ never into the running one, unless it blocks the running one.
|
||||
- what not to touch, and what lands beside it in other repositories;
|
||||
- the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside
|
||||
the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
|
||||
integration tests, which the forge's checks run).
|
||||
integration tests, which `mesh-controller.try` and the forge's checks run).
|
||||
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
|
||||
file:line, and the lead writes the file from its answers.
|
||||
5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
|
||||
better work the agents do, and the less review is needed). Before any agent starts, each issue carries, with
|
||||
`issue-tracker.note`: every file and test that touches the change, found by search and not from memory
|
||||
(file:line); how the change is checked and delivered end to end (which test proves its done-when, which
|
||||
(file:line); **every other reader and writer of each value the change adds or changes** (a field, a key, a kind
|
||||
of record), each with what it does with the new value, and a criterion for each that could treat it wrongly
|
||||
(2026-10-19: the router's sync with the controller ended every condition the controller did not list, and the
|
||||
survey, which followed only the new condition's own path, never named it); how the change is checked and delivered end to end (which test proves its done-when, which
|
||||
modules its delivery plan moves, to which machines); and for a group, its merge order. The context file
|
||||
points to these notes; an issue reread by another session or a later agent then holds what was found.
|
||||
When the reading is large, it starts with a read-only survey agent on the smaller model.
|
||||
@@ -111,7 +119,19 @@ never into the running one, unless it blocks the running one.
|
||||
2026-10-17), into issues of about 30 minutes each, one change each, in the order they land: the bound of a
|
||||
fixing agent's issue is then a real bound. On 2026-10-17 one issue held four changes sized at four to six
|
||||
hours; it ran past its 30-minute bound for over an hour, and the reports could only say "still on part D".
|
||||
6. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
||||
6. **Look for the orders a release hides, before any agent starts** (2026-10-18: design 47's release 1-2 was cut
|
||||
as one milestone and its prepare step found three orders inside it, each a cut of its own). Read, for each
|
||||
repository of the release, what it takes from the others and how: an SDK change still ships before its users
|
||||
(they vendor a pinned SDK, and a pin to a commit no branch holds is a download that fails later, issue 1148);
|
||||
a new key on the wire between the controller and the node-engine is sent only to a node that says it reads it,
|
||||
since the node-engine's decoder refuses an unknown key; the first module of a repository the catalogue has never
|
||||
built registers only by the route the controller allows for it; and a part that needs something the bus or a
|
||||
manifest cannot yet give (an event only a system account hears, a bucket whose keys expire) needs its decision
|
||||
first. Each order found is said to the operator with the cut it asks for, never built around.
|
||||
7. **Write fixtures from the shape the other side writes, never from memory** (2026-10-18: a watchman's tests
|
||||
mirrored a build key the controller never writes, and every test passed): copy a value from the writer's code or
|
||||
from the live bus, and name where it came from.
|
||||
8. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
||||
|
||||
## Step 1 — Develop (one fixing agent per repository, smaller model)
|
||||
|
||||
@@ -121,21 +141,28 @@ never into the running one, unless it blocks the running one.
|
||||
the next instruction, so its context is paid for once.
|
||||
- **Unit tests only, each well under a second** (how-we-build §5, ADR 0391). **The whole unit suite runs before
|
||||
every commit** (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a
|
||||
change breaks tests in packages it never touched. Each issue's first test fails before its change and passes
|
||||
after; `gofmt`, `go vet` and the whole unit suite stay green. **The tests are written from the issue's
|
||||
change breaks tests in packages it never touched. Each issue's first test fails before its change, on an assertion about what the
|
||||
code does, and passes after. **A test that fails only because it does not compile has not failed first**: where
|
||||
the test calls what the change adds, the agent takes out the change's effect (the line that does it, not the new
|
||||
function), sees the test fail, and puts it back (2026-10-19: a check request's new field was proven by a test of
|
||||
its helper alone, and dropping the field from the request passed every test); `gofmt`, `go vet` and the whole unit suite stay green. **The tests are written from the issue's
|
||||
acceptance criteria**: one test or more per criterion at the level it names, each test's comment citing
|
||||
the criterion it proves by its label (`// 1118/AC2`), and no criterion left without its test; an integration criterion is a test
|
||||
that compiles and waits for the check step. The integration tests (`merge-check.sh` with the store and the
|
||||
bus) wait for the check step.
|
||||
- **Nothing is pushed** and no pull request is opened in this step. Never land on a red check: run the
|
||||
that compiles and waits for the try at this step's end. The integration tests (those that start a store, a bus
|
||||
or a process) are run first by that try, and again by the forge's merge check.
|
||||
- **No pull request is opened in this step. When every issue is committed, the lead brings each feature branch up
|
||||
to the current trunk (fetch by name, then merge), pushes it and tries it** (`mesh-controller.try`, as step 3 item 1 says), so the review reads the integration tests' real
|
||||
results and judges none by reasoning (2026-10-19: a reviewer reasoned that a warning goes out at once, the
|
||||
router holds it 30 s, and the try after the review found it). A failed try goes back to the same fixing agent
|
||||
with its whole log, read as step 3 item 3 says, before the review starts. Never land on a red check: run the
|
||||
checks, read the result, then land in a separate step.
|
||||
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
|
||||
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
|
||||
never push, open, merge or close a pull request; never write to a store's database by hand, a schema change
|
||||
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
|
||||
move another issue; **never run an integration test locally** (no `go test -tags integration`, no `merge-check.sh`,
|
||||
no bus or store test run): compile them with `go vet -tags integration ./...` and leave the running to the
|
||||
forge's merge check. On 2026-10-17 three fixing agents ran them anyway, one for minutes on end, because the
|
||||
move another issue; **never run an integration test on the machine** (no `go test -tags integration`, no `merge-check.sh`,
|
||||
no store or bus started by hand): compile them with `go vet -tags integration ./...` and leave the running to
|
||||
the lead's try at the end of this step, where `mesh-controller.try` runs them on the pushed branch. On 2026-10-17 three fixing agents ran them anyway, one for minutes on end, because the
|
||||
brief only said they wait for the check (the operator). Report back in under 150 words: commits, tests added and that they failed first, the
|
||||
check results, anything you stopped on.
|
||||
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
|
||||
@@ -150,8 +177,9 @@ never into the running one, unless it blocks the running one.
|
||||
|
||||
## Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
|
||||
|
||||
1. **Bring every feature branch up to the current trunk** (fetch by name, then merge), so the reviewed tree is
|
||||
the tree that is pushed.
|
||||
1. **The reviewed tree is the tree that was tried**: every feature branch was brought up to the current trunk
|
||||
before its push and try (step 1). A try and a merge check each run on the merge with the trunk as it is then, so a
|
||||
trunk that moves later is met by the forge's merge check, not by another merge here.
|
||||
2. **One reviewer reads every repository's local commits** together, against the context files and the
|
||||
decisions. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
|
||||
its id for every later round, for the extra cycle after a failed check (step 3) and for its review record, so it
|
||||
@@ -160,13 +188,20 @@ never into the running one, unless it blocks the running one.
|
||||
It says which round it is in ("round 1 of at most 3").
|
||||
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
|
||||
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
|
||||
4. **Every new integration test is read against the code it drives before the first push.** It has never run: fixing agents only compile it, and its first run is the merge check, so each fault it hides costs a whole check. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
4. **Every new integration test is read against the code it drives and against its try.** Fixing agents only compile it, and its one run before the review is the try (step 1), which shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
|
||||
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
|
||||
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
|
||||
that would fail without the change, and no test proves less than its criterion says. A criterion missing
|
||||
for a case the issue's done-when needs is written onto the issue (step 0, item 5) and is a finding; a test
|
||||
missing for a criterion is a finding.
|
||||
**A rule keyed on a property is checked against everything else on the mesh that has it.** When a change
|
||||
decides by a property the bus or the store gives (a stream's retention, a folder's name, an account's kind),
|
||||
the reviewer lists what else already has that property, live, and asks what the rule does to it. On
|
||||
2026-10-19 release 1 of feature mail-sender made a work-queue stream's dead letters drop-only by the bus's
|
||||
work-queue retention; three review rounds passed it, and only its try showed that every seat's work stream has
|
||||
that retention too, so dead asks across the mesh would no longer have been delivered again (issue 1178, fixed
|
||||
before its pull request by a mark only a declared module work queue carries).
|
||||
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
|
||||
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
|
||||
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
|
||||
@@ -174,9 +209,15 @@ never into the running one, unless it blocks the running one.
|
||||
|
||||
## Step 3 — Check (no review)
|
||||
|
||||
1. **Push every feature branch and open one pull request per repository, together.**
|
||||
The integration tests
|
||||
run only in the merge check (the operator, 2026-10-16), never before. Pull requests in two or
|
||||
1. **Push the reviewed heads, try again a head the review changed, then open one pull request per repository,
|
||||
together.** **The integration tests run before the pull requests are opened**, first before the review (step 1)
|
||||
and again for a head the review changed since its try: `mesh-controller.try` with the
|
||||
repository and the pushed branch runs the repository's `merge-check.sh` (main's version) on the merge, in the
|
||||
toolchain containers and against the throwaway store and bus a merge check gets, and answers with an id to
|
||||
read back. A try that fails goes to step 3 item 3 as a failed check does; the pull requests are opened only
|
||||
when every head's last try passed (the operator, 2026-10-18: three pushes in a row failed their first merge check on
|
||||
faults a try finds in minutes). Never `merge-check.sh`, `go test -tags integration` or a store or bus started
|
||||
by hand on the machine (the operator, 2026-10-18). Pull requests in two or
|
||||
more repositories that share the branch name are one delivery group (ADR 0239 §2).
|
||||
Where a session pushes through the forge tool `gitea_commit` (one commit on the forge's current base), the
|
||||
reviewer first confirms that commit's tree equals the reviewed one. **Each pull request's description carries
|
||||
@@ -184,7 +225,7 @@ never into the running one, unless it blocks the running one.
|
||||
`Reviewed-by: <who@node> at <full sha>` (the issue 1022 note gives its form). mesh-delivery reads it at every new
|
||||
head and puts it in the Approve's ask; a missing line, or one naming another commit, is said first in the ask.
|
||||
It is never a gate.
|
||||
2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No
|
||||
2. **The forge's merge checks run the integration tests again, as the gate** (`mesh/merge-gate`, `mesh/repo-check`). No
|
||||
second review: step 2 reviewed this tree.
|
||||
3. **A check that fails** goes back to the same reviewer, continued by its id, with the failure, for **one more cycle** of step 2 with
|
||||
the same fixing agents; then push once again. **The failure is read from the check's whole log**, never
|
||||
@@ -202,6 +243,11 @@ never into the running one, unless it blocks the running one.
|
||||
carries an integration test that starts the module whole, its process against the real bus and store, and
|
||||
shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
|
||||
that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16).
|
||||
**A new manifest word's test goes through the push that meets it first, not only the verb that fills it.**
|
||||
On 2026-10-19 release 1 of feature mail-sender tested a process's own bus account through `module issue`
|
||||
alone; the first push after mesh-mail declared the process composed a random 40-character stand-in for the
|
||||
account file before anyone issued it, and the process crash-looped until the account was issued and pushed by
|
||||
hand (issue 1226).
|
||||
5. **The operator's Approve is asked as soon as the checks pass** (ADR 0421). Nobody marks, labels, comments or
|
||||
rechecks: mesh-delivery asks the Approve on Telegram, the channel that proves who answered today (ADR 0398 §3;
|
||||
an Approve at the terminal is issue 953), once both checks passed, for a delivery group once for all its
|
||||
@@ -269,6 +315,10 @@ It loops `mesh_wait` on the event families of what it watches (`mesh-delivery.tr
|
||||
a minute. After every answer or timeout it reads the state from the verb that owns it (`mesh-delivery.show`,
|
||||
`mesh-controller.plans`, `knowledge-base.get`), because an event only wakes it (ADR 0367). It finishes, and so wakes
|
||||
the lead, when the state changes or the wait passes its bound. The lead never sleeps or polls by hand to wait.
|
||||
**A watcher watches one wait and ends with it.** It is never re-pointed at the next pull request or plan: on
|
||||
2026-10-19 one watcher sent on to three pull requests in turn missed that the third's checks had passed at 04:58,
|
||||
and the lead told the operator for 15 minutes that they were still running. The next wait gets a fresh watcher,
|
||||
and before a report the watcher reads the owning verb once more and says only what it says now.
|
||||
|
||||
**Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery plan runs, and at once when something lands, stops or
|
||||
needs the operator,** the lead reports, in one of the two layouts below (the operator, 2026-10-16: an overview
|
||||
|
||||
@@ -18,8 +18,8 @@ hour is a feature.
|
||||
|
||||
## The aim
|
||||
|
||||
A small fix spends its time on the fix, and runs the long part once: **one review, one push, one check run,
|
||||
one delivery**. Everything before the push is local and fast.
|
||||
A small fix spends its time on the fix, and runs the long part once: **one try before the review, one review, one check run,
|
||||
one delivery**. Everything before the try is local and fast.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -34,26 +34,28 @@ one delivery**. Everything before the push is local and fast.
|
||||
2. **Move the issue to `fixing`** with `by` as who@node.
|
||||
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`, never in
|
||||
the repository's own clone, which stays on `main` (playbook [07](07-feature-branches.md), step 0, item 2). Write the
|
||||
test that fails today first; make the change; keep `gofmt`, `go vet` and the repository's whole unit suite green
|
||||
test that fails today first, on an assertion about what the code does: a test that fails only because it
|
||||
does not compile has not failed first (playbook [07](07-feature-branches.md) step 1); make the change; keep `gofmt`, `go vet` and the repository's whole unit suite green
|
||||
before every commit (each test well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391).
|
||||
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
|
||||
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
|
||||
`sh merge-check.sh`).
|
||||
4. **One review of the local commit, before anything is pushed** (a reviewer that did not write it, larger
|
||||
`sh merge-check.sh`). Then push the branch and run `mesh-controller.try` on it, so the review reads the
|
||||
integration tests' real results; a failed try goes back to the same agent before the review.
|
||||
4. **One review of the commit and its try** (a reviewer that did not write it, larger
|
||||
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it reads each acceptance criterion
|
||||
against its test, as playbook 07 step 2 item 6; a finding outside the issue's done-when and its acceptance
|
||||
criteria is opened as an issue of its own, never asked as a change). The reviewer's findings go back to the same agent that wrote the fix, continued by
|
||||
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
|
||||
operator's Approve of its merge is its review, and the review record says so.
|
||||
5. **Push once and open the pull request.** The trunk is what runs and nothing holds merged work back
|
||||
(ADR 0401), so a fix waits for no other change's delivery. The forge's merge
|
||||
check runs the integration tests, the only
|
||||
place they run (the operator, 2026-10-16). A failed check
|
||||
goes back to the same agent with the failure, and the reviewer reads only the change it makes.
|
||||
5. **Push the reviewed head, try it again if the review changed it, and open the pull request.** The trunk is what runs and nothing holds merged work back
|
||||
(ADR 0401), so a fix waits for no other change's delivery. `mesh-controller.try` on the pushed branch runs
|
||||
the integration tests, and the pull request is opened only when the head's last try passed; the forge's merge check runs
|
||||
them again as the gate (playbook [07](07-feature-branches.md) step 3). They are never run on the machine. A
|
||||
failed try or check goes back to the same agent with the failure, and the reviewer reads only the change it makes.
|
||||
6. **The pull request's description carries the review record** (`Reviewed-by: <who@node> at <full sha>`,
|
||||
playbook [07](07-feature-branches.md) step 3), and mesh-delivery asks the operator's Approve as soon as its
|
||||
checks pass and merges the approved head (ADR 0421); nobody marks, labels, comments or rechecks. A fix to a module that changes how it runs carries an
|
||||
integration test starting the module whole against the real bus and store, run by the forge's checks
|
||||
integration test starting the module whole against the real bus and store, run by `mesh-controller.try` and the forge's checks
|
||||
(ADR 0391 §1, §6); unit tests alone are not that proof, and the lab is not used. The operator's word is waited for only when the fix changes what the operator sees, removes data,
|
||||
or needs the operator's terminal.
|
||||
7. **Follow its delivery plan to done** (`mesh-controller.plans` with `wait` and `until: done`), prove the fix on
|
||||
@@ -97,7 +99,7 @@ reading itself and uses one subagent only for the review.
|
||||
## How it is checked
|
||||
|
||||
- A small fix has one pull request with one or few commits naming its issue, opened after its review: a pull
|
||||
request with many heads is a fix reviewed after its push.
|
||||
request with many heads is a fix reviewed after its pull request was opened.
|
||||
- Its issue moves to `resolved` within the hour its delivery plan ends.
|
||||
- A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
|
||||
running pushed with no integration test of the module whole, or a review report
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Novox Playbooks</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f7f6f2; --card:#ffffff; --ink:#1d1f23; --mute:#5d6470; --line:#d9d6cc; --soft:#efede6;
|
||||
--idea:#2f5fa7; --sym:#b4462b; --dec:#7a4bb0; --build:#3c8a5a; --mesh:#0f6f7a; --op:#c2410c; --side:#8a6d1f;
|
||||
--ok:#15803d; --okbg:#e6f4ea; --warn:#a16207; --warnbg:#fdf3d7; --bad:#b91c1c; --badbg:#fde8e8;
|
||||
}
|
||||
@media (prefers-color-scheme: dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--bg:#15171b; --card:#1e2127; --ink:#e8e6e1; --mute:#9aa1ad; --line:#353a43; --soft:#262a31;
|
||||
--idea:#7aa7ef; --sym:#f08a6c; --dec:#b993ef; --build:#6fcf97; --mesh:#4fc3cf; --op:#fb923c; --side:#d9b44a;
|
||||
--ok:#4ade80; --okbg:#173222; --warn:#facc15; --warnbg:#3a3016; --bad:#f87171; --badbg:#3a1d1d;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--bg:#15171b; --card:#1e2127; --ink:#e8e6e1; --mute:#9aa1ad; --line:#353a43; --soft:#262a31;
|
||||
--idea:#7aa7ef; --sym:#f08a6c; --dec:#b993ef; --build:#6fcf97; --mesh:#4fc3cf; --op:#fb923c; --side:#d9b44a;
|
||||
--ok:#4ade80; --okbg:#173222; --warn:#facc15; --warnbg:#3a3016; --bad:#f87171; --badbg:#3a1d1d;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}
|
||||
main{max-width:1120px;margin:0 auto;padding:28px 16px 72px}
|
||||
h1{font-size:28px;margin:0 0 4px}
|
||||
h2{font-size:20px;margin:44px 0 6px}
|
||||
h3{font-size:16px;margin:0 0 6px}
|
||||
p{margin:6px 0 10px}
|
||||
.sub{color:var(--mute);margin:0 0 18px}
|
||||
.lead{max-width:760px}
|
||||
nav{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 6px}
|
||||
nav a,.chip{display:inline-block;padding:4px 11px;border:1px solid var(--line);border-radius:999px;color:var(--ink);text-decoration:none;font-size:13px;background:var(--card);cursor:pointer}
|
||||
nav a:hover,.chip:hover{border-color:var(--mute)}
|
||||
.chip.on{background:var(--ink);color:var(--bg);border-color:var(--ink)}
|
||||
.box{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px;margin:12px 0}
|
||||
.scroll{overflow-x:auto}
|
||||
svg{display:block;max-width:100%;height:auto;font-family:inherit}
|
||||
svg text{fill:var(--ink);font-size:13px}
|
||||
svg .mute{fill:var(--mute);font-size:11.5px}
|
||||
svg .t{font-weight:600}
|
||||
svg .edge{stroke:var(--mute);stroke-width:1.6;fill:none}
|
||||
svg .dash{stroke-dasharray:5 4}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:14px;font-size:13px;color:var(--mute);margin-top:8px}
|
||||
.legend i{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:6px;vertical-align:-1px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
|
||||
.pb{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;border-top:4px solid var(--c,var(--line))}
|
||||
.pb .num{font:600 12px/1 ui-monospace,monospace;color:var(--c);letter-spacing:.04em}
|
||||
.pb h3{margin:4px 0 4px;font-size:17px}
|
||||
.pb .trig{color:var(--mute);font-size:13.5px;margin:0 0 8px}
|
||||
.pb details{border-top:1px solid var(--line);padding-top:6px;margin-top:6px}
|
||||
.pb summary{cursor:pointer;font-size:13.5px;font-weight:600;color:var(--ink);list-style:none}
|
||||
.pb summary::before{content:"▸ ";color:var(--mute)}
|
||||
.pb details[open] summary::before{content:"▾ "}
|
||||
.pb ol,.pb ul{margin:6px 0 4px;padding-left:20px;font-size:13.5px}
|
||||
.pb li{margin:2px 0}
|
||||
.kv{display:grid;grid-template-columns:96px 1fr;gap:4px 10px;font-size:13px;margin-top:6px}
|
||||
.kv b{color:var(--mute);font-weight:500}
|
||||
.tag{display:inline-block;font-size:11.5px;padding:1px 7px;border-radius:999px;background:var(--soft);color:var(--mute);margin:0 4px 4px 0}
|
||||
.new{background:var(--okbg);color:var(--ok);font-weight:600}
|
||||
table{border-collapse:collapse;width:100%;font-size:14px}
|
||||
th,td{text-align:left;padding:7px 9px;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
th{color:var(--mute);font-weight:600;font-size:12.5px;text-transform:uppercase;letter-spacing:.03em}
|
||||
.note{border-left:3px solid var(--ok);background:var(--okbg);padding:9px 12px;border-radius:6px;font-size:14px}
|
||||
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
||||
@media (max-width:760px){.two{grid-template-columns:1fr}}
|
||||
footer{color:var(--mute);font-size:13px;margin-top:48px}
|
||||
code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;border-radius:4px}
|
||||
.theme{float:right}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<button class="chip theme" id="theme" type="button">Theme</button>
|
||||
<h1>Novox Playbooks</h1>
|
||||
<p class="sub">How work is done on the mesh, from an idea or a symptom to a delivered change. Drawn from hq's <code>00-META/process/</code> at main <code>f269e83</code>, 2026-10-19, including playbook change hq #433 (try before the review).</p>
|
||||
|
||||
<nav>
|
||||
<a href="#cycle">The cycle</a>
|
||||
<a href="#feature">A feature, step by step</a>
|
||||
<a href="#tests">When tests run</a>
|
||||
<a href="#review">The review loop</a>
|
||||
<a href="#issues">An issue's life</a>
|
||||
<a href="#where">Where things live</a>
|
||||
<a href="#all">All playbooks</a>
|
||||
</nav>
|
||||
|
||||
<!-- ============ THE CYCLE ============ -->
|
||||
<h2 id="cycle">The development cycle</h2>
|
||||
<p class="lead">Work enters as an <b>idea</b> or a <b>symptom</b>. An idea becomes a decision, a design and then milestones; a symptom becomes an issue. Both end as code delivered to the machines, and the as-is design is updated. <b>No design without a decision; no development without a design or an issue that sends it.</b></p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 470" role="img" aria-label="The development cycle across playbooks 01 to 09">
|
||||
<defs>
|
||||
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker>
|
||||
</defs>
|
||||
<!-- entry points -->
|
||||
<g>
|
||||
<rect x="16" y="40" width="120" height="50" rx="25" fill="none" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="76" y="70" text-anchor="middle" class="t">Idea</text>
|
||||
<rect x="16" y="300" width="120" height="50" rx="25" fill="none" stroke="var(--sym)" stroke-width="2"/>
|
||||
<text x="76" y="330" text-anchor="middle" class="t">Symptom</text>
|
||||
</g>
|
||||
<!-- idea path -->
|
||||
<g>
|
||||
<rect x="176" y="30" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="251" y="58" text-anchor="middle" class="t">01 Research</text>
|
||||
<text x="251" y="78" text-anchor="middle" class="mute">questions, findings,</text>
|
||||
<text x="251" y="92" text-anchor="middle" class="mute">direction confirmed</text>
|
||||
|
||||
<rect x="366" y="30" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="446" y="58" text-anchor="middle" class="t">02 Graduation</text>
|
||||
<text x="446" y="78" text-anchor="middle" class="mute">decisions in the</text>
|
||||
<text x="446" y="92" text-anchor="middle" class="mute">knowledge base · to-be</text>
|
||||
|
||||
<rect x="566" y="30" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="646" y="58" text-anchor="middle" class="t">04 Breakdown</text>
|
||||
<text x="646" y="78" text-anchor="middle" class="mute">milestones by release,</text>
|
||||
<text x="646" y="92" text-anchor="middle" class="mute">issues per repository</text>
|
||||
|
||||
<rect x="766" y="30" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--build)" stroke-width="2.5"/>
|
||||
<text x="841" y="58" text-anchor="middle" class="t">07 Feature</text>
|
||||
<text x="841" y="78" text-anchor="middle" class="mute">one milestone =</text>
|
||||
<text x="841" y="92" text-anchor="middle" class="mute">one release</text>
|
||||
</g>
|
||||
<!-- symptom path -->
|
||||
<g>
|
||||
<rect x="176" y="290" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--sym)" stroke-width="2"/>
|
||||
<text x="251" y="318" text-anchor="middle" class="t">03 Issues</text>
|
||||
<text x="251" y="338" text-anchor="middle" class="mute">opened in the tracker,</text>
|
||||
<text x="251" y="352" text-anchor="middle" class="mute">diagnosed, located</text>
|
||||
|
||||
<rect x="566" y="290" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--build)" stroke-width="2.5"/>
|
||||
<text x="646" y="318" text-anchor="middle" class="t">08 Small fix</text>
|
||||
<text x="646" y="338" text-anchor="middle" class="mute">one issue, one repository,</text>
|
||||
<text x="646" y="352" text-anchor="middle" class="mute">about an hour</text>
|
||||
</g>
|
||||
<!-- delivered + as-is -->
|
||||
<g>
|
||||
<rect x="950" y="160" width="116" height="74" rx="10" fill="var(--card)" stroke="var(--mesh)" stroke-width="2.5"/>
|
||||
<text x="1008" y="188" text-anchor="middle" class="t">Delivered</text>
|
||||
<text x="1008" y="206" text-anchor="middle" class="mute">Approve, merge,</text>
|
||||
<text x="1008" y="220" text-anchor="middle" class="mute">delivery plan</text>
|
||||
|
||||
<rect x="766" y="160" width="150" height="74" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2" stroke-dasharray="5 4"/>
|
||||
<text x="841" y="188" text-anchor="middle" class="t">As-is updated</text>
|
||||
<text x="841" y="206" text-anchor="middle" class="mute">02 "when it ships":</text>
|
||||
<text x="841" y="220" text-anchor="middle" class="mute">to-be → implemented</text>
|
||||
</g>
|
||||
<!-- side playbooks -->
|
||||
<g>
|
||||
<rect x="176" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
|
||||
<text x="271" y="424" text-anchor="middle" class="t">05 Constitution sync</text>
|
||||
<text x="271" y="442" text-anchor="middle" class="mute">how-we-build → derived page</text>
|
||||
<rect x="396" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
|
||||
<text x="491" y="424" text-anchor="middle" class="t">06 Writing a module</text>
|
||||
<text x="491" y="442" text-anchor="middle" class="mute">what runs today → a module</text>
|
||||
<rect x="876" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
|
||||
<text x="971" y="424" text-anchor="middle" class="t">09 Cleanup</text>
|
||||
<text x="971" y="442" text-anchor="middle" class="mute">branches, worktrees, on the word</text>
|
||||
</g>
|
||||
<!-- edges -->
|
||||
<path class="edge" d="M136,65 H174" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M326,65 H364" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M526,65 H564" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M726,65 H764" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M916,65 C990,65 1008,100 1008,158" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M136,325 H174" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M326,325 H564" marker-end="url(#ar)"/>
|
||||
<text x="445" y="317" text-anchor="middle" class="mute">a fix of about an hour</text>
|
||||
<path class="edge" d="M726,325 C990,325 1008,290 1008,236" marker-end="url(#ar)"/>
|
||||
<path class="edge" d="M948,197 H918" marker-end="url(#ar)"/>
|
||||
<path class="edge dash" d="M251,290 C251,200 420,170 440,102" marker-end="url(#ar)"/>
|
||||
<text x="300" y="196" class="mute">a design gap: decision first</text>
|
||||
<path class="edge dash" d="M326,300 C560,250 720,170 800,102" marker-end="url(#ar)"/>
|
||||
<text x="612" y="236" class="mute">larger than an hour: a feature</text>
|
||||
<path class="edge dash" d="M1008,236 C1008,330 990,360 971,398" marker-end="url(#ar)"/>
|
||||
</svg>
|
||||
<div class="legend">
|
||||
<span><i style="background:var(--idea)"></i>from an idea</span>
|
||||
<span><i style="background:var(--sym)"></i>from a symptom</span>
|
||||
<span><i style="background:var(--dec)"></i>decisions and designs</span>
|
||||
<span><i style="background:var(--build)"></i>code changes</span>
|
||||
<span><i style="background:var(--mesh)"></i>the mesh delivers</span>
|
||||
<span><i style="background:var(--side)"></i>alongside</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ FEATURE SWIMLANE ============ -->
|
||||
<h2 id="feature">A feature, step by step (playbook 07)</h2>
|
||||
<p class="lead">One milestone is one release: one branch name in every repository it touches, one worktree per repository, one fixing agent per repository kept for the whole milestone, one reviewer, and one pull request per repository, opened once. Since hq #433 the branches are <b>tried before the review</b>, so the reviewer reads real integration results.</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 430" role="img" aria-label="Playbook 07 in lanes: lead, fixing agents, reviewer, the mesh, the operator">
|
||||
<defs>
|
||||
<marker id="ar2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker>
|
||||
</defs>
|
||||
<!-- step columns -->
|
||||
<g class="mute">
|
||||
<text x="190" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">0 · Prepare</text>
|
||||
<text x="190" y="34" text-anchor="middle" class="mute">15 min</text>
|
||||
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop + try</text>
|
||||
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · one try</text>
|
||||
<text x="630" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">2 · Review</text>
|
||||
<text x="630" y="34" text-anchor="middle" class="mute">≤ 3 rounds · 15 min</text>
|
||||
<text x="830" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">3 · Check</text>
|
||||
<text x="830" y="34" text-anchor="middle" class="mute">≤ 1 more try · 1 check</text>
|
||||
<text x="1000" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">4 · Deliver</text>
|
||||
<text x="1000" y="34" text-anchor="middle" class="mute">its delivery plan</text>
|
||||
</g>
|
||||
<!-- lanes -->
|
||||
<g>
|
||||
<rect x="0" y="46" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
|
||||
<rect x="0" y="190" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
|
||||
<rect x="0" y="334" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
|
||||
<text x="10" y="86" class="t">Lead</text><text x="10" y="102" class="mute">larger model</text>
|
||||
<text x="10" y="158" class="t">Fixing agents</text><text x="10" y="174" class="mute">smaller model</text>
|
||||
<text x="10" y="230" class="t">Reviewer</text><text x="10" y="246" class="mute">larger model</text>
|
||||
<text x="10" y="302" class="t">The mesh</text><text x="10" y="318" class="mute">try, checks, plan</text>
|
||||
<text x="10" y="374" class="t">Operator</text><text x="10" y="390" class="mute">Telegram</text>
|
||||
</g>
|
||||
<!-- lead -->
|
||||
<rect x="110" y="56" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="190" y="78" text-anchor="middle" class="t">Survey · criteria</text>
|
||||
<text x="190" y="96" text-anchor="middle" class="mute">context file per repo</text>
|
||||
<rect x="340" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="410" y="78" text-anchor="middle" class="t">Up to trunk · push</text>
|
||||
<text x="410" y="96" text-anchor="middle" class="mute">each branch</text>
|
||||
<rect x="760" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="830" y="78" text-anchor="middle" class="t">Push reviewed heads</text>
|
||||
<text x="830" y="96" text-anchor="middle" class="mute">open the PRs together</text>
|
||||
<rect x="930" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="1000" y="78" text-anchor="middle" class="t">Follow to done</text>
|
||||
<text x="1000" y="96" text-anchor="middle" class="mute">resolve the issues</text>
|
||||
<!-- fixing -->
|
||||
<rect x="300" y="128" width="220" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
|
||||
<text x="410" y="150" text-anchor="middle" class="t">Build each issue, commit</text>
|
||||
<text x="410" y="168" text-anchor="middle" class="mute">unit suite before every commit</text>
|
||||
<rect x="560" y="128" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
|
||||
<text x="630" y="150" text-anchor="middle" class="t">Fix findings</text>
|
||||
<text x="630" y="168" text-anchor="middle" class="mute">the same agents</text>
|
||||
<!-- reviewer -->
|
||||
<rect x="560" y="200" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="630" y="222" text-anchor="middle" class="t">Read all repos</text>
|
||||
<text x="630" y="240" text-anchor="middle" class="mute">+ tries · every criterion</text>
|
||||
<!-- mesh -->
|
||||
<rect x="340" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="410" y="294" text-anchor="middle" class="t">try</text>
|
||||
<text x="410" y="312" text-anchor="middle" class="mute">integration tests run</text>
|
||||
<rect x="760" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="830" y="294" text-anchor="middle" class="t">Forge checks</text>
|
||||
<text x="830" y="312" text-anchor="middle" class="mute">gate · repo · group</text>
|
||||
<rect x="930" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="1000" y="294" text-anchor="middle" class="t">Merge · delivery plan</text>
|
||||
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, tier by tier</text>
|
||||
<!-- operator -->
|
||||
<rect x="860" y="344" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--op)" stroke-width="2.5"/>
|
||||
<text x="930" y="366" text-anchor="middle" class="t">Approve</text>
|
||||
<text x="930" y="384" text-anchor="middle" class="mute">once, for the group</text>
|
||||
<!-- edges -->
|
||||
<path class="edge" d="M190,108 C190,140 260,154 298,154" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M410,128 V110" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M480,90 C545,110 545,250 462,270" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M480,298 C530,298 540,226 558,226" marker-end="url(#ar2)"/>
|
||||
<text x="490" y="262" class="mute">results</text>
|
||||
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
|
||||
<path class="edge dash" d="M610,182 V198" marker-end="url(#ar2)"/>
|
||||
<text x="514" y="196" class="mute">round n+1</text>
|
||||
<path class="edge" d="M700,226 C740,226 740,82 758,82" marker-end="url(#ar2)"/>
|
||||
<text x="712" y="140" class="mute">approved</text>
|
||||
<path class="edge" d="M830,108 V270" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M900,298 C915,298 920,330 925,342" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M1000,344 V326" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M1000,272 V110" marker-end="url(#ar2)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="two">
|
||||
<div class="box">
|
||||
<h3>Step 0 · what the lead writes before any agent starts</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>Every open question settled: on the issue, in the knowledge base, or by the operator.</li>
|
||||
<li>Into each issue: every file and test it touches (file:line), how it is checked and delivered, a group's merge order.</li>
|
||||
<li><span class="tag new">new</span>Every other reader and writer of each value the change touches, each with a criterion.</li>
|
||||
<li>Acceptance criteria: given / when / then, each naming its test level.</li>
|
||||
<li>Release orders hidden in the milestone, said to the operator, never built around.</li>
|
||||
<li>Fixtures copied from what the other side really writes, never from memory.</li>
|
||||
<li>An issue sized over an hour is split before work starts.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>Step 1 · the fixing agent's brief</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>One commit per issue, naming it; tests cite their criterion (<code>// 1217/AC3</code>).</li>
|
||||
<li><span class="tag new">new</span>A test fails first on what the code does. A compile error is no failure: take out the change's effect and see an assertion fail.</li>
|
||||
<li>The whole unit suite before every commit; each test well under a second.</li>
|
||||
<li>Integration tests are compiled only: never run on the machine.</li>
|
||||
<li>Never push, open or merge; never move an issue; never ssh.</li>
|
||||
<li>Code that differs from the context file: stop and report, file:line.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ TESTS ============ -->
|
||||
<h2 id="tests">When tests run</h2>
|
||||
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) never run there: <code>mesh-controller.try</code> runs them on the pushed branch, and the forge's merge check runs them again as the gate.</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 170" role="img" aria-label="Test levels over a feature's timeline">
|
||||
<defs><marker id="ar3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
<path class="edge" d="M120,140 H1060" marker-end="url(#ar3)"/>
|
||||
<text x="120" y="162" class="mute">develop</text><text x="470" y="162" class="mute">end of step 1</text><text x="640" y="162" class="mute">review</text><text x="800" y="162" class="mute">step 3</text><text x="960" y="162" class="mute">merge</text>
|
||||
<text x="10" y="44" class="t">Unit</text><text x="10" y="60" class="mute">on the machine</text>
|
||||
<g fill="var(--build)">
|
||||
<rect x="120" y="36" width="700" height="16" rx="8" opacity=".25"/>
|
||||
<circle cx="160" cy="44" r="6"/><circle cx="220" cy="44" r="6"/><circle cx="290" cy="44" r="6"/><circle cx="350" cy="44" r="6"/><circle cx="420" cy="44" r="6"/><circle cx="600" cy="44" r="6"/><circle cx="680" cy="44" r="6"/>
|
||||
</g>
|
||||
<text x="830" y="42" class="mute">whole suite before every commit</text><text x="830" y="56" class="mute">each test under a second</text>
|
||||
<text x="10" y="104" class="t">Integration</text><text x="10" y="120" class="mute">never on the machine</text>
|
||||
<g>
|
||||
<rect x="120" y="96" width="340" height="16" rx="8" fill="var(--mute)" opacity=".18"/>
|
||||
<text x="290" y="108" text-anchor="middle" class="mute">compiled only: go vet -tags integration</text>
|
||||
<rect x="470" y="90" width="110" height="28" rx="6" fill="var(--mesh)"/><text x="525" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">try</text>
|
||||
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">try again if changed</text>
|
||||
<rect x="920" y="90" width="120" height="28" rx="6" fill="var(--mesh)"/><text x="980" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">merge check</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- ============ REVIEW LOOP ============ -->
|
||||
<h2 id="review">The review loop</h2>
|
||||
<div class="two">
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 520 300" role="img" aria-label="The review loop: at most three rounds">
|
||||
<defs><marker id="ar4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
<rect x="20" y="20" width="150" height="54" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="95" y="44" text-anchor="middle" class="t">Round n of ≤ 3</text><text x="95" y="62" text-anchor="middle" class="mute">code + tries + criteria</text>
|
||||
<polygon points="300,20 380,47 300,74 220,47" fill="var(--card)" stroke="var(--mute)"/>
|
||||
<text x="300" y="51" text-anchor="middle" class="t">Wrong?</text>
|
||||
<rect x="400" y="20" width="110" height="54" rx="8" fill="var(--okbg)" stroke="var(--ok)" stroke-width="2"/>
|
||||
<text x="455" y="44" text-anchor="middle" class="t">Approve</text><text x="455" y="62" text-anchor="middle" class="mute">Reviewed-by line</text>
|
||||
<rect x="210" y="130" width="180" height="54" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
|
||||
<text x="300" y="154" text-anchor="middle" class="t">Same fixing agents fix</text><text x="300" y="172" text-anchor="middle" class="mute">answer per finding</text>
|
||||
<rect x="20" y="130" width="160" height="54" rx="8" fill="var(--soft)" stroke="var(--side)"/>
|
||||
<text x="100" y="154" text-anchor="middle" class="t">issue-tracker.open</text><text x="100" y="172" text-anchor="middle" class="mute">anything beyond the issue</text>
|
||||
<rect x="300" y="236" width="210" height="54" rx="8" fill="var(--warnbg)" stroke="var(--warn)" stroke-width="2"/>
|
||||
<text x="405" y="260" text-anchor="middle" class="t">After round 3: the operator</text><text x="405" y="278" text-anchor="middle" class="mute">what is left, with a recommendation</text>
|
||||
<path class="edge" d="M170,47 H218" marker-end="url(#ar4)"/>
|
||||
<path class="edge" d="M380,47 H398" marker-end="url(#ar4)"/><text x="384" y="38" class="mute">no</text>
|
||||
<path class="edge" d="M300,74 V128" marker-end="url(#ar4)"/><text x="306" y="104" class="mute">yes</text>
|
||||
<path class="edge" d="M210,157 C140,157 95,120 95,76" marker-end="url(#ar4)"/><text x="120" y="112" class="mute">n+1</text>
|
||||
<path class="edge dash" d="M250,66 C200,100 170,120 150,128" marker-end="url(#ar4)"/><text x="178" y="96" class="mute">hardening,</text><text x="178" y="108" class="mute">a nicer form</text>
|
||||
<path class="edge dash" d="M360,184 C380,210 400,220 405,234" marker-end="url(#ar4)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h3>What a round may ask</h3>
|
||||
<p style="font-size:14px">Only what makes the change <b>wrong</b> against its issues, their acceptance criteria and its decisions. Anything more (hardening, a further case, a nicer form) becomes a new issue, and the round does not wait for it.</p>
|
||||
<h3 style="margin-top:12px">What every round reads</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>Every criterion against its test: a test at its level that would fail without the change.</li>
|
||||
<li>Every new integration test traced through the code, and its try's result.</li>
|
||||
<li>That each integration test enters by the path the live trigger takes.</li>
|
||||
</ul>
|
||||
<div class="note" style="margin-top:10px">Why reviews took more than one round (R3b, 2026-10-19): a test that "failed" only by not compiling, a reader of the changed record the survey missed, and bus tests judged by reasoning before they ran. hq #433 changes all three.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ ISSUE LIFE ============ -->
|
||||
<h2 id="issues">An issue's life (playbook 03)</h2>
|
||||
<p class="lead">The issue tracker (seat <code>issue-tracker</code>) holds every issue's number, status, owner, fix and links. Its <code>move</code> refuses a wrong move at the write.</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 230" role="img" aria-label="Issue statuses and the moves between them">
|
||||
<defs><marker id="ar5" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
<g>
|
||||
<rect x="20" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--sym)" stroke-width="2"/><text x="80" y="100" text-anchor="middle" class="t">open</text>
|
||||
<rect x="220" y="70" width="140" height="50" rx="25" fill="var(--card)" stroke="var(--warn)" stroke-width="2"/><text x="290" y="100" text-anchor="middle" class="t">diagnosing</text>
|
||||
<rect x="440" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/><text x="500" y="100" text-anchor="middle" class="t">located</text>
|
||||
<rect x="640" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--build)" stroke-width="2"/><text x="700" y="100" text-anchor="middle" class="t">fixing</text>
|
||||
<rect x="860" y="70" width="140" height="50" rx="25" fill="var(--okbg)" stroke="var(--ok)" stroke-width="2.5"/><text x="930" y="100" text-anchor="middle" class="t">resolved</text>
|
||||
<rect x="440" y="170" width="120" height="44" rx="22" fill="var(--soft)" stroke="var(--mute)"/><text x="500" y="197" text-anchor="middle" class="t">wontfix</text>
|
||||
</g>
|
||||
<path class="edge" d="M140,95 H218" marker-end="url(#ar5)"/><text x="146" y="86" class="mute">by</text>
|
||||
<path class="edge" d="M360,95 H438" marker-end="url(#ar5)"/><text x="366" y="86" class="mute">located-in</text>
|
||||
<path class="edge" d="M560,95 H638" marker-end="url(#ar5)"/><text x="590" y="86" class="mute">by</text>
|
||||
<path class="edge" d="M760,95 H858" marker-end="url(#ar5)"/><text x="770" y="138" class="mute">fixed-by, delivered,</text><text x="770" y="152" class="mute">proven · core: replay</text>
|
||||
<path class="edge dash" d="M140,105 C300,150 520,150 640,108" marker-end="url(#ar5)"/><text x="300" y="150" class="mute">straight to fixing, with by</text>
|
||||
<path class="edge dash" d="M500,120 V168" marker-end="url(#ar5)"/><text x="508" y="150" class="mute">reason</text>
|
||||
<path class="edge dash" d="M700,70 C700,30 600,30 590,30 C400,30 120,30 90,68" marker-end="url(#ar5)"/><text x="320" y="24" class="mute">stopping: back to open, with a note of branch, last commit and what is left</text>
|
||||
</svg>
|
||||
<div class="legend"><span>A worker idle for a day may be taken over without a reason; otherwise <code>take-over</code> with a reason. An issue about a condition is linked to it, so the alert names the issue.</span></div>
|
||||
</div>
|
||||
|
||||
<!-- ============ WHERE THINGS LIVE ============ -->
|
||||
<h2 id="where">Where things live</h2>
|
||||
<div class="box scroll">
|
||||
<table>
|
||||
<tr><th>What</th><th>Lives in</th><th>Written with</th><th>Never</th></tr>
|
||||
<tr><td>Decisions (ADRs) and research efforts</td><td>the knowledge base (seat <code>knowledge-base</code>)</td><td><code>reserve</code>, then <code>propose</code> the record whole</td><td>a file in hq</td></tr>
|
||||
<tr><td>Issues: status, owner, fix, links</td><td>the issue tracker</td><td><code>open</code>, <code>move</code>, <code>note</code>, <code>link</code></td><td>a status key in a report</td></tr>
|
||||
<tr><td>An issue's narrative (design or governance only)</td><td>hq <code>04-ISSUES/NNN-…/</code></td><td>a pull request</td><td>its status</td></tr>
|
||||
<tr><td>Designs, as-is and to-be</td><td>hq <code>03-DESIGN/</code>, status in frontmatter</td><td>a pull request</td><td>the two layers mixed</td></tr>
|
||||
<tr><td>Playbooks, glossary, how-we-build</td><td>hq <code>00-META/</code></td><td>a pull request</td><td>a copy in a session's memory</td></tr>
|
||||
<tr><td>The rules every session keeps</td><td>instruction sections (module <code>claude-code</code>)</td><td><code>claude_code_instructions_propose</code>, the operator's Approve</td><td>a hand-edited file</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ============ ALL PLAYBOOKS ============ -->
|
||||
<h2 id="all">All playbooks</h2>
|
||||
<p class="sub">Open a card for its steps, what it produces and how it is checked.</p>
|
||||
<div id="filters" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px"></div>
|
||||
<div class="grid" id="cards"></div>
|
||||
|
||||
<footer>Drawn from hq main f269e83 on 2026-10-19 and kept in hq beside the playbooks. The playbooks are the authority; this page is a picture of them, and a change to a playbook's steps updates it in the same pull request (not yet checked: issue 1222).</footer>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const P = [
|
||||
{n:"01",name:"Research",kind:"idea",c:"--idea",
|
||||
trig:"An idea worth investigating before it is committed to design. A symptom is not research.",
|
||||
who:"The session that heard the idea leads.",
|
||||
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Reserve a number (kind research), never pick one by hand.","Propose the effort: overview, questions, method, with links.","Research; findings in 04-findings, one version per settled set.","Try at the cheapest level that answers the question.","Write 03-domains: modules, seats, glossary words added or retired.","Write 05-direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","If the review asks changes: propose again, at most three times."],
|
||||
prod:"A research effort in the knowledge base, status active, every version kept. Nothing in hq.",
|
||||
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · report every 10 min",
|
||||
prev:["Research that measured nothing","A recommendation the operator must argue back","Efforts copied into hq"],
|
||||
check:"propose refuses rule breaks; the knowledge base reviews; records.py on every PR."},
|
||||
{n:"02",name:"Graduation & design change",kind:"idea",c:"--dec",
|
||||
trig:"A research effort concludes, or an existing design must change.",
|
||||
who:"The lead, on the larger model; the decision is recorded before the design moves.",
|
||||
steps:["Check the conclusion against mission, context and effect.","Reserve a number per decision.","Propose one record per choice that can be reversed on its own.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed.","Add the decisions, updated:, and the effort's glossary words.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
|
||||
prod:"Decision records; a to-be design with frontmatter; glossary entries; later the as-is.",
|
||||
next:"04",bounds:"One sitting of about an hour",
|
||||
prev:["A decision rewritten silently","A deferral nothing tracks","A design amended without a decision"],
|
||||
check:"propose refuses; records.py and index.py; set-wide rules raise conditions."},
|
||||
{n:"03",name:"Issues",kind:"symptom",c:"--sym",
|
||||
trig:"Something is wrong: an unenforced rule, a stated behaviour that does not happen, a silent failure.",
|
||||
who:"Anyone opens; a worker claims it by moving it.",
|
||||
steps:["Open it in the tracker: title, report; take the number it gives.","Tracker unreachable: take no number, say so.","A design or governance issue: a folder in hq with the report.","About a condition: link the condition's key.","Move to diagnosing with by, before investigating.","Note the trail; a folder issue also gets a dated diagnosis.","Move to located with located-in, a registered owner.","Move to fixing with by, before fixing.","Name fixed-by at the merge; stay fixing until proven live.","Move to resolved; a core issue gives replay or replay-none.","A design gap: run 02 and give amended-design.","Not going to be fixed: wontfix with a reason."],
|
||||
prod:"A tracker record with notes and links; for a design issue, its narrative folder in hq.",
|
||||
next:"02 · 07 · 08",bounds:"A worker idle for a day is stale",
|
||||
prev:["Numbers that collide","Two agents starting the same work","Status held in two places"],
|
||||
check:"The tracker's move refuses at the write; cycle.py checks the folders."},
|
||||
{n:"04",name:"Work breakdown & handoff",kind:"idea",c:"--dec",
|
||||
trig:"A to-be design is settled and its building is about to start.",
|
||||
who:"The lead who will run the feature, on the larger model.",
|
||||
steps:["Confirm status designed; each decision traces to a record.","An open question remains: back to research.","Set code: to the owning repositories.","Read the as-is; write it first if it does not say what is replaced.","Write the Milestones: outcome, acceptance tests, repositories.","Cut milestones by release order; fold any not usable alone.","Ask the operator to confirm the milestones.","Open one issue per repository part, linked depends-on.","Write the issue numbers into the design; flip to in-progress.","Hand each milestone to 07, one at a time, one design at a time."],
|
||||
prod:"Milestones in the design; issues per repository part; the design in-progress.",
|
||||
next:"07",bounds:"One sitting · one design built at a time · a milestone is one release",
|
||||
prev:["Building without a breakdown","Building against an undocumented as-is","Deferrals with no owning issue"],
|
||||
check:"Read by the lead and the reviewer; a check is tracked by issue 845."},
|
||||
{n:"05",name:"Constitution sync",kind:"side",c:"--side",
|
||||
trig:"how-we-build.md changed a rule the mesh enforces at runtime.",
|
||||
who:"Whoever made the change.",
|
||||
steps:["Edit how-we-build; the reasoning stays there.","Record the change as a decision (02).","Publish the derived page under its slug.","Keep the section numbering stable.","Read the page back; verify the change is there.","Note the sync in the record's consequences."],
|
||||
prod:"A decision record; the derived constitution page injected into design sessions.",
|
||||
next:"02",bounds:"no time bound",
|
||||
prev:["Drift between the reasoned and the enforced copy","A publish that said success and did nothing","Edits to the derived page that vanish"],
|
||||
check:"The read-back in step 5."},
|
||||
{n:"06",name:"Writing a module",kind:"side",c:"--side",
|
||||
trig:"Something that runs today must run on the mesh.",
|
||||
who:"Whoever ports or writes it.",
|
||||
steps:["Read what runs: compose file, environment, where data sits.","Name what it provides and requires.","Declare capabilities for machine facts.","Write resources in the order they must happen.","Every secret in a file, never in env.","Pin every image by digest.","New: generate the credential; adopted: accept it.","Prove it against the real software: auth, refusal, restart."],
|
||||
prod:"A module declaration, optionally a provisioner, and its proof.",
|
||||
next:"07",bounds:"no time bound",
|
||||
prev:["A service running with no password","A new password locking out adopted data","A test that cannot fail"],
|
||||
check:"Example tests refuse the env-file mistake; review."},
|
||||
{n:"07",name:"Features",kind:"build",c:"--build",
|
||||
trig:"Work that changes code across issues or repositories, too large for a small fix.",
|
||||
who:"A lead (larger model), one fixing agent per repository (smaller), one reviewer (larger).",
|
||||
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Find release orders; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk, push, and try it. (new)","Review all of it with the tries' results; at most 3 rounds.","Push the reviewed heads; try again only what changed.","Open one pull request per repository, together, with Reviewed-by.","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery plan to done; resolve the issues; clean up."],
|
||||
prod:"One pull request per repository, one delivery plan, resolved issues, no branch or worktree left.",
|
||||
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one try and one check",
|
||||
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show"],
|
||||
check:"The forge's merge-gate and repo-check; the review record; the tracker's move."},
|
||||
{n:"08",name:"Small fixes",kind:"build",c:"--build",
|
||||
trig:"One issue in one repository, about an hour of work.",
|
||||
who:"The session, with one reviewer on the larger model.",
|
||||
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Push and try before the review. (new)","One review of the commit and its try.","Push the reviewed head; open the pull request.","The operator's Approve; the merge; the delivery plan.","Prove the fix live and resolve the same hour."],
|
||||
prod:"One pull request, one delivery, a resolved issue.",
|
||||
next:"—",bounds:"About an hour · one try before the review, one review, one check",
|
||||
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery"],
|
||||
check:"The forge's checks; the review record; the tracker's move."},
|
||||
{n:"09",name:"Cleanup",kind:"side",c:"--side",
|
||||
trig:"The operator asks, or merged branches, stale pull requests and old worktrees pile up.",
|
||||
who:"A session on the operator's word; the operator decides every deletion.",
|
||||
steps:["Take stock, read-only.","Write one report: item, class, evidence.","Classify: safe, likely safe, kept, unknown.","Report stale pull requests; never close them.","Ask: delete the safe items as listed?","Delete only the approved list; never force.","Report what was cleaned, kept and unknown."],
|
||||
prod:"A stock report, the approved deletions, issues for missing tools.",
|
||||
next:"03",bounds:"A worktree is safe only if untouched for 2 h · a PR is stale after 3 days",
|
||||
prev:["Deleting unfinished work","Deletion by name or age alone","Missing tools left as session notes"],
|
||||
check:"The report names the evidence for each deletion."}
|
||||
];
|
||||
const kinds={all:"All",idea:"From an idea",symptom:"From a symptom",build:"Code changes",side:"Alongside"};
|
||||
let current="all";
|
||||
function esc(s){return s.replace(/&/g,"&").replace(/</g,"<")}
|
||||
function render(){
|
||||
const g=document.getElementById("cards"); g.innerHTML="";
|
||||
P.filter(p=>current==="all"||p.kind===current).forEach(p=>{
|
||||
const d=document.createElement("article"); d.className="pb"; d.style.setProperty("--c",`var(${p.c})`);
|
||||
d.innerHTML=`<div class="num">PLAYBOOK ${p.n}</div><h3>${esc(p.name)}</h3><p class="trig">${esc(p.trig)}</p>
|
||||
<div>${p.bounds.split(" · ").map(b=>`<span class="tag">${esc(b)}</span>`).join("")}</div>
|
||||
<details><summary>Steps (${p.steps.length})</summary><ol>${p.steps.map(s=>`<li>${esc(s).replace("(new)",'<span class="tag new">new</span>')}</li>`).join("")}</ol></details>
|
||||
<details><summary>Produces, prevents, checked by</summary><div class="kv">
|
||||
<b>Who</b><span>${esc(p.who)}</span><b>Produces</b><span>${esc(p.prod)}</span>
|
||||
<b>Then</b><span>${esc(p.next)}</span><b>Prevents</b><span>${p.prev.map(esc).join("; ")}</span>
|
||||
<b>Checked by</b><span>${esc(p.check)}</span></div></details>`;
|
||||
g.appendChild(d);
|
||||
});
|
||||
}
|
||||
const f=document.getElementById("filters");
|
||||
Object.entries(kinds).forEach(([k,v])=>{const b=document.createElement("button");b.type="button";b.className="chip"+(k===current?" on":"");b.textContent=v;
|
||||
b.onclick=()=>{current=k;[...f.children].forEach(x=>x.classList.remove("on"));b.classList.add("on");render()};f.appendChild(b)});
|
||||
render();
|
||||
const root=document.documentElement;
|
||||
try{const t=localStorage.getItem("pb-theme");if(t)root.dataset.theme=t}catch(e){}
|
||||
document.getElementById("theme").onclick=()=>{const dark=root.dataset.theme?root.dataset.theme==="dark":matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
root.dataset.theme=dark?"light":"dark";try{localStorage.setItem("pb-theme",root.dataset.theme)}catch(e){}};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,6 +36,7 @@ one-for-one — `mesh-catalog`, `mesh-sdk` and `mesh-tools` exist where the tabl
|
||||
| `mesh-surfaces` | 3 | tools, web, cli |
|
||||
| `mesh-sdk` | — | the stable spine modules build against — the tool-serving SDK harness, the messaging/event framework, the contracts and core primitives. Holds nothing per-module and nothing volatile (ADR 0039). |
|
||||
| `mesh-lab` | — | **exists.** The lab — scenario lifecycle, networking, placement. Ships to nobody; runs on a workstation. |
|
||||
| `mesh-delivery` | — | the delivery domain: the parts that build, check and deliver a change by watching and writing keys on the bus, nine modules each whole in it, prefixed `mesh-delivery-` (ADR 0429). Made by the forge module from its config (ADR 0428). |
|
||||
|
||||
Tier 4's shape is open, and deliberately so: see ADR 0019 and
|
||||
research 005.
|
||||
|
||||
@@ -5,6 +5,7 @@ code: [mesh-controller internal/catalogue/state.go, mesh-controller internal/bro
|
||||
updated: 2026-10-04
|
||||
decisions:
|
||||
- "ADR 0201"
|
||||
- "ADR 0439"
|
||||
---
|
||||
|
||||
# A module's state, as it runs
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
layer: as-is
|
||||
status: implemented
|
||||
code: [mesh-controller, mesh-tools]
|
||||
updated: 2026-10-16
|
||||
updated: 2026-10-19
|
||||
decisions:
|
||||
- "ADR 0106"
|
||||
- "ADR 0436"
|
||||
- "ADR 0438"
|
||||
---
|
||||
|
||||
# Logins on the bus, as they run
|
||||
@@ -25,6 +27,16 @@ Nothing marks what a tool does.** Read from the controller's and the tool runner
|
||||
- **The caller is in the address.** A call's address carries the caller's name, so the runtime takes the caller
|
||||
from the address, never from the message. For a seat every machine holds, the address also names the machine.
|
||||
|
||||
- **A module's process may have a login of its own** (ADR 0436, delivered 2026-10-19 in mesh-controller #390 and
|
||||
mesh-sdk #36). A `process` resource that declares `bus` runs as a user its module makes and gets one login per
|
||||
machine, named by machine, module and process. It may emit its declared events, take work from its module's own
|
||||
work-queue stream through the one consumer the controller makes for it, use its module's states, read the
|
||||
router's records of its module's asks, call the verbs it declares, and listen only on its own inbox. It can
|
||||
answer no call and hold no seat. `module issue` delivers its password as the module's own secret
|
||||
`broker-<process id>`, readable by that user alone. mesh-mail's sender is the first such process: it holds the
|
||||
mail passwords that the tool runner may not read, and sends each mail the operator approved, which the tools
|
||||
record as queued before they put it on its work queue (ADR 0438).
|
||||
|
||||
## What it cannot do
|
||||
|
||||
- **No expiry.** A login lives until its record is deleted and the list rewritten on the bus's node.
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-catalog modules/mesh-delivery]
|
||||
updated: 2026-10-18
|
||||
code: [mesh-controller, mesh-catalog modules/mesh-delivery, mesh-delivery, mesh-host, mesh-sdk]
|
||||
updated: 2026-10-19
|
||||
decisions:
|
||||
- "ADR 0447"
|
||||
- "ADR 0446"
|
||||
- "ADR 0445"
|
||||
- "ADR 0444"
|
||||
- "ADR 0437"
|
||||
- "ADR 0435"
|
||||
- "ADR 0434"
|
||||
- "ADR 0433"
|
||||
- "ADR 0431"
|
||||
- "ADR 0429"
|
||||
- "ADR 0428"
|
||||
- "ADR 0426"
|
||||
- "ADR 0421"
|
||||
- "ADR 0420"
|
||||
@@ -62,6 +73,48 @@ commit's journey, in what order the steps are allowed, and where each fact of it
|
||||
2026-10-16 from the decisions of research 055, which replaced the "release branches", the toolchain image, the
|
||||
controller's delivery tables and the timers that polled them.
|
||||
|
||||
## Where it goes: parts that watch and write keys
|
||||
|
||||
ADR 0429 (accepted on 2026-10-18, from research 063) takes building and delivering out of the controller. They
|
||||
become independent **parts**, each a process with its own bus credential that watches keys and writes keys:
|
||||
no part calls another, there is no plan object and no orchestrator, and each key has one writer. A delivered
|
||||
source revision is derived from the keys: every **want** equals its module's **fingerprint**, and every
|
||||
**have** equals its want and is healthy.
|
||||
|
||||
- **The forge bridge** turns the forge's events into stream messages and is the only part that calls the
|
||||
forge.
|
||||
- **The indexer** computes each module's fingerprint at the merge preview and writes the candidates of each
|
||||
group and the modules of the trunk.
|
||||
- **The builder** claims a build by creating its key, once per fingerprint, and builds it; build-agent becomes
|
||||
this part.
|
||||
- **The checker** runs the merge check on built artifacts: a change's tests, through the build module of each
|
||||
module's language and by that language's convention, running only the packages whose inputs changed and
|
||||
taking the kept verdict of the rest; it writes the change's one status (ADR 0444, ADR 0445, ADR 0447).
|
||||
- **The gate** merges a group's pull requests once check, review and the operator's Approve agree on the same
|
||||
heads, and then moves the mesh's source revision in one write.
|
||||
- **The teller** carries conditions and asks to the operator's phone and is the only writer of an ask's answer.
|
||||
- **The assigner** writes which fingerprint each node should run, by six rules: the build is done, the
|
||||
fingerprint is not held, the node is the module's **canary** or the canary runs it healthy, at most `step`
|
||||
other nodes are ahead when `step` is above zero, every provider runs healthy, and the module's credential for that fingerprint exists.
|
||||
It also runs the **composition check**: whether the machines a change touches still compose with it, read
|
||||
from the keys the indexer and the builder hold and from what the controller keeps of machines, settings and
|
||||
grants, with the composition it sends with (ADR 0446). Its module
|
||||
is made in release 4 for that check alone; its wants come in release 5.
|
||||
- **The node agent**, the node-engine's own process, applies its wants one module at a time, backs up a data
|
||||
module first, and writes its haves.
|
||||
- **The watchman** turns the bus's connections into presence and mirrors every key whose next move a machine
|
||||
owes into a **pulse**, whose expiry is a stall.
|
||||
- **The status view** is the operator's command `mesh-delivery-cli` and a web view; it reads every bucket and
|
||||
writes only its own answers to asks.
|
||||
|
||||
Nine of these are modules of the repository `mesh-delivery`, prefixed `mesh-delivery-` (ADR 0429 §2); the
|
||||
repository is made by the forge module from its config (ADR 0428; issue 1128 makes the config value reach the
|
||||
node at once). The reviewer stays the review agent's session.
|
||||
|
||||
It lands in six releases, each leaving the mesh working, each one milestone below (ADR 0429 §7). Until the
|
||||
release that replaces it is delivered, each section from here to *The bootstrap* stays this design and its rule
|
||||
(ADR 0429 §21); each is rewritten when the release that replaces it is prepared (issue 1130).
|
||||
|
||||
## The parts
|
||||
|
||||
```
|
||||
@@ -267,6 +320,58 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
|
||||
warrant for those heads in place of an approving forge review (ADR 0421 §9). A member the forge refuses is
|
||||
merged again on its fresh check while the group's batch stays open for it.
|
||||
|
||||
### The check from release 4
|
||||
|
||||
*ADR 0444, ADR 0445, ADR 0446, ADR 0447; research 058.*
|
||||
|
||||
Until release 4 is delivered, the subsections above are the rule, with their two statuses. From its delivery, a
|
||||
check is this:
|
||||
|
||||
- **One status per change.** A pull request carries one required status, `mesh/check`, written by the checker
|
||||
on its head; it passes only when the head merged onto its trunk's head passed its tests and the composition
|
||||
check. For a member of a delivery group it is the group's one verdict, the same on every member's head: every
|
||||
member's tests, read beside the others' heads, and one composition of all members' changes. It replaces
|
||||
`mesh/merge-gate` and `mesh/repo-check`; the forge requires it, the checker writes it and the gate reads it from
|
||||
the same delivery (ADR 0447). `mesh/delivery-group` stays the delivery's view, not a required check. Wherever
|
||||
this design says both checks passed, they read this one status.
|
||||
- **What the status says.** Its line says the verdict, the tests run and kept at each level, the machines
|
||||
composed, and how long each part took: waiting in the queue, cloning, the unit level, the integration level's
|
||||
setup and its running, and the composition. A failure names the test or the machine first. The line links to
|
||||
the check's full report, which carries the delivery plan's line too (ADR 0447 §3).
|
||||
- **Tests by the language's convention.** The checker runs a change's tests through the build module of each
|
||||
module's language (milestone 6's build modules, ADR 0405), with no manifest field and no script per
|
||||
repository: in Go, `go test` is the unit level and the `integration` build tag the other, so the two levels
|
||||
are apart by construction; in Node and TypeScript, the type-check and the package's `test` script, and its
|
||||
`test:integration` script when it has one. A repository with no tests has none, and says so. Each level is
|
||||
judged by its own bounds: the unit level by its running time (five seconds), the integration level by its
|
||||
setup (thirty seconds) and its tests' running time (sixty seconds), measured and judged apart. A
|
||||
repository's `merge-check.sh` goes; hq, which builds no module, keeps its script until issue 1296 settles how
|
||||
it is checked (ADR 0445).
|
||||
- **Only what changed runs.** The build module names, from the toolchain, everything each package's tests read,
|
||||
and the checker keeps a package's passing verdict at each level under the digest of those inputs. A check
|
||||
runs a package only when no passing verdict is kept under its digest; a check asked again after a failure
|
||||
runs what failed and what changed, never the whole suite; the change's own new and changed tests run first,
|
||||
and the check stops at their first failure, before the rest and before the composition. A kept verdict is
|
||||
written once, by the checker's credential alone, from the result it read, and is addressed by its digest;
|
||||
each check builds with a cache of its own, so one pull request cannot poison another's verdict (ADR 0444,
|
||||
issue 482). With every check asked again on a trunk move (ADR 0395), such a re-ask runs only what the move
|
||||
changed.
|
||||
- **The composition check is the assigner's.** Whether the machines still compose with a change is checked by
|
||||
the part that owns what each machine should run. The assigner reads the modules, fingerprints and manifests
|
||||
the indexer holds and the builds the builder holds; the machines, their roles, assignments and settings, and
|
||||
the grants and credentials it reads from the controller's facts snapshot and from the controller's buckets it
|
||||
names in its manifest's `reads` (ADR 0237 §1, ADR 0408 §3). It clones no repository but at most the change's
|
||||
own, and
|
||||
composes only the machines that run or could run a module the change moves or adds, or whose grants or
|
||||
providers it alters; for a group, once over every member's change, read from the indexer's candidates of the
|
||||
group, and while the indexer holds none the group's check waits and says why. It composes with the function the
|
||||
controller sends with, moved into mesh-sdk, so the two agree for one mesh-sdk version; its verdict names the
|
||||
mesh-sdk version it composed with, and while the assigner and the controller run different versions the
|
||||
controller's self-check raises a condition until they agree. No judge is built from a change: a change to the
|
||||
composition is proved by its own tests, a field only a newer composition reads goes out as optional first, and
|
||||
the running composition refuses a manifest that uses a field it does not read, naming the field. What it fails on, and its warning past twelve rebuilt modules, are today's merge
|
||||
gate's (ADR 0446, ADR 0237 §2).
|
||||
|
||||
## The Approve and the merge
|
||||
|
||||
*ADR 0421, ADR 0398 §3, ADR 0406 §1.*
|
||||
@@ -404,11 +509,13 @@ delivery time.
|
||||
image and its module retire.
|
||||
- **Every builder runs the same toolchain, and says so.** Each builder names its toolchain versions with every
|
||||
build outcome, and the controller's self-check raises a condition while two builders differ.
|
||||
- **Builds and tests run directly**, as the build seat's own account on its node, in no container. A
|
||||
repository's `# mesh-check-toolchain` line selects which installed toolchain runs its check.
|
||||
- **A judge has an account of its own.** A judge built from a sibling's merge (ADR 0382 §3) is built and run as a
|
||||
second account of the Go build module, whose home, configuration and cache the build seat's account cannot write,
|
||||
so a check's script cannot change it.
|
||||
- **Builds and tests run directly**, as the build seat's own account on its node, in no container. The
|
||||
build module of each module's language runs its tests by that language's convention; no repository's
|
||||
script or toolchain line decides it, hq's alone excepted until issue 1296 (ADR 0445).
|
||||
- **A judge has an account of its own, until release 4 is delivered.** Until then, a judge built from a sibling's
|
||||
merge (ADR 0382 §3) is built and run as a second account of the Go build module, whose home, configuration and
|
||||
cache the build seat's account cannot write, so a check's script cannot change it. From release 4 no judge is
|
||||
built from a change and the account is not made (ADR 0446 §5).
|
||||
- **Every dependency comes by the version its lock file names**, checked against its checksum: the mesh's own
|
||||
code from the mesh's forge, its own packages from its package registry, any other from its public source.
|
||||
- **A builder fetches from the forge over SSH with a key of its own**, as the build seat's account, under a forge
|
||||
@@ -525,6 +632,42 @@ What the build proves before this design is implemented (research 055's directio
|
||||
with no person's release and nothing held in mesh-delivery.
|
||||
12. No table in the controller's store holds builds, rollouts or assignments.
|
||||
|
||||
From ADR 0429 (research 063's done-when, and the live proof each of its releases names):
|
||||
|
||||
13. The status view shows every module on every node from the have keys, and agrees with the controller's own
|
||||
read of the nodes' reports.
|
||||
14. A part stopped by hand shows stuck within two minutes, and no part holds a timer.
|
||||
15. A group of two repositories merges as one source revision, and every module's fingerprint the indexer
|
||||
writes equals the controller's source fingerprint.
|
||||
16. A merge builds nothing its check built, and a hotfix's build waits for no other build.
|
||||
17. A day of deliveries shows no difference between the assigner's wants and what the controller sent that the
|
||||
six rules do not explain.
|
||||
18. On the live mesh a milestone of three repositories reaches every node through the keys alone (research 063's
|
||||
scenario A), and the assigner replaced in the middle of it resumes with nothing lost or repeated (scenario
|
||||
D, once).
|
||||
19. Plans, batches, the merge window, tiers, the plans hold and node declaration holds, the push of whole
|
||||
declarations, their retries, takeovers and healers, and the node-engine's handling of whole declarations are
|
||||
deleted from the code, and no verb that starts, stops, retries or closes a plan remains.
|
||||
20. Each release's pull request reports the controller's code lines against research 063's baseline (111,386
|
||||
lines on 2026-10-18).
|
||||
|
||||
From ADR 0444 to ADR 0447 (research 058's conclusion: a small fix delivered within four minutes after
|
||||
development, the check's share about a minute):
|
||||
|
||||
21. A leaf fix's check takes about a minute, measured as research 058 measured it: the check's log from its
|
||||
first line to its verdict, plus the time from the change's announcement to its verdict in the delivery,
|
||||
each part's time as the status's line says it.
|
||||
22. A change that adds a failing test is answered failed with only that test's package run; a check asked again
|
||||
after one failed package runs that package alone; a check whose inputs equal an earlier one's runs nothing
|
||||
and takes every kept verdict. Measured on the status's line: the tests run and kept at each level, and the
|
||||
failed test it names.
|
||||
23. A library or catalogue change's composition check composes only the machines it touches, and clones no
|
||||
repository but at most the change's own. Measured in the check's report: the machines composed, against the
|
||||
machines that run or could run a module the change moves or adds, and the repositories cloned.
|
||||
24. A pull request carries one required status, and a delivery group one verdict on every member's head, its
|
||||
line naming what was proved and each part's time; no repository a module is built from holds a
|
||||
`merge-check.sh`.
|
||||
|
||||
## What retires
|
||||
|
||||
- The "release branches" and the controller's judge of them (ADR 0358, superseded by ADR 0401).
|
||||
@@ -541,11 +684,20 @@ What the build proves before this design is implemented (research 055's directio
|
||||
group's merges (ADR 0418 §1, §5, §6).
|
||||
- The controller's tables of builds, build requests, rollouts and their tiers, batches and merges, and
|
||||
assignments; the record mirrors, probe D17's audit and healer H6 for those kinds (ADR 0408).
|
||||
- At release 4's delivery: `merge-check.sh` and its `# mesh-check-toolchain` line in every repository a module is
|
||||
built from, hq's alone kept until issue 1296 (ADR 0445 §6); the controller's merge gate, composing every
|
||||
machine from clones and throwaway stores (ADR 0446 §1, §6); the judge built from a change and the Go build
|
||||
module's second account for it (ADR 0446 §5); and the two statuses `mesh/merge-gate` and `mesh/repo-check`
|
||||
(ADR 0447 §1).
|
||||
|
||||
## Milestones
|
||||
|
||||
Confirmed by the operator on 2026-10-17 (playbook 04). Each is one release of playbook 07; the next starts
|
||||
when the previous one's delivery is done.
|
||||
Milestones 1 to 6 were confirmed by the operator on 2026-10-17 (playbook 04), and 5b on 2026-10-18. Releases 1
|
||||
to 6 of ADR 0429 follow milestone 5b in its order, releases 1 and 2 first confirmed as one milestone (ADR 0429 §7 allows it:
|
||||
they need nothing of each other) and then split by their release orders into R1a to R2d (below), and milestone 6 folded into release 4: confirmed by the operator on 2026-10-18
|
||||
(playbook 04 step 5). The issues of R1a to R2d are opened; each later release's are opened when it is prepared. Each milestone is one release of playbook 07; the next starts when the
|
||||
previous one's delivery is done, and a release's clauses of ADR 0429 hold from its delivery (§21). Every
|
||||
release reports the controller's code lines against research 063's baseline (acceptance test 20).
|
||||
|
||||
| # | Outcome | Acceptance tests | Repositories | Issues |
|
||||
|---|---|---|---|---|
|
||||
@@ -556,10 +708,24 @@ when the previous one's delivery is done.
|
||||
| 4 | A delivery backs up the data it moves first, and a failed backup holds it and is said | 3 | mesh-controller, mesh-catalog (mesh-delivery, the backup seat's holder) | 980, split into 1065, 1066, 1067, 1068 |
|
||||
| 3 fix | A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories | 1 | mesh-catalog (mesh-delivery, gitea) | 1082, 1049, 1047, 1083, 1079 (1084 closed as its duplicate) |
|
||||
| 5 | One read of a module on a node names the commit, build and rollout that put it there | 10, 12 | mesh-controller, mesh-catalog (mesh-config, nats) | 981 |
|
||||
| 5b | The controller makes no build or deploy plan: building and delivering a change are done outside it, driven by events on the bus, and the controller receives only what each node should run | — (its decision names them) | mesh-controller, mesh-catalog (mesh-delivery) | 1117 |
|
||||
| 6 | Builds use installed toolchains with no container, builders fetch over SSH with their own key, and an SDK change ships in one group with its users | 2, 4 | mesh-catalog (build modules, build-agent, ssh-client, gitea), mesh-controller, mesh-sdk | 979, 971 |
|
||||
| 5b | Decided: building and delivering leave the controller for parts that watch and write keys in the repository `mesh-delivery`, landing in six releases (ADR 0429); the forge module makes that repository from its config, protected before any team reaches it (ADR 0428) | — (ADR 0429 names them: 13 to 20) | hq, mesh-catalog (gitea) | 1117, 1124, 1128 |
|
||||
| 6 | Folded into release 4 by ADR 0429 §10: installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once | (2, 4: proved by release 4) | — | 979, 971 (moved to release 4) |
|
||||
| R1a | The node-engine, the controller and the parts can read one layout of the have, presence, pulse and stuck keys: mesh-sdk ships it before its users | — (13, 14 need it) | mesh-sdk | 1131 (key layouts) |
|
||||
| R1b | Every node writes what it runs of each module, on its own keys and nobody else's, and the bus says to one part when a connection opens and closes | 13 in part | the node-engine (`mesh-host`), mesh-controller | 1132 (have keys), 1142 (the node-engine's check, its tests half), 1136 (the have bucket and its grants), 1145 (a module and a build per resource), 1146 (a new repository's first module registered at its merge's Approve), 1147 (the bus's connect events for the watchman), 1160 (a plan passes a machine silent at a middle tier), 1162 (the have bucket takes a node's facts) |
|
||||
| R1c | The operator sees every module on every node, with its build, phase and health, in `mesh-delivery-cli` (aliased `nox-delivery`) and its web view | 13 | mesh-delivery (view) | 1134 (the view), 1137 (the alias, in the view's manifest) |
|
||||
| R2a | The watchman and the controller can read one layout of the stuck keys: mesh-sdk ships it before its users | — (14 needs it) | mesh-sdk | 1153 (the stuck keys) |
|
||||
| R2b | A part, node or check that stops moving is raised as a condition within two minutes, with no timer in a part | 14 | mesh-delivery (watchman), mesh-controller | 1135 (the watchman), 1154 (a bucket whose keys expire), 1155 (running checks readable, with their start), 1156 (stuck keys raised; the silent check from presence), 1157 (a refused send key raised), 1159 (a long check is not stuck) |
|
||||
| R2c | The controller says when it last heard each machine from presence alone, and no longer hears the alive message | 14 | mesh-controller | 1183 (every reader of last heard takes presence; the controller's alive side deleted) |
|
||||
| R2d | The node-engine sends no alive message | 14 | the node-engine (`mesh-host`) | 1158 (the node-engine's alive side deleted) |
|
||||
| R3a | The gate, the bridge, the indexer and the teller can read one layout of the source revision, candidates, modules, merges, asks, answers, owed acts, forge messages and conditions, and one function computes the source fingerprint: mesh-sdk ships them before their users (ADR 0437) | — (15 needs it) | mesh-sdk | 1200 (source revision, candidates, modules, forge messages), 1201 (merges, asks, owed acts, answers), 1202 (a condition on its one subject), 1203 (the source fingerprint, proven against three fingerprints the live builder recorded); ahead of it in mesh-controller, so those could be read: 1204 and 1208 (`builds` says each build's source fingerprint) |
|
||||
| R3b | The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437) | — | mesh-controller, mesh-catalog (messenger) | 1210 (the controller's own path and the merge gate's rule by the seat's claim), 1211 (a merge's announcement heard from the seat's holder), 1212 (the pull requests' keys read from the holder's bucket), 1213 (a merge's warrant read under the asker the announcement names: the teller, or mesh-delivery until R3d), 1214 (the builder computes the source fingerprint with mesh-sdk's function), 1215 (a build record carries its fingerprint's inputs), 1216 (a module may say a condition only under its own name, proven on the controller's grant); in the messenger: 1217 (the router takes a condition on its one subject), 1218 (the same over the bus); one delivery group. Its prepare step found that the controller hears no `transition` or `group` event, and that the router cannot refuse a condition under another module's name, the grant does: both go into R3c's insight on ADR 0437 with 1209 |
|
||||
| R3c | The forge bridge, the indexer and the teller run beside today's mesh-delivery and write their keys; the view shows modules against the controller's source fingerprints; nothing is merged by them yet (ADR 0437) | 15 in part | mesh-delivery (forge bridge, indexer, teller, view), mesh-catalog (the forge module) | 1235 (the forge bridge writes pull messages on FORGE), 1236 (its merged message, with the trees of the merge commit), 1237 (its messages for the pull requests open at its start and each reconnect), 1238 (the indexer writes the candidate groups), 1239 (the indexer writes the modules' fingerprints at a trunk merge, and 1209: none for an image that compiles a Go program from a context), 1240 (the teller runs and carries nothing yet), 1241 (the view shows the candidate groups and each module's fingerprint against the controller's); in the forge module: 1234 (a merge commit's trees in one call, and the head branch in the open pull requests); one delivery group, mesh-catalog first. Its prepare step found three orders: the forge had no tool for a commit's trees, so the forge module joins the milestone as a member of its group; and mesh-sdk shipped two layouts before it, the controller's build record (1223) and a build's directory and a pull request's module directories (1233). It also found that the bridge's FORGE is a stream the others hear by `consumes`, not a bucket they `read`; that the broker test of the three credentials goes into R3d with the gate's (1243); and that the parts hand-write their bus connection until mesh-sdk gives one (1242): facts for ADR 0437's next insight, with R3d's |
|
||||
| R3d | A group reaches the mesh as one source revision written by the gate; the merge's Approve is asked by the teller and answered on the phone until issue 953; the view shows the open asks; today's module mesh-delivery is carried over into the gate and ends (ADR 0437) | 15 | mesh-delivery (gate, forge bridge, teller), mesh-catalog (mesh-delivery removed, mesh-issues) | opened when it is prepared |
|
||||
| R4 | The controller asks no build: a build is keyed by its fingerprint, so a merge builds nothing its check built; with milestone 6, toolchains are installed, builders fetch over SSH, and an SDK change ships with its users; a check runs only what changed, its tests by the language's build module, its composition by the assigner, and says in one status what it proved and how long each part took (ADR 0444 to ADR 0447) | 16, 2, 4, 21 to 24 | mesh-delivery (builder, checker, assigner's composition check), mesh-controller (build dispatch and merge gate deleted; build-agent leaves), mesh-catalog (build modules, ssh-client, gitea), mesh-sdk (the composition), the node-engine (`mesh-host`) and mesh-tools (scripts deleted), hq | 979, 971; the check: 1287, 1288, 1289 (kept verdicts, the change's own tests first), 1290, 1291, 1292 (the build modules run the tests), 1293, 1294, 1295 (`merge-check.sh` goes), 1296 (how hq is checked: the operator's), 1297, 1298 (the assigner's composition check), 1299 (the controller's merge gate deleted), 1300, 1301, 1302 (one status), 1303 (the word merge gate struck); the rest opened when it is prepared |
|
||||
| R5 | The assigner writes what each node should run by its six rules, compared with what the controller sends, and the view shows every difference; the operator answers the asks that need no proof of who answered in the view (ADR 0437 §10) | 17 | mesh-delivery (assigner), mesh-controller (grants keys) | opened when it is prepared |
|
||||
| R6 | Each node applies from its wants, a new version reaches the module's canary first and the rest once it is healthy there, and the plans are deleted from the code | 18, 19 | the node-engine (`mesh-host`: the node agent), mesh-controller (plans deleted), mesh-delivery (the gate's delivery states go) | opened when it is prepared |
|
||||
|
||||
The issues are linked `depends-on` in this order; 976 depends on 975, and 979 also on 971. Milestone 3's first live group (milestone 4's, on 2026-10-17) held one Approve but merged in two passes and went out in two delivery plans: a member's tests read the other member's repository, so the first merge made the second's verdict out of date, and a closed group never asked its composed verdict again (issues 1049, 1082). The operator put a fix of milestone 3 after milestone 4 and before milestone 5 on 2026-10-18; acceptance test 1 is proved again by it. The operator put milestone 5b after milestone 5 and before milestone 6 on 2026-10-18, as an issue rather than a research effort; where the planning goes is settled by a decision record before it is built (issue 1117), and milestone 6's build changes are read against it. Milestone 2b was
|
||||
The issues are linked `depends-on` in this order; 976 depends on 975, and 979 also on 971. R3's prepare step on 2026-10-19 found ADR 0429 §20 contradicting itself and four release orders; ADR 0437 (accepted, the operator's Approve) cuts release 3 into R3a to R3d, each one release, and moves answering asks in the view to R5. R2c's prepare step on 2026-10-19 found one more order: the alive message is the only source of when the controller last heard a machine, which `nodes`, `node` and `status` read, so the controller takes that from presence (1183, R2c) and is delivered before the node-engine stops sending (1158, R2d); 1158 depends on 1183. In release 1-2, 1132 and 1136 depend on 1131, 1134 and 1135 on 1136, 1134 also on 1132 and 1128, and 1137 on 1134. Release 1-2 was confirmed as one milestone; its prepare step on 2026-10-18 found the release orders hidden in it, and the operator chose at 18:52 to cut it by them, so it is the milestones R1a to R2d above, each one release (the operator, 2026-10-19: a milestone is one release): an SDK change still ships before its users until release 4 (ADR 0429 §10); a new key on the wire goes only to a node that says it reads it; a new repository's first module registers at its merge's Approve (ADR 0432, issue 1146); the bus gives the watchman connect events only through a system account (ADR 0431); and release 2's other gaps are settled by ADR 0433 and ADR 0434. A node that is down has no effect on a delivery and plays what it missed when it returns (ADR 0435, the operator, 2026-10-18; issues 1160, 1161). Milestone 3's first live group (milestone 4's, on 2026-10-17) held one Approve but merged in two passes and went out in two delivery plans: a member's tests read the other member's repository, so the first merge made the second's verdict out of date, and a closed group never asked its composed verdict again (issues 1049, 1082). The operator put a fix of milestone 3 after milestone 4 and before milestone 5 on 2026-10-18; acceptance test 1 is proved again by it. The operator put milestone 5b after milestone 5 and before milestone 6 on 2026-10-18, as an issue rather than a research effort; where the planning goes is settled by a decision record before it is built (issue 1117), and milestone 6's build changes are read against it. Milestone 2b was
|
||||
put before milestone 3 by the operator on 2026-10-17. Milestone 3 builds ADR 0418, ADR 0419 and ADR 0421
|
||||
(issues 977, 978 and 1022), and splits mesh-controller's unit tests from its integration tests (issue 895, the
|
||||
operator on 2026-10-17), so the unit level holds its bound of ADR 0391; the integration level's sixty seconds
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh]
|
||||
updated: 2026-10-11
|
||||
updated: 2026-10-18
|
||||
decisions:
|
||||
- "ADR 0429"
|
||||
- "ADR 0244"
|
||||
- "ADR 0006"
|
||||
- "ADR 0008"
|
||||
@@ -31,7 +32,7 @@ shape — what each is for, where its records live, and how they depend on one a
|
||||
| **Placement** | to decide, send and apply what each node runs, and say why | the controller's `inventory` database |
|
||||
| **Provisioning** | to resolve which module serves a provision for which consumer, and wire the two | the controller |
|
||||
| **Identity and access** | to issue, hold, rotate and check credentials and grants | the controller's `identity` and `licences` databases, and the vault |
|
||||
| **Change and delivery** | to take one commit from its pull request to every node that should run it, and put it back when it fails | the `mesh-delivery` module, and the controller's planner and rollout |
|
||||
| **Delivery** | to take one commit from its pull request to every node that should run it, and put it back when it fails | the `mesh-delivery` module and the controller's planner and rollout today; from each release of ADR 0429 on, the parts of the repository `mesh-delivery` it builds |
|
||||
| **Health and repair** | to notice what is wrong, say it once, repair what may be repaired unattended, and record what was done by hand | the controller's condition store |
|
||||
| **Data** | to know every item of data a module holds, how precious it is, and keep it recoverable | the manifests' data declarations, and every node's `node-backup` seat |
|
||||
| **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller |
|
||||
@@ -61,7 +62,7 @@ a change of meaning upstream ripples down, never up. Arrows point downstream.
|
||||
+-------------> Health and repair <-------------------+
|
||||
| |
|
||||
v v
|
||||
Change and delivery Operator and conversation
|
||||
Delivery Operator and conversation
|
||||
| ^
|
||||
+-------------------+
|
||||
(a held delivery waits for a person's word)
|
||||
@@ -70,7 +71,7 @@ a change of meaning upstream ripples down, never up. Arrows point downstream.
|
||||
Media stands beside the diagram, below Module alone: its words are about what a person watches, not
|
||||
about the mesh's machinery, and no other domain reads them.
|
||||
|
||||
Change and delivery sits low because it reads health and data; in time it runs first, and asks Placement
|
||||
Delivery sits low because it reads health and data; in time it runs first, and asks Placement
|
||||
to send. An authorising answer acts in whichever domain asked, through that domain's own verb: the
|
||||
conversation owns the asking, never the act.
|
||||
|
||||
@@ -84,7 +85,7 @@ ADR 0006 named seven contexts of the controller. Each is a domain now, under the
|
||||
| config | split: settings to Module and Placement, secrets to Identity and access |
|
||||
| connectivity | Connectivity |
|
||||
| provisioning | Provisioning |
|
||||
| delivery | Change and delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239) |
|
||||
| delivery | Delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239); named so by ADR 0429 §2, its parts living in the repository `mesh-delivery` |
|
||||
| observability | Health and repair: the mesh built conditions, not "alerts", and half of the domain is repair |
|
||||
| identity | Identity and access, with the licences store the seven did not name |
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog]
|
||||
updated: 2026-10-19
|
||||
decisions:
|
||||
- "ADR 0440"
|
||||
- "ADR 0378"
|
||||
- "ADR 0379"
|
||||
- "ADR 0359"
|
||||
- "ADR 0361"
|
||||
- "ADR 0138"
|
||||
- "ADR 0167"
|
||||
- "ADR 0439"
|
||||
- "ADR 0201"
|
||||
- "ADR 0436"
|
||||
---
|
||||
|
||||
# 59 — The mail web client
|
||||
|
||||
**The operator's mail, every account of it, in a browser on the mesh's own machines. A module of its own,
|
||||
`mesh-mail-web`, holds the seat `mail-webclient`; it renders pages on the server in Go and reaches mail only through
|
||||
`mail-store`'s verbs, under a caller the operator grants account by account. Its route reaches the private network
|
||||
only, under its internal name, and the operator signs in with Keycloak. A mail written there goes out only on the
|
||||
operator's Approve, as every mail the mesh sends does. Once it works on the laptop and the workstation, Betterbird is removed.**
|
||||
(ADR 0440, building on ADR 0378 §5 and ADR 0379 §2.)
|
||||
|
||||
**Acceptance.** In a browser on the laptop and on the workstation, the operator signs in, browses, searches and reads the mail of
|
||||
every account `mesh-mail` holds, opens an attachment, and writes a mail that goes out on their Approve on Telegram;
|
||||
the same page is not reachable from a machine outside the private network.
|
||||
|
||||
## 1. The parts
|
||||
|
||||
```
|
||||
browser (laptop, workstation) ── https://mail.<node>.internal ──▶ route proxy (internal reach only, the mesh's own certificate)
|
||||
│
|
||||
▼
|
||||
mesh-mail-web (seat mail-webclient, once for the mesh)
|
||||
│ sign-in: oidc-client ──▶ Keycloak
|
||||
│ pages rendered on the server, a mail shown as text
|
||||
▼ over the bus, as caller <machine>.mesh-mail-web
|
||||
mail-store (mesh-mail): mail_accounts · mail_search · mail_read
|
||||
mail_send ──▶ the operator's Approve (Telegram)
|
||||
mail_send_status
|
||||
mesh-mail's account bucket (ADR 0439 §2): folders and their counts
|
||||
```
|
||||
|
||||
- **`mesh-mail-web`** is built from `mesh-mail`'s source, as `mesh-mail-notifier` is, and is assigned to the machine
|
||||
that runs `mesh-mail` (the control-node today). It keeps no mail: no database, nothing on disk (ADR 0440 §1, §4).
|
||||
- **`mesh-mail`** gains its account bucket (ADR 0439 §2, issue 1255) and a caller check on `mail_send_status`
|
||||
(ADR 0440 §7, issue 1261), both released with `mesh-mail-web`. It
|
||||
answers `mesh-mail-web` only for the accounts its trusted setting `grants` gives `mesh-mail-web` (ADR 0359 §4,
|
||||
ADR 0378 §13). `mail_send_status` becomes part of `mail-store`'s protocol (ADR 0440 §7).
|
||||
- **The browser never reaches the bus.** The bus has no WebSocket listener or sign-in for a person yet (research 036,
|
||||
gap G1), so every page is HTML from `mesh-mail-web` (ADR 0440 §5).
|
||||
|
||||
## 2. Reachable from the private network only (ADR 0440 §2)
|
||||
|
||||
The operator's rule of 2026-10-15: a web UI for mail is reached only over the mesh's private network, never from
|
||||
outside. Two things hold it:
|
||||
|
||||
1. **Its route's reach is `internal`** (ADR 0138 §7). Reach is a setting of the machine a module runs on, keyed by
|
||||
port, and with none the route proxy composes a public name as well as the internal one. So `mesh-mail-web`'s
|
||||
`reach` is set to `internal` on its machine before it is assigned there. The route proxy then composes only
|
||||
`mail.<node>.internal`, serves it only to the mesh's own machines (ADR 0167 §6), and certifies it with the mesh's
|
||||
own authority.
|
||||
2. **The module answers only its internal name.** A request whose host is not `mail.<node>.internal` is answered
|
||||
404, so a public name composed by mistake shows nothing.
|
||||
|
||||
Measured on 2026-10-19: the laptop and the workstation hold the mesh's authority (`mesh-internal-ca.crt`) as a trust anchor, and
|
||||
Firefox on both reads the system's trust through p11-kit, so the page opens there without a warning. Away from the
|
||||
private network (the laptop travelling without its tunnel) the page is unreachable; Betterbird stays the offline client
|
||||
until it is removed (§6).
|
||||
|
||||
## 3. Signing in (ADR 0440 §3)
|
||||
|
||||
`mesh-mail-web` requires `oidc-client` from Keycloak, as grafana does, and uses the authorisation-code flow. It answers
|
||||
no page, attachment or send to a browser that has not signed in, and lets in only the identities its trusted setting
|
||||
`operators` names (nobody by default). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict` and ends
|
||||
after 12 hours. Every form that changes something (today only the send) carries a token bound to the session.
|
||||
|
||||
The operator chose this on 2026-10-19: without it, any process on any mesh machine, agents included, could read every
|
||||
account through the page and get around `grants`.
|
||||
|
||||
## 4. The pages
|
||||
|
||||
| page | what it shows | how |
|
||||
|---|---|---|
|
||||
| accounts | every granted account, its state, its folders and their counts | `mail_accounts`; the folders from `mesh-mail`'s account bucket, only of an account `mail_accounts` answered (the bucket's read is wider than the grant, ADR 0440 §4) |
|
||||
| folder | one folder's mails, newest first, in pages of 50 | `mail_search` with the folder; the next page asks for mails before the last one shown |
|
||||
| search | mails across every granted account by words, sender, recipient, date, attachment | `mail_search`'s filters (issues 1228-1232) |
|
||||
| mail | one mail: its headers, its body as escaped text, its attachments | `mail_read` |
|
||||
| attachment | one attachment, as a download | `mail_read` with the attachment's key |
|
||||
| write | from (a granted account), to, subject, body; after sending, the Approve's state | `mail_send`, then `mail_send_status` |
|
||||
|
||||
- **A mail is shown as text** (ADR 0440 §5): `mail_read` turns HTML into text, the page escapes it, and nothing
|
||||
remote is loaded. An attachment answers `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`.
|
||||
Every page carries a content security policy that allows no script or style from elsewhere.
|
||||
- **A send is an ask** (ADR 0361 §2): the page says the ask is raised, then follows `mail_send_status` (ADR 0436
|
||||
§12's states, now part of `mail-store`'s protocol, ADR 0440 §7) until a final one: sent, failed, refused, not
|
||||
proven, declined, expired or unknown. The operator's Approve comes on Telegram, as for every mail the mesh sends.
|
||||
- **The seat's verbs** (ADR 0378 §5): `open` answers the page's address, and with an account and a mail that mail's
|
||||
page; `status` answers whether it serves and which accounts it is granted.
|
||||
|
||||
## 5. What the first version leaves out
|
||||
|
||||
Each is an issue with an owner (ADR 0440 §8):
|
||||
|
||||
- a reply, cc, bcc and attachments on a sent mail: issue 1257 (it needs `mail_send` to take them first);
|
||||
- a page that shows new mail without a reload, from ADR 0439's buckets: issue 1258, after issue 1255;
|
||||
- the agent that searches, triages and drafts (ADR 0378 §5): issue 1259, which needs a decision first;
|
||||
- the work account: a later decision of its own (ADR 0359 §7).
|
||||
|
||||
## 6. The build's acceptance tests
|
||||
|
||||
1. A request without a session, for a page, an attachment or a send, is redirected to sign-in; an identity not in
|
||||
`operators` is answered 403; a send form without its token is answered 403; the session cookie is `Secure`,
|
||||
`HttpOnly` and `SameSite=Strict` and ends 12 hours after sign-in.
|
||||
2. A request whose host is not the internal name is answered 404.
|
||||
3. Against a stand-in `mail-store` and a filled account bucket on the real bus, the pages call only
|
||||
`mail_accounts`, `mail_search`, `mail_read`, `mail_send` and `mail_send_status`, list folders from the bucket,
|
||||
page by date, and a send shows its state; the manifest declares no database and no writable directory.
|
||||
4. A mail whose body holds `<script>` and a remote image is shown as text; an attachment is a download with
|
||||
`nosniff`; every page carries a content security policy allowing only its own origin.
|
||||
5. After a mail is indexed, `mesh-mail`'s account bucket changes only the keys that describe it, within ADR 0439's
|
||||
bounds (issue 1255's own tests).
|
||||
6. The declaration for its machine composes `mesh-mail-web`'s route with its internal name only (`mesh-controller.plan`).
|
||||
7. `mail_send_status` answers each state of ADR 0436 §12, and `unknown` for an id it does not know and for another
|
||||
caller's send (a unit test of `mesh-mail`, issue 1261).
|
||||
8. The seat's verbs: `open` answers the page's address, and with an account and a mail that mail's page; `status`
|
||||
answers whether it serves and the accounts it is granted (a unit test of `mesh-mail-web`, issue 1262).
|
||||
9. The acceptance sentence at the top, live: in a browser on the laptop and on the workstation, for every account.
|
||||
|
||||
Tests 1 to 4 are one integration test that starts the module whole against the real bus (playbook 07 step 3 item 4);
|
||||
tests 5 and 7 are `mesh-mail`'s own and test 8 `mesh-mail-web`'s, unit tests; test 6 is read before the module is
|
||||
assigned; test 9 is proven live after the delivery.
|
||||
|
||||
## 7. Milestones
|
||||
|
||||
| # | Outcome, in one sentence | Proves | Repositories and their parts | Depends on |
|
||||
|---|---|---|---|---|
|
||||
| 1 | In a browser on the laptop and the workstation, the operator signs in and browses, searches and reads every account's mail with its attachments, and writes a mail that goes out on their Approve | tests 1 to 9 | mesh-catalog: `mesh-mail`'s account bucket (issue 1255, ADR 0439 §2); the module `mesh-mail-web` (manifest, sign-in, the pages, the send, the seat's verbs, the integration test) | — |
|
||||
| 2 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 1 |
|
||||
|
||||
**Confirmed by the operator on 2026-10-19.** Milestone 1's issues, one repository part each, in their order:
|
||||
1255 (`mesh-mail`'s account bucket), 1261 (`mail_send_status` answers only its caller), 1262 (the module), 1263
|
||||
(sign-in), 1264 (accounts, folders and mails), 1265 (search), 1266 (a mail and its attachments), 1267 (writing a
|
||||
mail), 1268 (the integration test). Milestone 2 is issue 806.
|
||||
|
||||
After milestone 1's delivery and before its live proof, four values are set, each on the operator's Approve:
|
||||
`mesh-mail-web`'s `reach` on its machine (`internal`), then its assignment there, its `operators`, and its caller in
|
||||
`mesh-mail`'s `grants` for every account.
|
||||
@@ -58,6 +58,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 |
|
||||
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
|
||||
| [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 |
|
||||
| [`59-the-mail-web-client.md`](59-the-mail-web-client.md) | **In progress.** The operator's mail, every account, in a browser on the mesh's own machines: a module of its own, `mesh-mail-web`, holds `mail-webclient`, renders pages on the server and reads and sends only through `mail-store`'s verbs under a caller granted per account; its route reaches the private network only, the operator signs in with Keycloak, and a mail goes out only on the operator's Approve; then Betterbird is removed | ADR 0440, ADR 0378, ADR 0379 |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
@@ -55,7 +55,10 @@ in running prose; a quotation keeps its words.
|
||||
## Ground rules
|
||||
|
||||
- **Markdown only** — but for the checks in `00-META/checks/` and `merge-check.sh` at the root, which
|
||||
runs them on every pull request as its `mesh/repo-check` (ADR 0238).
|
||||
runs them on every pull request as its `mesh/repo-check` (ADR 0238), and
|
||||
`00-META/process/playbooks.html`, a drawn overview of the playbooks kept for a later docs site (the operator,
|
||||
2026-10-19); a change to a playbook's steps updates it in the same pull request. Nothing checks that
|
||||
yet: issue 1222 asks for the check.
|
||||
No new top-level folders without explicit confirmation.
|
||||
- **Status lives in YAML frontmatter** — on design docs (`layer`, `status`, `code`, `updated`,
|
||||
and `decisions` as "ADR NNNN"). A decision record's and a research effort's status live in the knowledge
|
||||
|
||||
@@ -40,7 +40,7 @@ Implementation lives in the code repositories — see
|
||||
|
||||
## Rules
|
||||
|
||||
- Markdown only.
|
||||
- Markdown only, but for the checks and one drawn overview of the playbooks (`00-META/process/playbooks.html`).
|
||||
- No new top-level folders without explicit confirmation.
|
||||
- Knowledge flows `GENESIS → RESEARCH → DESIGN`. Research graduates into design only after
|
||||
analysis against GENESIS confirms alignment, and only through a recorded decision.
|
||||
|
||||
Reference in New Issue
Block a user