From 221165c7120345824808a292ea7987d5dc4fe88b Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 10:47:44 +0100 Subject: [PATCH] Designs 25 and 55 answer the review by architectural domain: the bus module's events on its old sections, overdue creation raised by the watchman, the proven author, events and the database caught up after a restart, the bus module's integration test --- 00-META/glossary.md | 4 +- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 88 +++++++++++++++------ 03-DESIGN/01-to-be/55-the-knowledge-base.md | 64 +++++++++++---- 3 files changed, 118 insertions(+), 38 deletions(-) diff --git a/00-META/glossary.md b/00-META/glossary.md index 8980ad04..110f3a44 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -117,6 +117,8 @@ downstream of Module and Placement. **Decided by** ADR 0262, 0277, 0348. **Uses:** module, node, manifest (Module), assignment (Placement), provision (Provisioning), proposal (Operator and conversation). +- **template file** — a file a module ships with placeholders, such as `${config:}`, which the provisioner + client fills on each node (ADR 0349). Never a template of bus state (see [Homonyms](#homonyms)). - **config value** — anything a module reads that someone may change: a font size, a domain, an address. A module declares every one it reads under `config` in its manifest, with a type, a description, validation, whether it is trusted, optionally the predefined key it falls back to, and a default or @@ -1101,7 +1103,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical. | snapshot | the latest state of one ledger instance, from which a rebuild starts | **snapshot** of a ledger's state | Data | | | the bus's own backup of each stream | **stream snapshot** | Data | | template | a storage type with a configuration, asked for in `requires` | **template** of bus state | Data | -| | a file with placeholders a provisioner fills | **template file** | Config, Provisioning | +| | a file with placeholders a provisioner fills | **template file** | Config | | option | a setting of a template a module may change | **option** of a template | Data | | | one choice of an ask to a person | an ask's **option** | Operator and conversation | | effect | what an operation does to a ledger's state | **ledger effect** | Data | diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 2fac27f3..65485ee1 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -672,6 +672,7 @@ full, and the judge refuses a manifest that gives it: | log | stream | refuses when full; never rewritten; an entry at most 256 KiB; one subject per key | cap 1 to 8192 MiB (1024); age 1 to 3650 days (none) | | objects | object store | refuses when full; kept for good | cap 1 to 8192 MiB, required; a grant | | ledger | ledger | as log, plus its type and a snapshot per instance; its state and index buckets as state | cap; keeping days only in a ledger's own configuration | +| (none: a bare stream) | stream | the module's own subjects; within the mesh's bounds | its own configuration, which must give "when full" (refuse or drop the oldest), a cap of 1 to 8192 MiB and an age of 1 to 3650 days or none, and may give a grant; the judge refuses one missing any of the three | The template log is the **module log** of ADR 0297. The mesh, never the module, names the bus objects and their subjects (ADR 0201 §4): `_`, and a local name holding the separator is refused so no two @@ -690,14 +691,23 @@ modules' names meet. ``` - **The trigger is the judge's verdict** (ADR 0595 §4), heard as an event that carries the module's entries whole, - so the bus module never reads the indexer's state and never polls. The judge checks every entry once, at the merge + so the bus module never reads the indexer's state and never polls. It hears the verdicts on a durable consumer, so + one given while it was down is heard when it is back, and at start it runs `check` over every accepted manifest, + creating what is missing. The judge checks every entry once, at the merge check and at take-in (ADR 0578; ADR 0595 §8). - **The bus module has a process of its own** beside its tool bundle, with a declared health check. Its credential - creates streams and buckets under modules' names only, and deletes only through `drop`. + creates streams and buckets under modules' names only, and deletes only through `drop` and `recreate`. Its health +turns unhealthy, naming the cause, when its last pass over the verdicts or over fill is more than three minutes +old, so the only guard against a store filling never stops without a sound; another module's entry at 75 percent +raises that module's condition and leaves the bus module healthy. - **The binding travels on `created`** (ADR 0595 §1, §7). The grants part, which issues every membership, hears it - and writes the binding (the names on the bus) into the module's membership, so no part writes another's state. + on a durable consumer and writes the binding (the names on the bus) into the module's membership, kept in its own + state `memberships`, so no part writes another's state and a binding outlives the 30 days its event is kept. A module whose binding is missing at start fails loudly, naming the entry; it never starts blind. A `refused` - create hands no binding, and a refused or overdue creation raises a condition naming the module and the entry. + create hands no binding. **A refused or overdue creation is loud**: overdue means no `created` or `refused` within + five minutes of an accepted verdict that asks for the entry. The condition is raised by the watchman, which already + hears the judge's verdicts, never by the bus module, which cannot raise it while it is down; it names the module + and the entry, and a later `created` clears it. - **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no other module's: the new form has no readers. What another module needs of that data it asks through the owner's tools or seat verbs, and what the owner lets others hear is its declared events, subscribed to through @@ -726,20 +736,32 @@ only on the operator's warrant (ADR 0259 §6), asked through the operator channe the bus module takes a stream snapshot of the entry. A drifted entry is said, never quietly fixed: only a difference that grows is applied without a person. -**Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned`; +**Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned` +(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it); each carries the module, the local name, the bus name, the template, and the settings asked against those held. The -bus module asks for them with the template events at 30 days, the bound's top, because a drift or an orphan is read -back days later by a person; the conditions hold the live state. This settles where they are kept, which -ADR 0595 §12 left to the design. +bus module's own kept data stays on the old sections (ADR 0595 §10), because it cannot wait for its own process to +create it: it declares this stream in its `streams` section, kept 30 days (ADR 0363's bound), created by the grants +part as today, with a cap of 256 MiB; it drops its oldest when full, which loses no live state, because every drift, +orphan and fill stands as a condition until it clears. Thirty days, because a drift or an orphan is read back days later by a person; the conditions hold the +live state. The tool `history` of one entry reads them back: every event the bus module said of it, in order. This +settles where they are kept, which ADR 0595 §12 left to the design. **Fill is the bus module's one periodic read** (ADR 0595 §6): every minute, for every entry that refuses when full, because the bus says nothing as a store fills. The condition `full` is raised from 75 percent of the cap and cleared below it. The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old -key cleared. +key cleared. A bare stream that refuses when full is read the same way. **The conditions**, each naming the module +and the entry: `full` (cleared below 75 percent), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a +`drop` or by a manifest asking for it again) and a refused create (cleared by a later `created`). -**Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs answer every entry the bus module -created and its fill; `create`, `apply`, `recreate` and `drop` are its tools for a person, the last two asking the -warrant; its health names the process's last pass, the entries it could not create, and the entries over 75 percent. +**Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs, marked reads, answer every entry the +bus module created and its fill, and `history` its events for one entry; `create` and `apply`, marked acts, and +`recreate` and `drop`, marked dangerous and so granted only by name (ADR 0386 §6, ADR 0387), are its tools for a +person. Such a tool, in the bus module's bundle, holds no grant of those verbs' subjects, and neither does the tool +runner's account: it hands the request to the process on the process's own tool subject, carrying the caller the +bus proved, and the process acts only for a caller whose role grants that tool, for `recreate` and `drop` only once +the operator's warrant is held, and answers what it did. The bus seat's `apply` (a difference that grows) and the +ledger's `apply` (§ 12.4) are two verbs on two subjects, granted apart; this design writes them qualified. Its health +names the process's last pass, the entries it could not create, and the entries over 75 percent. ### 12.4 The ledger @@ -772,13 +794,19 @@ key-value buckets, and asked for with the template ledger. another writer, `apply` answers success with its operation's sequence and re-reads, never appending again. - **A full state bucket loses no operation** (§6): `apply` answers that the operation is taken, with its sequence, and that the state is behind because the bucket is full; the next `apply` after room is made catches it up. -- **The author is proven** (§7): `apply` stamps the owning module's authenticated account and the author it passes - on, the account that called the owner's tool, never words the caller chose. +- **The author is proven** (§7): `apply` stamps the owning module's authenticated account and the account that + called the owner's tool. The owner passes that author on by naming the tool call it serves: the tool runner, which + served that call and proved its caller, stamps the caller it proved, so the module cannot set the author to words of + its own; an `apply` naming no call in flight carries the owner's account alone, and one naming another module's + call is refused. A copy of old records (design 55 § 8.1) is stamped with the owner's account and keeps the old + author as a separate field marked imported, never as the proven author. - **Every instance has a snapshot** (§8): the latest state on a subject of its own, never expiring, written by every `apply`. It makes a kept ledger's rebuild fast and an expiring ledger's possible; a repair that finds it behind the state rewrites it. - **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module, held by the - tool runner and never by the module's own code, whose account may read its ledger and not publish on it. Only the + tool runner and never by the module's own code, whose account may read its ledger and not publish on it. The grants + part issues it with the module's membership on that machine, scoped to the subjects of that module's ledger + histories and snapshots, and removes it when the module is unassigned from the machine. Only the owner's account reads the history, the snapshot and the state. - **An index** is a second key-value bucket kept by a watcher in the owning module's process, rebuildable from the history; it raises a condition when it lags behind (§4). @@ -789,16 +817,22 @@ The new calls live in a small mesh-sdk package of their own, served by the tool modules that use them; the module library, which 112 modules import, does not change (ADR 0595 §9). They are the object store's (put, get, list, delete, info, watch); a stream's read-back and fill; a work queue's size and dead letters; and the ledger's: `apply`, `get`, `history`, `watch`, `query`, and its repairs `rebuild` (the state from -the history or the snapshot), `reindex`, `snapshot` and `lag` (ADR 0597 §10). mesh-sdk's catalog and membership +the history or the snapshot), `reindex`, `snapshot` and `lag` (ADR 0597 §10). The tool runner serves the repairs as +tools of every module that owns a ledger, limited to its own ledgers, so no owner declares them and the next ledger's +owner has them on its first day. mesh-sdk's catalog and membership packages change once for the typed `requires` entry and the binding, rebuilding about 30 modules. ### 12.6 The move, and the order of the build **The build, each step undoable on its own** (ADR 0595 §10): (1) mesh-sdk's typed `requires`, binding and calls -package; (2) the judge's rules; (3) the bus module's process, verbs and events, creating only for the new form; (4) -the tool runner's calls, `apply` and its ledger credential among them; (5) modules move, each owner first. Undoing -step 3 leaves what it created in place, and a module moved after it fails loudly on its missing binding. The -placement holds a moved module off a machine whose tool runner does not read the typed binding, and says so. +package; (2) the judge's rules; (3) the bus module's process, verbs and events, creating only for the new form, with the grants part's writing of +the binding on `created`; (4) the tool runner's calls, the ledger's `apply` and its ledger credential among them; (5) +modules move, each owner first. Each undo: undoing step 2 refuses the new form again, which no module uses before +step 5; undoing step 3 leaves what it created in place, and a module moved after it fails loudly on its missing +binding; undoing step 4 leaves a moved module without its calls, which the placement holds off that machine. The +placement holds a moved module off a machine whose tool runner does not serve the typed binding and the calls the +module asks for (a ledger's `apply` among them), and says so; where the placement reads what a tool runner serves is +left to the build of step 4 and tracked on issue 1966. **A module moves when it is next touched** (ADR 0595 §11), keeping its local names and carrying its old settings as options, so its bus objects and data stay and no drift is raised. It asks for each entry once: an old section @@ -820,13 +854,23 @@ are tracked by issue 1966. |---|---| | A manifest's bus state is valid, once | the judge's unit tests, each refusal naming the field: an unknown template, an option outside its bound, past values 0, "when full" given, a fixed setting given, a name used twice across storage types, two modules' bus names alike, a `reads` in the new form, an effect outside the fixed set (ADR 0595 §8, ADR 0596 §2, ADR 0597 §2) | | The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused | -| Drift, orphans and the warrant | live: a bucket whose settings differ raises `drifted` and its condition; `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused; an orphan is listed, raises `orphaned`, and goes only by `drop`, after a stream snapshot | -| Full | live: each template at its cap refuses, events drop the oldest; `full` raised at 75 percent and cleared below | +| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at 75 percent and cleared below | +| Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there | +| Overdue and stopped are loud | live: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old | +| A person's tools reach the process, and only by role | live: a role without the dangerous grant is refused `drop`'s tool; the tool runner's account holds no grant of `create`, the seat's `apply`, `recreate` or `drop` | +| The author is the proven caller | the ledger's integration test: an `apply` naming another module's tool call is refused; the author stamped is the caller the tool runner proved, whatever the module passed | | Only the owner reaches its data | live: another module's account reading an entry in the new form, a ledger's history, snapshot or state among them, is refused; a raw publish on a ledger's history is refused | | The ledger keeps its promises | one integration test against the real bus (ADR 0597's How it is checked): two writers race and one is refused; two interleaved applies leave the state at the higher sequence; a write cut between history and state is repaired; a full state bucket answers taken with its sequence and catches up; an idle instance whose operations expired rebuilds from its snapshot; `rebuild`, `reindex` and `lag` repair by hand; a forged author is refused; every `apply` leaves the snapshot equal to the state; an index behind raises its condition and `reindex` clears it | +| Only the owner applies | the ledger's integration test: another module's ledger `apply` on the knowledge base's ledger is refused | +| A bare stream is complete | the judge's unit test: a bare stream missing "when full", a cap or an age is refused, naming the field | | The calls rebuild only their users | a read of the delivery plan: only the importers of the calls package rebuild when it changes | | The move keeps data | live: a module moved from `state` keeps its bucket and data and raises no drift; a new entry in an old section is still created by the grants part; the count falls by one; at 0 a manifest using the section is refused and the foundation's are still accepted; a moved module is held off a machine whose tool runner does not read the typed binding | +**The test budget** (50 integration and 500 unit tests per repository): the design adds one integration test in the +nats module's repository and one in mesh-tools (the ledger), and table cases, not new functions, to the judge's unit +tests in mesh-delivery and to mesh-sdk's decoding tests. Each repository's count against the budget is read in the +prepare step of issue 1966, and a repository over it merges tests to stay within it. + **Acceptance.** The operator asks the bus module's `list` and sees every module's kept data with its template, its settings and its fill; a module that moved keeps its data; a drift or an orphan reaches the operator as a condition and is changed only on their word; and the knowledge base keeps its records as a ledger (design 55 § 10). diff --git a/03-DESIGN/01-to-be/55-the-knowledge-base.md b/03-DESIGN/01-to-be/55-the-knowledge-base.md index ff9c1422..2718e1ad 100644 --- a/03-DESIGN/01-to-be/55-the-knowledge-base.md +++ b/03-DESIGN/01-to-be/55-the-knowledge-base.md @@ -78,7 +78,7 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424). │ digest and the sequence of their write-section │ state asked one key per part asked of the judge and not yet answered: ticket, node, when │ state findings the whole-set findings standing now, and the told marks (told.) - │ events reviews one message per part reviewed: the digest judged, the verdict, the findings + │ stream reviews one message per part reviewed: the digest judged, the verdict, the findings ├ written through the tool runner's apply on the control-node, the only publisher on the │ ledger's history (ADR 0597 §3, §9) ├ in its memory the search index, built from the bus at start and kept by watching the @@ -116,15 +116,23 @@ is one operation the module hands to `apply`: among them `reserve`, `propose`, ` `withdraw`. The module checks its own rules first (an `accept` needs the operator's Approve where the record needs it, § 5; only the author writes a draft, § 4), then calls `apply`, which checks the operation against the record's type, appends it to the instance's history with the sequence it expects, derives the head by the operation's effect, -and writes the head and its snapshot. The author is the account the bus proved for the caller of the module's verb, -passed on by the module and stamped by `apply`, never a name the caller gave (ADR 0597 §7). +and writes the head and its snapshot. The author is the account the bus proved for the caller of the module's verb: +the module names the tool call it serves, and the tool runner stamps the caller it proved for it, never a name the +caller or the module gave (ADR 0597 §7; [design 25](25-the-bus-on-nats.md) § 12.4). + +**Every operation's event is sent once, after a restart too.** `apply` emits nothing; the module sends the event of +each operation it applied (`submitted`, `reviewed` and the rest) with the operation's sequence as the message's +id, so the bus drops a second send, and keeps the last sequence it sent per record in the state `findings` +(`sent..`). At start it sends the events of every later operation in each record's history, so a +module stopped between `apply` and its event loses none, and a consumer of `mesh-knowledge.reviewed` never waits for +one that was never sent. **A write is whole or refused, and an operation commits when its history takes it** (ADR 0597 §5, §6, changing ADR 0592 §10). The commit point of every change to a record is the operation appended to the ledger's history: a failure before it is appended refuses the call, and nothing is taken; a racing writer is refused by the bus, which expects the history's last sequence for the instance. Once it is appended, the call answers that it is taken, with its sequence; the head is derived by `apply` as an update of the revision it read, and a head behind its history is -caught up by the next `apply` or read. A full state bucket answers that the operation is taken and the head behind, +caught up by the next `apply` (ADR 0597 §6). A full state bucket answers that the operation is taken and the head behind, never that it was refused. So nothing an author was told was refused takes effect later. A reservation is an operation `reserve` on an instance with no history, so two sessions reserving at once get two numbers or one refusal. The module's other state (`drafts`, `asked`, `findings`) keeps the compare-and-set on the key's revision, @@ -169,7 +177,7 @@ waiting more than 30 minutes. A part waiting more than 30 minutes is asked of th channel, and the ask clears when the part is judged; the module stays healthy meanwhile, because a record waiting on its review is not the module failing (issue 508). The condition at 75 percent of a cap is the bus module's (ADR 0595 §6; [design 25](25-the-bus-on-nats.md) § 12). -**Events** (ADR 0592 §34) are kept 30 days; what a later reader needs is also in the version message. Each carries: +**Events** (ADR 0592 §34) are kept 30 days; what a later reader needs is also in the `submit` operation, in the ledger's history. Each carries: | Event | Carries | |---|---| @@ -249,12 +257,15 @@ costs only the sections it changes; the forms of ADR 0592 §1 to §9 that it rep - **The history** is kept for good: the ledger template's history refuses a new operation when full, rather than dropping its oldest, and the bus module raises its condition at 75 percent of the cap (ADR 0595 §3, §6). Each instance's snapshot, the latest head on a subject of its own, makes a rebuild fast (ADR 0597 §8). -- **A draft** is a key in the state `drafts` (§ 4). **A part's verdict** is a message on the stream `reviews`, and a - part asked of the judge and not yet answered a key in the state `asked` (§ 5). **The whole-set findings** standing +- **A draft** is a key in the state `drafts` (§ 4). **A part's verdict** is the operation `review` on the record's + history first, then a message on the stream `reviews`; a `review` with no message on `reviews` is sent at start + with the operation's sequence as its id, so the consumers of `reviews` miss none and hear none twice. A part + asked of the judge and not yet answered a key in the state `asked` (§ 5). **The whole-set findings** standing now are keys in the state `findings`, and what was told to the operator once is a key `told.` there. These keep their own forms (ADR 0597 §12), asked for in `requires`: the states by the template state; `reviews`, kept for good and heard only where granted (ADR 0592 §6, §8), as a bare stream with its own configuration (kept for good, -refusing when full, a grant), because no template both keeps for good and takes a grant (ADR 0595 §2). +refusing when full, a grant), because no template both keeps for good and takes a grant (ADR 0595 §2); the bus module's read of fill every minute +covers it like any entry that refuses when full. **Caps** (ADR 0592 §25, its object bucket's cap lapsing by ADR 0597 §12). Each is set from the move's dry run, within its template's bound (ADR 0595 §3): a history at most 8192 MiB, a state or a ledger's state at most 64 MiB. The @@ -270,6 +281,12 @@ starting figures, from today's 83 MB for 736 records with every version copying A write refused at a cap names the cap, and the module's health turns unhealthy naming it. +**The ledger's state is the tightest cap.** 64 MiB is already the template's top. A head holds no text, only fields, +clauses, links and its sections' titles and digests; estimated at most 8 KiB for a usual record, the 736 heads of +today need about 6 MiB, and about 1000 once designs and glossary entries become records (slice 3, § 9) about 8 MiB. +Those are estimates; the move's dry run measures the real total, and a total above 32 MiB is told to the operator +before the move. + **Nobody else reads any of it** (ADR 0596 §2, ADR 0597 §9, changing ADR 0592 §8 and §9). Only the knowledge base's own account reads its ledger's history, snapshot and state and its other kept data; the ledger's history is published on only by the tool runner's ledger credential. Another module asks the knowledge base's verbs (`get`, `history`, @@ -494,7 +511,7 @@ field by field and section by section, and the result is written on issue 426. ### 8.1 The move off the store The records leave the store once, and the database is dropped only after a check and the operator's yes -(ADR 0592 §12 to §16, the forms copied into changed by ADR 0597 §12): +(ADR 0592 §12 to §16; the forms the records are copied into are those of ADR 0597 §12): 1. **Order.** The ledger is built first, in the order of ADR 0595 §10: mesh-sdk's typed `requires`, binding and package of the new calls; the indexer's judge's rules; the bus module's process, verbs and events; the tool @@ -508,7 +525,9 @@ The records leave the store once, and the database is dropped only after a check 2. **Copy.** The move copies everything the database holds onto the bus, every record, version, clause, link, review, finding, ask with its warrant, reservation and told mark: each record's versions as operations on its instance in the ledger, in order (a `write-section` for each section a version changed, then its `submit`, then - the record's review, ask, warrant and acceptance), through `apply`, so each head is derived and never copied; + the record's review, ask, warrant and acceptance), through `apply`, so each head is derived and never copied; each operation is stamped with the knowledge base's own + account and keeps the database's author as a separate field marked imported, which `history` shows as the author + of an imported version; each standing finding into `findings`, each told mark as `told.`. A clause keeps its number, status and source as the database holds them (ADR 0321). It emits `moved` per record, writes nothing twice, and a second run changes nothing and says so. A dry run lists what it would write and the size each bucket and stream will need, from which the caps are set. @@ -523,9 +542,20 @@ The records leave the store once, and the database is dropped only after a check bus, so a restart never falls back to the database. A difference is told to the operator, and the module stays on the database. 6. **Rollback.** Until the database is dropped, the module writes each change to the database too, after `apply` - has taken its operation, so a rollback of the delivery reads a current database; a write the database refuses - after the ledger took it is retried and, past its tries, raises a condition naming the record, never answered as - refused. A rollback is safe only until the drop. This settles the rollback ADR 0597 §13 left to the design. + has taken its operation, so a rollback of the delivery reads a current database. What the database holds is + kept as a key in the state `findings`, `database.sequence`: the last history sequence written to it. At start, + and after a write the database refused, the module catches the database up from the history from that sequence + on, so a restart between `apply` and the database write loses nothing. A database still behind after three tries + raises the condition `database-behind`, naming each record it lacks and saying that a rollback now would lose + them, and never answers the author that the write was refused; the module's health is unhealthy while it stands, + the drop (step 8) is refused while it stands, and the catch-up tool copies the records again from the ledger and + clears it. A rollback is a person's act, and the condition is what tells them it is unsafe. **After a rollback**, + the older module wrote to the database alone: beside `database.sequence` the module keeps the newest version the + database held when it last wrote there, and a forward delivery that finds a newer version in the database refuses + every writing verb, + copies the changed records from the database onto the ledger as operations, runs the check of step 4 again, and + serves writes only once it is clean. A rollback is safe + only until the drop. This settles the rollback ADR 0597 §13 left to the design. 7. **Tell and ask.** Once the check is clean, the operator is told its result and asked, once, whether the database may be dropped, because dropping it removes data. 8. **Drop.** On the operator's yes, the delivery after it removes the module's requirement of a database, and the @@ -653,7 +683,7 @@ table-driven; one integration test starts the module whole against the real bus | two writes race on one draft | unit: the second compare-and-set loses, is retried, and is refused with the reason past its tries | | `submit` races `take-over` | unit: one wins; the other is refused naming the new author or the new version | | two reservations race | unit: two numbers, or one refused; never one number twice | -| a write cut between the history and the head | the ledger's integration test (design 25 § 12): the call answered that the operation is taken; the next `apply` or read derives the head; the module sends the event once | +| a write cut between the history and the head | the ledger's integration test (design 25 § 12.7): the call answered that the operation is taken; the next `apply` derives the head. The module's own integration test: stopped after `apply`, restarted, it sends the event once | | a failure before the operation is appended | unit: the call is refused, and no head, event or version follows later | | a change through the bus during the move | unit: read back from the database, so a rollback of the delivery reads a current database (ADR 0592 §14) | | a section changed after the operator's Approve | unit: the record is asked again; a version with every section unchanged is not (ADR 0592 §21) | @@ -674,8 +704,12 @@ table-driven; one integration test starts the module whole against the real bus | a form-only version | unit: accepted when only names and links changed and each target resolves; refused otherwise | | an unproven caller | unit: `open`, `write`, `submit`, `propose` and `take-over` refuse it | | the index behind | unit: health unhealthy while the index's sequence is behind the ledger's state; `ask` refuses "index not built" | +| a forward delivery after a rollback | unit: a database changed by the older module is found, the writing verbs refused, the changes copied and checked, then writes served | +| a restart between `apply` and the database write | unit: the module is stopped after `apply` and before the database write, restarted, and the database matches the history; health showed it behind meanwhile | +| the database refuses past its tries | unit: `database-behind` names the record; the drop is refused while it stands; the catch-up tool clears it | +| a restart between `apply` and its event | unit: the module is stopped after `apply` of a `review`, restarted, and sends one `reviewed`; a second start sends none | | the move as operations | unit: each record's versions copied as `write-section` and `submit` operations in order; the derived head equals the database's; a second run appends nothing | -| the start | integration: the time to a built index measured and reported | +| the start | integration: the time to a built index measured and reported, failing above 40 seconds, the estimate's top | | A section is a part with a title | a table test, one case per refused name and text of § 2.2; a form-only version whose text changed beyond its links is refused | | Every bucket has a tool, and health says the index | the manifest test compares tools and events with the code; a unit test of the health before and after the index is built |