Compare commits
42
Commits
022392a22b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5c6c98ecc | ||
|
|
d2dccddc27 | ||
|
|
dcc6f185d4 | ||
|
|
e925a843ce | ||
|
|
66bb013b85 | ||
|
|
0738fa1405 | ||
|
|
a0a4edabc9 | ||
|
|
ebc7665b2a | ||
|
|
f96f964a2d | ||
|
|
4a2c15f725 | ||
|
|
ae9768c2c1 | ||
|
|
440279fec4 | ||
|
|
3439d19d06 | ||
|
|
89c4295f6d | ||
|
|
3e061bbd99 | ||
|
|
f09cc22d99 | ||
|
|
6ffd3709d8 | ||
|
|
990a77c760 | ||
|
|
cc605e3b13 | ||
|
|
023ad01160 | ||
|
|
9ffa60924e | ||
|
|
c6d36b2f32 | ||
|
|
f6311a66bf | ||
|
|
c04c0ebb76 | ||
|
|
491de157da | ||
|
|
bea864af8b | ||
|
|
7ed83f4965 | ||
|
|
71759e83f8 | ||
|
|
3174d5aa6e | ||
|
|
e7c8ce2740 | ||
|
|
5816ca669f | ||
|
|
afe83b9124 | ||
|
|
f269e83f5d | ||
|
|
6c607504de | ||
|
|
20c857443c | ||
|
|
7735cfd107 | ||
|
|
7e1c0f4c15 | ||
|
|
19a8e92801 | ||
|
|
59c6954b0c | ||
|
|
95bf5cbf20 | ||
|
|
f0bcc75fcf | ||
|
|
550487fc90 |
+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.
|
||||
|
||||
+73
-8
@@ -403,14 +403,23 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
|
||||
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.
|
||||
**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).
|
||||
@@ -763,6 +772,54 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec
|
||||
- **grab** — an engine handing one media release to a download client. It names the work it is for, so
|
||||
the media-importer knows where the copy belongs.
|
||||
|
||||
## Mail — the operator's own correspondence, which the mesh holds and reads for them
|
||||
|
||||
**Purpose.** To hold the operator's mail accounts, say each mail on the bus, find and read mail for whoever the
|
||||
operator granted, note each mail for the operator and tell them of the mail that matters, and send a mail only on
|
||||
the operator's Approve. **Recorded by** `mesh-mail` (its index database and attachment bucket, ADR 0359 §3; its
|
||||
account buckets, ADR 0439) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons,
|
||||
ADR 0459). **Downstream of** Identity and access and Operator and conversation; **upstream of** nothing.
|
||||
**Decided by** ADR 0359, 0361, 0378, 0439, 0440, 0458, 0459, 0460, 0461, 0462.
|
||||
**Uses:** operator, person, module, seat; grant, identity (Identity and access); ask, warrant, operator channel,
|
||||
content rule, untrusted input (Operator and conversation). A *judgement* is one call of the model with no tools
|
||||
and no session (ADR 0278), in its plain sense.
|
||||
|
||||
- **mail account** — one of the operator's mailboxes that `mesh-mail` holds, of a kind of adapter (`imap`,
|
||||
`imap-oauth`, `graph`), named by its address (ADR 0359 §1).
|
||||
- **`mail-store`** — the seat holding the mail accounts: `accounts`, `search`, `read`, `send`, `send_status` (ADR
|
||||
0378 §2, ADR 0440 §7). Always the seat's name: the bare *store* is Core's.
|
||||
- **mail event** — what `mesh-mail` publishes on its stream `MAIL` for a new, changed or removed mail: headers,
|
||||
flags and attachment references, never a body (ADR 0359 §2).
|
||||
- **mail send** — a mail the mesh sends, which goes out only on the operator's warrant for that one mail (ADR 0361
|
||||
§2), with the states `mail_send_status` answers (ADR 0440 §7). Only a caller granted asking to send for that
|
||||
account may ask for one (ADR 0460).
|
||||
- **mail web client** — the module `mesh-mail-web`, holder of `mail-webclient`: the operator's mail in a browser on
|
||||
the private network (ADR 0440 §1). The page it serves is the mail page.
|
||||
- **mail notice** — `mesh-mail-notifier`'s desktop pop-up for a new inbox mail (ADR 0378 §7, §9). It retires when
|
||||
`mesh-mail-notifier` is unassigned everywhere (ADR 0461 §7) and then moves to *mail signal*'s *Not:* line
|
||||
(issue 1418).
|
||||
- **mail reading** — the model's reading of each new mail as it arrives, in batches, into its mail group and mail
|
||||
entry; the seat `mail-reading`, held by `mesh-mail-reader` (ADR 0459). Not *triage* (the issue tracker's
|
||||
judgement), and not `mail_read`, `mail-store`'s tool that fetches one mail's text.
|
||||
- **mail entry** — what mail reading keeps about one mail: its mail group, a one-line summary, its mark and why, a
|
||||
due date; never the body (ADR 0459 §4). Not *catalogue entry*: the catalogue is Delivery's.
|
||||
- **mark** — what a mail entry says the mail is to the operator: *matters*, *scam warning* or neither, never two
|
||||
(ADR 0459 §7). Not *kind* (Media's).
|
||||
- **mail group** — a set of mails the model put together because they belong to one subject (ADR 0459 §4). Not *delivery
|
||||
group* or *account group*.
|
||||
- **reading lesson** — one correction the operator made on the mail page (a mark or a mail group), kept so later
|
||||
mail reading follows it (ADR 0459 §8 to §10). Always qualified: the bare *lesson* is Operator and conversation's.
|
||||
- **mail signal** — the notice on the operator's channel, one `tell` per batch in words only, and the place at the
|
||||
top of the mail page, that tells the operator mail matters (ADR 0461). Always qualified: the bare *signal* is
|
||||
Health and repair's.
|
||||
- **scam warning** — the mark of a mail the model judges an attempt to deceive the operator, with why; shown in
|
||||
its own place on the mail page and never a mail signal (ADR 0459 §7, ADR 0461 §6).
|
||||
- **mail conversation** — a thread (ADR 0383) with a mail context, followed on the mail page: the mail questions
|
||||
and their answers, each answer with the mails it found. The mail context is the framing that a mail is data, the
|
||||
tools `mail_search` and `mail_read` only, the operator's senders and folders, and the reading lessons (ADR 0462).
|
||||
- **mail question** — what the operator writes in the mail conversation to find or understand mail. Not *ask*
|
||||
(Operator and conversation's request for the operator's input).
|
||||
|
||||
## The record — how this repository works
|
||||
|
||||
Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's.
|
||||
@@ -852,7 +909,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| | 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** | Delivery |
|
||||
| | the pull request status | **merge 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 |
|
||||
@@ -868,6 +925,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| store | the one database server | **store** | Core |
|
||||
| | the OCI registry | **artifact store** | Delivery |
|
||||
| | a node's own copy of its last declaration | **last declaration** | Placement |
|
||||
| | the seat holding the mail accounts | **`mail-store`** | Mail |
|
||||
| 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 |
|
||||
| | the upgrade policy that sends nowhere | `record` | Module |
|
||||
@@ -875,7 +933,8 @@ 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** | 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 |
|
||||
@@ -893,6 +952,12 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| | 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* | — |
|
||||
| trusted | a config value that decides what runs or who can reach what | **trusted** config value | Config |
|
||||
| | a sender on the operator's own list | **trusted** sender, its message an **operator message** | Operator and conversation |
|
||||
| lesson | an agent's proposal after the operator corrects its way of working | **lesson** | Operator and conversation |
|
||||
| | the operator's correction of mail reading | **reading lesson** | Mail |
|
||||
| signal | something that must keep happening | **signal** | Health and repair |
|
||||
| | the notice that mail matters | **mail signal** | Mail |
|
||||
| conversation | the domain *Operator and conversation* | the domain's name | Operator and conversation |
|
||||
| | the mail questions and answers on the mail page | **mail conversation** | Mail |
|
||||
|
||||
## How this page is kept
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; `mesh-controller.try` runs the integration tests on each pushed branch, then the pull requests are opened and the forge's merge checks run them again; no further review | one try and 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.
|
||||
@@ -93,7 +98,10 @@ never into the running one, unless it blocks the running one.
|
||||
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.
|
||||
@@ -133,13 +141,20 @@ 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 (those that start a store, a bus or a
|
||||
process) wait for the check step, where `mesh-controller.try` runs them first.
|
||||
- **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;
|
||||
@@ -147,7 +162,7 @@ never into the running one, unless it blocks the running one.
|
||||
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 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 check 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
|
||||
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
|
||||
@@ -162,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
|
||||
@@ -172,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
|
||||
@@ -186,12 +209,13 @@ never into the running one, unless it blocks the running one.
|
||||
|
||||
## Step 3 — Check (no review)
|
||||
|
||||
1. **Push every feature branch, try it, then open one pull request per repository, together.**
|
||||
**The integration tests run once before the pull requests are opened**: `mesh-controller.try` with the
|
||||
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 try passed (the operator, 2026-10-18: three pushes in a row failed their first merge check on
|
||||
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).
|
||||
@@ -219,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
|
||||
@@ -286,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,20 +34,22 @@ 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, try it, and open the pull request.** The trunk is what runs and nothing holds merged work back
|
||||
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 once, and the pull request is opened only on a pass; the forge's merge check 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>`,
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
layer: as-is
|
||||
status: implemented
|
||||
code: [mesh-catalog modules/mesh-mail, mesh-catalog modules/mesh-mail-web, mesh-catalog modules/mesh-mail-notifier]
|
||||
updated: 2026-10-20
|
||||
decisions:
|
||||
- "ADR 0359"
|
||||
- "ADR 0361"
|
||||
- "ADR 0378"
|
||||
- "ADR 0379"
|
||||
- "ADR 0436"
|
||||
- "ADR 0439"
|
||||
- "ADR 0440"
|
||||
---
|
||||
|
||||
# The mail modules, as they run
|
||||
|
||||
**One module, `mesh-mail`, holds every mail account and is the mesh's one `mail-store`. A second module,
|
||||
`mesh-mail-web`, is the mesh's one mail web client. A third, `mesh-mail-notifier`, shows a
|
||||
desktop notice of new mail on the machines it is assigned to. A caller may read an account's mail, or ask to
|
||||
send from it, only when `mesh-mail`'s trusted setting `grants` names it for that account — one list per
|
||||
account, giving both at once, to the same callers. A send is never performed by a tool: it is queued for a
|
||||
process of its own, which sends only once the operator approves it on Telegram.** Read from `mesh-catalog` on
|
||||
`origin/main` at `a67f0196` (2026-10-20) and from the live mesh through the mesh MCP server on 2026-10-20.
|
||||
|
||||
```
|
||||
mesh-mail (mail-store, once for the mesh — the control-node)
|
||||
accounts search read send send-status ── MAIL stream (headers only, 7 days) ──▶ a granted caller
|
||||
own database (index) · own object-store bucket (attachments) · three key-value buckets (sends, account state, newest mails)
|
||||
process keep (IDLE + index) · process watch (mail_health) · process send (mesh-mail-send account; holds the passwords)
|
||||
▲ grants: one list per account, read AND send alike
|
||||
│
|
||||
┌────┴─────────────────────────────┬───────────────────────────────┐
|
||||
mesh-mail-notifier (mail-notifier, mesh-mail-web (mail-webclient,
|
||||
once per machine — laptop, work- once for the mesh — the control-node)
|
||||
station) sign-in (Keycloak) ─ pages ─ send ask ─▶ operator's Approve (Telegram)
|
||||
node-notifier.send on new inbox mail
|
||||
```
|
||||
|
||||
## 1. The modules and their seats
|
||||
|
||||
- **`mesh-mail`** claims `mail-store`, held once for the mesh (`accounts`, `search`, `read`, `send`,
|
||||
`send-status`). It is assigned today to one machine, the control-node (`module.json`; confirmed live,
|
||||
2026-10-20). ADR 0378 §4 decided to move it to the home server as a step of its own, on the operator's
|
||||
Approve (issue 803); as built, that move has not happened: the live mesh still runs it, and `mesh-mail-web`
|
||||
beside it, on the control-node.
|
||||
- **`mesh-mail-web`** is a module of its own, with its own Go module and source under `modules/mesh-mail-web`;
|
||||
it claims `mail-webclient`, held once for the mesh. It is assigned to the same machine as `mesh-mail` and runs as its
|
||||
own account, `mesh-mail-web`. Its manifest declares no database and no store of its own: nothing beyond its
|
||||
route, its sign-in binding and its own settings on disk.
|
||||
- **`mesh-mail-notifier`** is a module of its own as well (`modules/mesh-mail-notifier`), and keeps a
|
||||
byte-identical copy of `mesh-mail`'s `mailevent.go`, held to it by its `copies_test.go`, because each module
|
||||
builds alone. ADR 0378 §9 and ADR 0440 §1 say both are "built from `mesh-mail`'s source"; as built, they are
|
||||
separate modules that share none of `mesh-mail`'s own code but that copied file. It claims `mail-notifier`, held once per machine; a
|
||||
module claiming it needs `node-notifier` held on the same machine (ADR 0378 §7). It is assigned today to two
|
||||
machines, the laptop and the workstation (live read of each machine's modules, 2026-10-20); the control-node
|
||||
and the home server hold no instance of it.
|
||||
- None of the three seats is named `mesh-*`. A caller reaches them by seat (`mail-store.<verb>`,
|
||||
`mail-webclient.<verb>`, `mail-notifier.<verb>`); `mesh-mail` also answers its own tool names (`mail_accounts`
|
||||
and the rest) through the same handlers.
|
||||
|
||||
## 2. Accounts and the MAIL stream
|
||||
|
||||
- **Three accounts**, all of the kind `imap`, each a password-based sign-in given at the desk as the module's
|
||||
own secret `mail-credential-<account>` — one against the mesh's own mail server, two against outside
|
||||
providers (ADR 0359 §5, ADR 0361 §1; `mesh-controller.settings` for `mesh-mail`, read live on 2026-10-20). No
|
||||
account's name or address is repeated in this document.
|
||||
- **It watches each account and says each mail on the bus.** `mesh-mail` keeps IMAP IDLE on the inbox and polls
|
||||
the other folders; for each new, changed or removed message it publishes an event on its own stream `MAIL` —
|
||||
account, folder, message id, date, from, to, subject, size, flags and attachment references, never a body or
|
||||
an attachment's bytes (ADR 0359 §2). The stream keeps 7 days (`module.json`).
|
||||
- **A send's own record** lives in its state bucket `sends`, which only the `send` process and the tools write
|
||||
(`module.json`; ADR 0436 §8).
|
||||
- **The index and the attachment store are its own.** A row per message and folder lives in its own Postgres
|
||||
database; attachments live in an object-store bucket, each kept once by its SHA-256 digest (ADR 0359 §3, ADR
|
||||
0439 §5-§6).
|
||||
- **Two more buckets answer without the database.** An account bucket holds each account's state and, per
|
||||
folder, its name and its mail and unread counts; a mail bucket holds the newest mails of each account (up to
|
||||
200, fewer once there are more than 81 accounts), for a view that need not ask the database (ADR 0439 §2-§4).
|
||||
`mesh-mail-web`'s accounts page reads the folders from this bucket — a read wider than any one caller's grant
|
||||
— but only for an account its own grant already let it ask about (ADR 0440 §4).
|
||||
|
||||
## 3. `grants` — one list per account, giving both at once
|
||||
|
||||
- **The setting.** `mesh-mail`'s trusted setting `grants` names, per account, the callers that may reach it: a
|
||||
module's name alone (granted wherever it runs), `<machine>.<module>`, or a person. Nobody is granted an
|
||||
account by default (ADR 0359 §4).
|
||||
- **One check, every door.** The code that decides whether a caller may read an account's mail (`mail_search`,
|
||||
`mail_read`, the account's share of `MAIL`) and the code that decides whether it may ask to send from it
|
||||
(`mail_send`) is the same function, reading the one list (`modules/mesh-mail/cmd/mesh-mail/config.go`'s
|
||||
`Grants.Allows`, called from `tools.go` for every tool, `mail_send` included, confirmed on `origin/main` at `a67f0196`). There is no
|
||||
second list and no narrower grant for sending alone: a caller named for an account may do both, and a caller
|
||||
left out of it may do neither.
|
||||
- **Who is granted, today, for every account alike** (`mesh-controller.settings` for `mesh-mail`, read live on
|
||||
2026-10-20): `mesh-mail-notifier` (named alone, so wherever it is assigned it may read); each of the four
|
||||
machines' own `node-tools` — the control-node's, the laptop's, the workstation's and the home server's, which
|
||||
is how an agent session on any of them reaches a mail account at all; and the control-node's `mesh-mail-web`.
|
||||
So the notifier, every machine's agent sessions, and the web client can each both read an account's mail and
|
||||
ask to send from it, from the one list.
|
||||
|
||||
## 4. The send path
|
||||
|
||||
- **A send is an ask, never a write by the tool.** `mail_send` (account, to, subject, body) is open only to a
|
||||
caller the account's `grants` names. It performs nothing itself: it raises an ask on the operator channel
|
||||
naming the account, the recipients, the subject and the first lines, and returns the ask's id at once (ADR
|
||||
0361 §2).
|
||||
- **The operator's Approve queues it once.** On an Approve recorded on a channel that proves who answered,
|
||||
`mesh-mail`'s tools record the send `approved` and publish it, once, on the work queue `mail-sends`, carrying
|
||||
only the send's id, its account and its ask id — never the recipients, subject or body, which stay in the
|
||||
send's own record (ADR 0436 §8).
|
||||
- **A process of its own performs it.** `mesh-mail`'s process `send` runs as its own account, `mesh-mail-send`,
|
||||
never the tool runner's account: it alone reads the accounts' passwords. It takes work from the queue,
|
||||
verifies the operator's warrant again against the router's own record rather than trusting the queued message,
|
||||
claims the send once, hands it to the account's outgoing server, and records the outcome — retrying a send the
|
||||
server provably never took, up to five times, each after a longer wait, before giving up (ADR 0436 §2, §7,
|
||||
§9-§10).
|
||||
- **`mail_send_status` answers one of a fixed set of states**: waiting for the operator, approved and waiting
|
||||
for the sender, being sent, waiting for its next attempt, sent, failed (with its reason), refused, not proven
|
||||
(the handover may or may not have reached the server), declined, expired, or unknown for an id it does not
|
||||
know or that belongs to another caller's send (`send.go`, `sender.go`; ADR 0436 §12).
|
||||
|
||||
## 5. `mesh-mail-web`
|
||||
|
||||
- **Its pages**, all behind sign-in: the accounts page (`/`), a folder (`/folder`), search (`/search`), one
|
||||
mail (`/mail`), an attachment download (`/attachment`), writing a mail (`/write`), its send (`/send`) and its
|
||||
state (`/send-status`) — plus the sign-in callback (`/signed-in`). Every changing form (today, the send)
|
||||
carries a token bound to the session (`server.go`, `csrf.go`).
|
||||
- **Sign-in is Keycloak's.** The module requires `oidc-client` and uses the authorisation-code flow with PKCE;
|
||||
the identity (the ID token's own username claim) must be in the trusted setting `operators` — nobody by
|
||||
default, and today one identity, the operator's own (ADR 0440 §3; `mesh-controller.settings` for
|
||||
`mesh-mail-web`, read live on 2026-10-20). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict`,
|
||||
ending 12 hours after sign-in (`auth.go`).
|
||||
- **Reachable only from the mesh's own machines.** Its route's reach is set to `internal`, so the route proxy
|
||||
composes only its internal name, never a public one, and the module itself answers any other host 404 (ADR
|
||||
0440 §2; `server.go`).
|
||||
- **A mail is shown as text.** `mail_read`'s own HTML-to-text is rendered through `html/template`'s escaping,
|
||||
never parsed as markup; an attachment is answered only as a download, `Content-Disposition: attachment` and
|
||||
`X-Content-Type-Options: nosniff`; every page carries a content security policy allowing nothing from
|
||||
elsewhere (ADR 0440 §5; `mail.go`).
|
||||
- **It writes nothing of its own.** No database, nothing on disk beyond its own settings; it reaches mail only
|
||||
through `mail-store`'s verbs, calling as `<machine>.mesh-mail-web`, and reads one thing directly besides —
|
||||
`mesh-mail`'s account bucket, for folders and counts, of an account its own grant already let it ask about
|
||||
(ADR 0440 §4, §6).
|
||||
|
||||
## 6. `mesh-mail-notifier`
|
||||
|
||||
- **One notice per new inbox mail, at most once.** For each account it is granted, on hearing `mesh-mail`'s
|
||||
`added` event, it shows the sender (name and address), the subject and the account's name through the
|
||||
machine's `node-notifier`, under its own account (`runs-as`), never the tool runner's.
|
||||
- **Quiet rules.** It shows nothing for a mail outside the inbox, for a mail from the account's own address, and
|
||||
for a mail already flagged read when it arrives (`notifier.go`, `mailevent.go`). During an account's first sync
|
||||
or a resync `mesh-mail` publishes no `added` events, so nothing reaches the notifier then (`notifier.go`'s
|
||||
opening note, citing `mesh-mail`'s `imap.go`).
|
||||
- **Folding.** More than three qualifying mails of one account within a trailing 60-second window become one
|
||||
notice, shown once the window has passed since the fold's own first mail, naming how many and the latest
|
||||
sender; a message already shown — tracked by its message id, or by its folder, UIDVALIDITY and UID where it
|
||||
has none — is never shown twice, even redelivered. A notice that fails to send is retried at most once a
|
||||
minute (`notifier.go`, its constants `FoldOver`, `Window`, `RetryBackoff`).
|
||||
- **Assignment.** It is assigned today to the laptop and the workstation (live read, 2026-10-20), where it needs
|
||||
`node-notifier` to be held beneath it.
|
||||
|
||||
## 7. Health
|
||||
|
||||
- **`mesh-mail`**'s liveness is its `keep` process's own unit health; its watching is judged once a minute by
|
||||
its `watch` process and read through the tool `mail_health`: `waits` (naming the secret) for an account whose
|
||||
password was never given, `unhealthy` (naming the account and why) for a connection down or an IDLE silent
|
||||
for five minutes, healthy otherwise; its `send` process is a plain unit (ADR 0359 §6; `health.go`).
|
||||
- **`mesh-mail-web`**'s own tool `mail_web_health` reads the one state its HTTP endpoint keeps: still setting up
|
||||
sign-in, listening, or ended with its error — unhealthy whenever it is not listening (`main.go`).
|
||||
- **`mesh-mail-notifier`** declares no process resource: its manifest declares only its account and its state
|
||||
directory. Its bundle is the one process the machine's runtime launches for it and keeps alive, under its own
|
||||
account (`runs-as`); it serves its tools and runs the watcher beside them (`main.go`'s opening note; commit
|
||||
12d78520, issue 859). Its tool `mail_notifier_health` is unhealthy once an account's notice
|
||||
has failed to show for more than ten minutes, and answers only when asked; no check of the node-engine judges
|
||||
the module by it. A watching process of its own is tracked by issue 841, not yet decided (its `README.md`).
|
||||
The comment in its `health.go` that still names a process `notify` predates that commit. It keeps one state
|
||||
bucket, `shown`, for 30 days, to show no mail twice (`module.json`).
|
||||
@@ -26,6 +26,7 @@ Where the two disagree, the implementation wins and the disagreement is stated.
|
||||
| [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences |
|
||||
| [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool |
|
||||
| [`17-accounts-on-a-machine.md`](17-accounts-on-a-machine.md) | The operator and agent accounts on the node record, claude-code's own login and guard, the judgement that the agent account cannot become root, and the forge accounts agents push as |
|
||||
| [`18-the-mail-modules.md`](18-the-mail-modules.md) | mesh-mail's accounts and MAIL stream, the one `grants` list that gives both reading and sending at once, the send path through its own process and the operator's Approve, mesh-mail-web's pages and sign-in, and mesh-mail-notifier's pop-up and its quiet rules |
|
||||
|
||||
## What these documents are not
|
||||
|
||||
|
||||
@@ -2,8 +2,16 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-catalog modules/mesh-delivery, mesh-delivery, mesh-host, mesh-sdk]
|
||||
updated: 2026-10-19
|
||||
updated: 2026-10-20
|
||||
decisions:
|
||||
- "ADR 0453"
|
||||
- "ADR 0452"
|
||||
- "ADR 0450"
|
||||
- "ADR 0449"
|
||||
- "ADR 0447"
|
||||
- "ADR 0446"
|
||||
- "ADR 0445"
|
||||
- "ADR 0444"
|
||||
- "ADR 0437"
|
||||
- "ADR 0435"
|
||||
- "ADR 0434"
|
||||
@@ -81,15 +89,21 @@ source revision is derived from the keys: every **want** equals its module's **f
|
||||
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.
|
||||
- **The builder** claims a build by creating its key, once per fingerprint, and builds it; the builder is made in R4c from
|
||||
build-agent's code, and build-agent builds the builder's own module beside it until R4g, then leaves (ADR 0453 clause 5).
|
||||
- **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 R4c 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
|
||||
@@ -310,6 +324,59 @@ 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 the status switch of release 4 (R4f) 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 and the gate reads it from R4f, the delivery in which
|
||||
the checker's line is the only one (ADR 0447 §5); the checker writes it unrequired from R4c (ADR 0453 clause 4). `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; the integration level's bounds fail a repository from its
|
||||
first check that meets them (ADR 0453 clause 9). A
|
||||
repository's `merge-check.sh` goes in R4g (ADR 0453 clause 8); 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.*
|
||||
@@ -447,11 +514,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 (R4f).** 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 R4f 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
|
||||
@@ -587,6 +656,23 @@ From ADR 0429 (research 063's done-when, and the live proof each of its releases
|
||||
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).
|
||||
@@ -603,6 +689,11 @@ From ADR 0429 (research 063's done-when, and the live proof each of its releases
|
||||
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 deliveries (R4f for the merge gate, the judge and the two statuses; R4g for the scripts): `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
|
||||
|
||||
@@ -623,7 +714,7 @@ release reports the controller's code lines against research 063's baseline (acc
|
||||
| 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 | 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) |
|
||||
| 6 | Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): 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: proved by R4e; 4: in part by R4e, wholly by R4g) | — | 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) |
|
||||
@@ -631,21 +722,31 @@ release reports the controller's code lines against research 063's baseline (acc
|
||||
| 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 | opened when it is prepared |
|
||||
| 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) | opened when it is prepared |
|
||||
| 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) | opened when it is prepared |
|
||||
| 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 | 16, 2, 4 | mesh-delivery (builder, checker), mesh-controller (build dispatch deleted; build-agent leaves), mesh-catalog (build modules, ssh-client, gitea), mesh-sdk | 979, 971; the rest opened when it is prepared |
|
||||
| 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 | The gate's keys can carry what the forge needs and the controller answers a what-if as data, so the gate has what it reads before it runs; a failed delivery plan asks the operator Retry on the phone, and a seat whose holder is assigned nowhere passes to its only claimant; the operator sees only the Retry notice (ADR 0450, ADR 0449) | — | mesh-sdk, mesh-controller | in mesh-sdk: 1321 (a merge key carries the Approve's heads), 1322 (an owed act carries a status's target, a commit without a pull request, two comments), 1323 (a name a key cannot hold is escaped); in mesh-controller: 1324 (what-if as data), 1325 (the build seat reads the gate's pull keys), 1326 (the gate's buckets writable by its runner only), 1243 (the three parts' credentials); and, delivered first as feature no-terminal (ADR 0449): 1317 (the seat passes to its only claimant), 1314 (a failed delivery plan raises a condition), 1315 (Retry offered and performed), 1316 (a refused answer is told). Its prepare step found four things the gate reads that ship before it, which cut the former R3d in two (ADR 0450), and two steps on the path that needed the operator's terminal (ADR 0449) |
|
||||
| R3e | 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 when it first holds the seat and ends; no step needs the operator's terminal (ADR 0437, ADR 0450) | 15 | mesh-delivery (gate's parts: forge bridge, teller, indexer, view), mesh-catalog (the gate, mesh-delivery removed, mesh-issues), mesh-tools (the tool runner's waits) | opened when it is prepared, from R3d's survey; the controller's deletions it makes possible go to release 4 (1318) |
|
||||
| R4a | The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalogue, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3) | — (2 needs it) | mesh-sdk, the node-engine (`mesh-host`) | in mesh-sdk: 1353 to 1364, 1298 (the composition over a plain input); in the node-engine: 1308 (the user namespaces capability) |
|
||||
| R4b | mesh-controller and the node-engine use the catalogue, the validator and the composition from mesh-sdk and delete their copies; the controller composes every node both ways on the live facts and the two agree before this is delivered; it writes the grants and credential bindings the assigner reads; its unit level meets its bound (ADR 0453 clauses 1, 3) | — | mesh-controller, the node-engine (`mesh-host`) | 1375 (to be split when it is prepared), 1337 (the unit level's bound) |
|
||||
| R4c | The new parts run dark beside today's: the indexer writes the manifests by digest and the fingerprints in the new form beside today's (its own, first delivery), then the build modules (the Go build module, the Node and TypeScript build module: toolchain, sandbox, fetch, digest, test, build), the builder, the checker writing `mesh/check` as a status the forge does not require, and the assigner's composition check writing into the checker's report; the build seat's accounts are decided by a record before the fetch step merges (ADR 0453 clauses 4, 6, 7, 8, 11, 12, 16) | 22, 23 (measured on the dark run, proved in R4f) | mesh-delivery (indexer, builder, checker, assigner's composition check), mesh-catalog (build modules, ssh-client, gitea), mesh-controller (the build agent's whole-check tests, the script's steps as tests), mesh-sdk | 971 (its record first), 1297 (the assigner's composition check), 1309 (the fetch and run steps), 1287, 1288, 1289, 1290, 1291, 1292 (the checker; the build modules run the tests), 1251; the steps of 1293 and 1294 that are no test become tests in R4c to R4e; the rest opened when it is prepared |
|
||||
| R4d | Four modules stop being built from a Dockerfile: route-proxy, nats, n8n and mail's autoconfiguration module (automx, replaced by a Go program), each rework its own release step, nats in a delivery of its own; free between R4a and R4e and never in the same delivery as another release (ADR 0452, ADR 0453 clauses 13, 14, 16) | — | mesh-catalog, mesh-controller (route-proxy), the node-engine (`mesh-host`: n8n's supplementary group) | the four image reworks, opened when it is prepared |
|
||||
| R4e | The controller asks no build: a build is keyed by its fingerprint, the new form becomes the only one as one group, and a merge builds nothing its check built; builders fetch over SSH, so an SDK change ships with its users; the vendored copies leave mesh-controller and mesh-sdk; delivered only once the builder's artifact digests equal build-agent's (ADR 0453 clauses 2, 4, 5, 6) | 16, 2, 4 in part | mesh-controller (builds are no longer asked), mesh-delivery (builder), mesh-sdk | 979 (the installed toolchains; closed with the toolchain images in R4g), 1304, 1305, the controller asking no build (opened when it is prepared) |
|
||||
| R4f | A pull request carries one required status that says what it proved and how long each part took; the controller's merge gate and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10) | 21, 22, 23, 24 (the status; the scripts' absence is proved in R4g) | mesh-delivery (checker, assigner), mesh-controller (merge gate, replays, `delivery-plan`, `forget-checks`, the asker `mesh-delivery` deleted), mesh-catalog (gitea) | 1299 (the merge gate deleted; closes issue 709, the replays' socket), 1300, 1301, 1302 (one status), 1318 |
|
||||
| R4g | The clean-up: the scripts of the repositories a module is built from, build-agent with its socket and Dockerfile code, the toolchain images and the word merge gate go; the builder's own module is delivered from the builder's keys (ADR 0453 clauses 5, 8, 10) | 4 (no container image builds remain), 24 (no `merge-check.sh` in a repository a module is built from) | mesh-controller (build-agent removed), mesh-catalog, mesh-delivery, mesh-tools (scripts deleted), hq | 1295 (`merge-check.sh` goes; the tests of 1293 and 1294 are R4c to R4e's), 979 (closed with the toolchain images), 1303 (the word merge gate struck); 1296 (how hq is checked) is the operator's and undated by this release |
|
||||
| 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. 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
|
||||
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 R4e (ADR 0429 §10, ADR 0453 clause 2); 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
|
||||
is issue 1055. Acceptance test 6 spoke of a ready mark when milestone 2 was delivered; restated for
|
||||
the Approve, it is proved again by milestone 3.
|
||||
|
||||
R4's prepare step on 2026-10-20 cut release 4 by its orders into R4a to R4g, each one release (ADR 0453, the operator's Approve): the SDK ships before its users, the new parts run dark before anything relies on them, the status switch follows a measured run, and the scripts and build-agent go last;
|
||||
R4d, the four image reworks, has no order of its own and may be delivered at any point between R4a and R4e. Where a record dates a rule by release 4, it reads the R4 release that builds the rule (ADR 0453 clause 15).
|
||||
|
||||
## What is not decided here
|
||||
|
||||
- A web view of deliveries beyond the pull request's comment.
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh]
|
||||
updated: 2026-10-18
|
||||
updated: 2026-10-20
|
||||
decisions:
|
||||
- "ADR 0429"
|
||||
- "ADR 0244"
|
||||
- "ADR 0006"
|
||||
- "ADR 0008"
|
||||
- "ADR 0302"
|
||||
- "ADR 0354"
|
||||
- "ADR 0458"
|
||||
---
|
||||
|
||||
# 49 — The mesh in domains
|
||||
|
||||
**Every concept of the mesh belongs to one of eleven domains, which owns its one word. The glossary is
|
||||
**Every concept of the mesh belongs to one of thirteen domains, which owns its one word. The glossary is
|
||||
organised by them and is the authority; a word it retires is named in the entry that replaced it, with
|
||||
where it is retired; and two checks hold the documents and the tools' descriptions to it.**
|
||||
(ADR 0244; the eleventh, Media, ADR 0302.)
|
||||
(ADR 0244; the eleventh, Media, ADR 0302; the twelfth, Config, ADR 0354; the thirteenth, Mail, ADR 0458.)
|
||||
|
||||
A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word,
|
||||
and the domain decides what that word means. The glossary
|
||||
@@ -38,6 +40,8 @@ shape — what each is for, where its records live, and how they depend on one a
|
||||
| **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller |
|
||||
| **Operator and conversation** | to let the mesh and its operator tell, ask and answer, and act only on an answer it can trust | the router, and the controller for authorising asks |
|
||||
| **Media** | to find, fetch and file copies of the works a person watches, listens to and reads, each in the library its kind names | the `media-importer` module's settings and memory, and the servarr apps' own libraries |
|
||||
| **Config** | to hold, check and resolve every value a module reads that someone may change, and say where each resolved value came from | the `mesh-config` module, in its buckets on the bus, with its store as the history behind them |
|
||||
| **Mail** | to hold the operator's mail accounts, say each mail on the bus, note each mail for the operator and tell them of the mail that matters, and send a mail only on the operator's Approve | `mesh-mail` (its index database, attachment bucket and account buckets) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons) |
|
||||
|
||||
Beside them, **the record** is this repository's own vocabulary — research, decisions, designs,
|
||||
issues, playbooks, checks — listed because its words meet the mesh's.
|
||||
@@ -71,6 +75,11 @@ 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.
|
||||
|
||||
Mail stands below the diagram's foot: it is downstream of Identity and access (the operator grants each mail
|
||||
account to a caller) and of Operator and conversation (a mail send is an ask, a mail signal is a notice through
|
||||
the operator channel), and upstream of nothing. It is the operator's own correspondence, which the mesh holds and
|
||||
reads for them, not the mesh talking about itself, so it is not part of Operator and conversation (ADR 0458).
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog]
|
||||
updated: 2026-10-20
|
||||
decisions:
|
||||
- "ADR 0440"
|
||||
- "ADR 0378"
|
||||
- "ADR 0379"
|
||||
- "ADR 0359"
|
||||
- "ADR 0361"
|
||||
- "ADR 0138"
|
||||
- "ADR 0167"
|
||||
- "ADR 0439"
|
||||
- "ADR 0201"
|
||||
- "ADR 0436"
|
||||
- "ADR 0458"
|
||||
- "ADR 0459"
|
||||
- "ADR 0460"
|
||||
- "ADR 0461"
|
||||
- "ADR 0462"
|
||||
- "ADR 0278"
|
||||
---
|
||||
|
||||
# 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.**
|
||||
(ADR 0440, building on ADR 0378 §5 and ADR 0379 §2.)
|
||||
|
||||
**Then the model reads the mail for the operator.** A module of its own, `mesh-mail-reader`, holds the seat
|
||||
`mail-reading`: it reads each new mail in batches, keeps a mail entry for it (its mail group, one line, its mark),
|
||||
tells the operator on Telegram of the mail that matters in a mail signal, shows a scam warning on the page only, and
|
||||
learns from the operator's corrections as reading lessons; it never changes the mailbox and keeps no mail text.
|
||||
Reading and asking to send are granted apart first, and the pop-up for every new mail retires once mail signals
|
||||
work. Later, once the mesh's threads exist, the operator asks questions about their mail in a mail conversation, a
|
||||
thread with a mail context. Betterbird is removed once mail reading works on the laptop and the workstation.
|
||||
(Research 066; ADR 0458 to 0462. The words are the glossary's Mail domain.)
|
||||
|
||||
**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. A new mail that matters reaches the
|
||||
operator's phone as one mail signal and stands at the top of the mail page in its mail group; a mail that does not
|
||||
matter gives no pop-up and no mail signal anywhere.
|
||||
|
||||
## 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 (milestone 3, §11).
|
||||
|
||||
## 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. Reading and asking to send, granted apart (ADR 0460)
|
||||
|
||||
`mesh-mail`'s trusted setting `grants` gives a caller, per account, **reading** (that account's `MAIL` events,
|
||||
`mail_accounts`, `mail_search`, `mail_read`) and **asking to send** (`mail_send`, `mail_send_status`), each on its own,
|
||||
set as `grants` is today. A caller granted reading only is refused by both send tools, with the missing grant named;
|
||||
one granted asking to send only is refused every read. When the split is delivered every caller in today's `grants`
|
||||
keeps both for each account it has, so nothing stops; narrowing one afterwards is the operator's choice.
|
||||
`mesh-mail-reader` is granted reading only, for every account, and nothing before the split is delivered. This is
|
||||
issue 1227.
|
||||
|
||||
## 6. Mail reading (ADR 0459)
|
||||
|
||||
```
|
||||
mail-store (mesh-mail) ── MAIL: mail.<account>.message.added ──▶ mesh-mail-reader (seat mail-reading, once for the mesh)
|
||||
▲ │ its own database: mail entries, mail groups,
|
||||
└──────────── mail_read (reading grant only) ─────────────┤ reading lessons; never a body
|
||||
│ batches of ≤ 10 mails ──▶ claude_code_judge (within ≤ 40 s)
|
||||
│ claude_code_judged
|
||||
├──▶ operator-channel.tell (a mail signal, §7)
|
||||
▼
|
||||
mesh-mail-web ── entries · groups · correct · lessons · undo · status ──▶ the mail page
|
||||
```
|
||||
|
||||
- **One module, one seat.** `mesh-mail-reader`, built from `mesh-mail`'s source like `mesh-mail-web`, claims
|
||||
`mail-reading` (declared in `mesh-mail`'s manifest beside the three seats of ADR 0379 §4) and runs once, on the
|
||||
machine that runs `mesh-mail`. It hears `added` events under its own `runs-as` account and the operator's reading
|
||||
grant (ADR 0378 §13), and moves, flags, deletes and sends nothing.
|
||||
- **A mail entry per mail, never the body.** Its own database (`postgres-database`) keeps each mail's mail group,
|
||||
a one-line summary, its mark (*matters*, *scam warning* or neither) and why, and a due date where the mail gives
|
||||
one, beside the mail groups and the reading lessons.
|
||||
- **Batches.** One judgement reads at most 10 mails, and every part of its question has a bound, so it never
|
||||
passes 55 KiB of the judgement's 64 KiB: the framing that a mail is data and never an instruction, at most 4 KiB;
|
||||
the mail groups, at most 4 KiB, the largest first; the reading lessons that apply, at most 40 within 12 KiB,
|
||||
sender and mail group first, then the newest, under the framing that a reading lesson outranks anything a mail
|
||||
says; then each mail, at most 3.5 KiB (its headers at most 512 bytes, its text cut to its first 3 KiB), a mail in
|
||||
a Junk folder said to be so. A batch asks for up to 40 s with `within` and collects with
|
||||
`claude_code_judged` (built under issue 508, recorded by ADR 0459 §14). Research 066 measured 1.9 to 3.1 s per
|
||||
mail, so a batch of 10 takes about 19 to 31 s. A batch starts at least 160 s after the previous one started, so
|
||||
an issue tracker's check that waits behind a batch on the same machine ends within 115 s, inside its two minutes
|
||||
(ADR 0278 §2), which research 066's 15 mails and 120 s back to back would not; mail reading then reads up to 225
|
||||
mails an hour (ADR 0459 §15).
|
||||
Batches stay judgements; whether they become threads is issue 1417.
|
||||
- **Reading lessons.** When the operator changes a mail's mark or mail group on the page, `mesh-mail-web` calls
|
||||
`correct` and the correction is kept at once as a reading lesson (headers, the change, when; never body text),
|
||||
with no further Approve: it is the signed-in operator's own act. The page lists the reading lessons, undoes one
|
||||
(later mail reading only), and shows reading lessons per 100 read mails per week, which answers whether mail
|
||||
reading learns the operator's way.
|
||||
- **The page.** Above the classical list it shows the mail that matters at the top, the mail groups, and the scam
|
||||
warnings in a place of their own, each with why; the classical list (accounts, folders, search, one mail) stays
|
||||
and always opens. `mesh-mail-web` reads these through `mail-reading`'s verbs and still keeps no mail state
|
||||
(ADR 0378 §5 and ADR 0440 §4 as ADR 0459 §13 changes them); it writes the operator's corrections with
|
||||
`correct` and `undo`.
|
||||
- **It fails loudly.** Its own health check is unhealthy when a judgement is refused, times out or answers out of
|
||||
shape, with the cause and since when; the controller raises its condition, the page shows the cause above the
|
||||
classical list instead of mail groups, and no mail signal is sent. On recovery the mail that arrived meanwhile is
|
||||
read in arrival order from its durable consumer on `MAIL` (kept 7 days). A warning before a licence runs out is
|
||||
issue 1409.
|
||||
|
||||
## 7. Mail signals, and the pop-up retired (ADR 0461)
|
||||
|
||||
- **One tell per batch.** A batch that marks mail as mattering sends one `operator-channel.tell`: the headline
|
||||
"1 mail matters" or "N mails matter", a first line naming the mail with the nearest due date (sender's name,
|
||||
summary, due date), a second naming the next and, with more than two, ending "and N-2 more on the mail page".
|
||||
When the hourly cap of 10 refuses one, its mails stay at the top of the page and the next mail signal counts
|
||||
them.
|
||||
- **Words only.** No link, mail address, host name or path: the router refuses a message holding any, and the mail
|
||||
page opens only on the mesh's own machines. The reader checks its words with `messenger_check` first and, refused,
|
||||
sends the safe form "N mails matter: open the mail page".
|
||||
- **At once, at any hour**; the phone's own quiet hours decide. **Never** for a scam warning, and never while mail
|
||||
reading is unhealthy.
|
||||
- **The pop-up retires.** Once mail signals pass acceptance test 13 on the real accounts, `mesh-mail-notifier` is
|
||||
unassigned from the laptop and the workstation on the operator's Approve and leaves `grants`; `mail-notifier`
|
||||
stays declared and unheld, and nothing takes it over (ADR 0379 §3, §7). Betterbird's own pop-ups stay off by its
|
||||
setting until it is removed (ADR 0379 §6). Issue 1418; issue 801 closes as replaced.
|
||||
|
||||
## 8. The mail conversation (ADR 0462)
|
||||
|
||||
A mail conversation is a thread (ADR 0383), run by the thread handler (ADR 0384), with a mail context: the framing
|
||||
that a mail is data, the tools `mail_search` and `mail_read` only under a caller of the thread's own granted reading
|
||||
only (issue 1419), the operator's real senders and folder names, and the reading lessons. The operator writes a mail
|
||||
question on the mail page; each answer names the mails it found, and a question with no answer says so.
|
||||
`mesh-mail-web` creates and follows the thread over the bus on its server side; the page loads one script from its
|
||||
own origin and talks only to it (content security policy: script from that origin, `connect-src 'self'`), so the
|
||||
browser never reaches the bus. The thread keeps the text of each mail it read for good, as every thread is kept (ADR
|
||||
0383 §3), which the operator accepted; mail reading keeps none. In the same milestone `mail_search` matches folder
|
||||
names without regard to case and describes its three `-any` switches as true or false only. It is built after design
|
||||
58's milestone 3 (threads, issue 880) and once issue 1419's caller is decided.
|
||||
|
||||
## 9. What stays outside
|
||||
|
||||
Each is an issue with an owner:
|
||||
|
||||
- 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;
|
||||
- drafting a mail, the rest of issue 1259 (its triage is mail reading, its search the mail conversation), and writing
|
||||
to the mailbox: issue 730;
|
||||
- a task list for the operator: issue 1396; the search faults found by research 066: issues 1400 and 1401;
|
||||
- the work account: a later decision of its own (ADR 0359 §7).
|
||||
|
||||
## 10. The build's acceptance tests
|
||||
|
||||
Tests 1 to 9 are milestone 1's; tests 10 to 17 are research 066's eight, in its order.
|
||||
|
||||
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.
|
||||
|
||||
10. A new mail on each account has a mail entry, in a mail group, within 5 minutes of its mail event; a test searches
|
||||
`mesh-mail-reader`'s database for a known body line and finds none.
|
||||
11. The reader's caller is refused by `mail_send` for every account, and no mail is moved, flagged or deleted by it.
|
||||
12. (Milestone 4.) On the mail page, a mail question by a person, by a time range and by "what needs a reply" finds
|
||||
its target mails, research 066's three failed kinds included; a question with no answer says so; the conversation
|
||||
is a thread readable through the thread seat; and the browser makes no request but to the mail page's own origin.
|
||||
13. A batch with one mail that matters sends exactly one `tell` naming it, with no URL, mail address or host name in
|
||||
its words, and the mail is at the top of the page; a sender whose name carries a host name still gives a
|
||||
mail signal, in the safe form.
|
||||
14. Research 066's 20 steering mails, read again, mark no injected words as mattering, and every phishing decoy that
|
||||
is marked is a scam warning, which sends no `tell`.
|
||||
15. A reading lesson given on a real mail changes how the next mail of that sender is noted; undone, it no longer
|
||||
does; the page lists it.
|
||||
16. With the judgement refused (the reader's licence binding removed in the integration test), the condition is
|
||||
raised with its cause, the page shows it over the classical list, no `tell` is sent, and after recovery the mail
|
||||
that arrived meanwhile is read in arrival order.
|
||||
17. With `mesh-mail-notifier` unassigned, a new mail that does not matter shows nothing on the laptop or the
|
||||
workstation, and one that matters gives one mail signal.
|
||||
|
||||
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.
|
||||
|
||||
Tests 11, 13 (with a stand-in router), 16 and the grant split's refusals are one integration test that starts
|
||||
`mesh-mail` and `mesh-mail-reader` whole against the real bus and store; the batch's budget, the seat's verbs and the
|
||||
mail signal's words are unit tests; tests 10, 13, 14, 15 and 17 are proven live after milestone 2's delivery, test 13
|
||||
before `mesh-mail-notifier` is unassigned; test 12 with milestone 4.
|
||||
|
||||
## 11. 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 | Each new mail is read into a mail group on the mail page; the mail that matters reaches the operator's phone as one mail signal, a scam warning shows on the page only, the operator's corrections become reading lessons, and the pop-up for every new mail is gone | tests 10, 11, 13 to 17 | mesh-catalog: `mesh-mail`'s grants split (issue 1227) and the seat `mail-reading` declared; the module `mesh-mail-reader` (batches, mail entries, reading lessons, mail signals, health, the integration test); `mesh-mail-web`'s mail groups, mail signals, scam warnings and reading lessons; `mesh-mail-notifier` unassigned (issue 1418) | 1 |
|
||||
| 3 | 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) | 2 |
|
||||
| 4 | On the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; the thread's mail tools under a caller of their own granted reading only (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the grant split and the reading lessons); design 58's milestone 3 (issue 880) |
|
||||
|
||||
**Milestone 1, confirmed by the operator on 2026-10-19, is delivered;** its live proof on the laptop and the
|
||||
workstation (a search, an attachment, one send on the Approve) is still the operator's. Its 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). After its delivery four values were set, each on
|
||||
the operator's Approve: `mesh-mail-web`'s `reach` on its machine (`internal`), its assignment there, its
|
||||
`operators`, and its caller in `mesh-mail`'s `grants` for every account.
|
||||
|
||||
**Milestones 2 to 4, confirmed by the operator on 2026-10-20.** Their issues, one repository part each, in their
|
||||
order (each depends on the one before unless said):
|
||||
|
||||
- Milestone 2: 1227 (`mesh-mail`'s grants split), 1426 (the seat `mail-reading` and the module `mesh-mail-reader`),
|
||||
1427 (batches, mail entries and mail groups), 1428 (reading lessons and the seat's verbs; with 1427 after 1426),
|
||||
1429 (mail signals; after 1427), 1430 (the mail page; after 1428 and 1429), 1431 (the integration test; after
|
||||
1430 and 1227), 1418 (`mesh-mail-notifier` unassigned; after 1431 and test 13 live).
|
||||
- Milestone 3: 806 (Betterbird removed; after 1418).
|
||||
- Milestone 4: 1433 (`mail_search`'s folder case and `-any` switches; depends on none), 1419 (the thread's
|
||||
reading-only caller; a decision first, after 1227 and design 58's milestone 3, issue 880), 1432 (the mail conversation;
|
||||
after 1431, 1419 and issue 880).
|
||||
|
||||
Milestone 2 is one release of `mesh-catalog`: the grant split, the new seat, the new
|
||||
module and the page change ship together, and `mesh-mail-reader` is granted reading only after that delivery, on the
|
||||
operator's Approve; `mesh-mail-notifier` is unassigned only after test 13 passes live. Milestone 3 is issue 806.
|
||||
ADR 0379 §2's bar for removing Betterbird is milestone 1's; keeping Betterbird until mail reading (milestone 2) works,
|
||||
and not until the mail conversation, is the lead's proposal, for the operator to confirm with the milestones
|
||||
(playbook 04, step 5). Milestone 4 waits for threads and for the decision that names its thread's reading-only caller (issue 1419, which needs a decision first).
|
||||
@@ -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