diff --git a/03-DESIGN/01-to-be/39-milestones/bus-and-switch.md b/03-DESIGN/01-to-be/39-milestones/bus-and-switch.md index f5a732b8..6802d066 100644 --- a/03-DESIGN/01-to-be/39-milestones/bus-and-switch.md +++ b/03-DESIGN/01-to-be/39-milestones/bus-and-switch.md @@ -44,32 +44,45 @@ readings, and never a session. Issue 1409 (no warning before a licence's limit) part, the main file and the glossary. The order between them is the module graph's, each an edge the delivery plan derives (§50). -**Open design questions.** Three questions no accepted record settles; the lines that wait on one say so, and develop -does not start on them before the operator's answer is written here (playbook 02, *Answering a design question*): +**The lead's answers, by the principles.** Four questions no accepted record settled were answered on 2026-11-11 by +the lead of issue 1409, against hq's principles, as the operator's way of working since 21:15 that evening has it (the +lead answers develop and design questions against the principles; no decision record is written for a detail). Each +line they reach names its answer: -- **Q1, the vault's own-account rule.** ADR 0550 §3 moves the grants and tokens into the vault and waits until the - licence manager and each agent module run under a bus account of their own (ADR 0349 §11, issue 569); both run - today in their machine's tool runner, under its account. Lines §15 to §20 wait on it. -- **Q2, the record behind the switch.** ADR 0183 §21 (moving a consumer is a person's act), ADR 0206 §16 (no - `licence.switched` event) and ADR 0206 §18 (every later change is a person's act) are in force; the operator's - answers of 2026-11-11 change them. A record changing those three clauses comes before develop (playbook 02, - *Changing a decision*). Lines §24 to §33 are written from the operator's answers and wait on it. -- **Q3, how the first start reads today's rows.** The carry-over (§11 to §14) reads the store database with the - module's own credential, and a build whose manifest no longer requires `postgres-database` is composed with no - credential for it, while a retiring provision keeps its account only until the operator's Approve (ADR 0570 §5). - Line §11's source waits on it. +- **A1, the vault's own-account rule** (§15 to §17). ADR 0550 §3 waits until the licence manager and each agent + module run under a bus account of their own (ADR 0349 §11, issue 569); both run in their machine's tool runner, + under its account. Settled by principle 6 (inside the mesh every agent and module is trusted; who may do what is not + a question) and the operator's instruction that who may do what is not the operator's question (approved + 2026-11-11 21:15 CET): the vault serves licence secrets to the tool runner's account. Its check that the asking + machine is the node the binding names stays, as a guard against mistakes, not as a permission. +- **A2, the switch without a record** (§24 to §33). ADR 0183 §21 (moving a consumer is a person's act), ADR 0206 §16 + (no `licence.switched` event) and ADR 0206 §18 (every later change is a person's act) are in force. The operator + decided, 2026-11-11 about 21:10 to 22:20 CET: switch automatically at 99 %, on all nodes, configurable; and said that + night not to write records for what they decided. So the operator's answer of 2026-11-11 replaces those three + clauses for this module, and no record is written. +- **A3, how the first start reads today's rows** (§1, §11, §14). A build whose manifest no longer requires + `postgres-database` is composed with no credential for it, and `mesh-store.query` refuses the grant column. Settled + by principle 7 (the simplest thing that works): this milestone keeps `requires postgres-database`, and the `secret` + `grant-key` that opens the grants at rest, as the carry-over's read-only source; dropping both, and the database's + retirement on the operator's Approve, is the module's next change once `licences_carried` (§44) shows the + carry-over done. The delivery gap this names: no path hands a build the credential of a provision its manifest + dropped, so a module cannot read its retiring database at the first start of its new form. +- **A4, no wait for design 47's milestone G** (§9, §50). Settled by principle 7: this milestone ships soon, built only + on what main holds today (mesh-sdk, the vault, the operator channel, and today's way a module says a condition, + ADR 0437 §9), so it uses neither G's writer nor its stream. ## The kept data on the bus -1. **The manifest asks for its kept data by template and requires no database.** `requires` loses - `postgres-database` and the `secret` requirement `grant-key` (its key ends with the grants' move, §20); `contributes`, - `binds`, `secrets`, the resources `database-url` and `prepare` and the run-once step `prepare` go with them. It - gains one `bus-state` entry per piece of kept data, each in the typed long form (ADR 0595 §1): the local name, the - storage type, the template and its options, as §2 to §9 list them. The old section `state` (today `bindings`) and - the `reads` entry `claude-code.holdings` go: the module moves to the new form whole (ADR 0595 §11), keeping the local - name `bindings` so its data stays. Why: the operator, 2026-11-11, and principle 1; the templates are ADR 0595 §2. - How it is checked: the manifest validator accepts the manifest at the merge check; a catalog test finds no - `postgres-database`, `reads` or old `state` in it. +1. **The manifest asks for its kept data by template, and keeps its database only as the carry-over's source.** + It gains one `bus-state` entry per piece of kept data, each in the typed long form (ADR 0595 §1): the local name, + the storage type, the template and its options, as §2 to §9 list them. The old section `state` (today + `bindings`) and the `reads` entry `claude-code.holdings` go: the module moves to the new form whole (ADR 0595 §11), + keeping the local name `bindings` so its data stays. `requires` keeps `postgres-database` and the `secret` + `grant-key`, with their `contributes`, `binds`, `secrets` and the resource `database-url`, read only by §11 and + never written (A3); the run-once step `prepare`, which makes the tables, goes, since nothing writes them again. + `uses` gains `operator-channel` (§9, §28). Why: the operator, 2026-11-11, and principle 1; the templates are + ADR 0595 §2; A3. How it is checked: the manifest validator accepts the manifest at the merge check; a catalog test + finds no `reads`, old `state` or `prepare` in it; a unit test finds no write to the database in the code. 2. **State `licences`: one key per licence, no secret in it.** Template `state`, option `history` 1. The key is the licence's key form (§10). The value: `name`, `kind` (`subscription` or `api-key`), `account` (the account id), `address`, `organisation`, `refreshFingerprint`, `accessExpiresAt`, `refreshExpiresAt`, `failures` (refreshes @@ -118,14 +131,20 @@ does not start on them before the operator's answer is written here (playbook 02 for each table of today's database the rows read and the entries written. Written once, at the end of §11; never deleted, since its presence is what keeps a second start from carrying over again. Why: ADR 0437 §7's way of carrying a module's state over once. How it is checked: §13. -9. **State `findings` and the events `finding-events`.** The manager is an observer (design 47 G §383): it asks - the state `findings` by the template `state` and the stream `finding-events` by the template `events`, with - `max-age-days` 7 and `max-mib` 16, its field `events` listing `finding-written`, `finding-cleared` and - `findings-stated`, as design 47 G §529 has every part that is not of the foundation declare them; `consumes` - gains `mesh-conditions.findings-asked`. It writes its findings (§29 to §31, §33) with mesh-sdk's finding writer - (design 47 G §461), which states them at its start and on the keeper's ask. Why: a condition reaches the operator - only from a finding the condition keeper hears (ADR 0564 §2 as design 47 G §383 changes it). How it is checked: - whole (manager), a finding written raises its condition at the keeper and its clearing clears it. +9. **Its conditions are said on the operator channel, and kept in its own state `conditions`.** The manager says + each condition of §11, §29 to §31 and §33 as any module does on main today (ADR 0437 §9): published on + `mesh.seat.operator-channel.accept.condition.claude-licence-manager` (the subject the operator channel's contract + names, through its client in mesh-contracts), carrying the condition's `key`, `severity` (`urgent` or `warning`), + `scope` and `id`, `raised` or `cleared`, its `summary` and ADR 0253's words (`headline`, `needs`, `explanation` + opened by its verdict, `resolved` and `actions`); the router alone decides whether and where the operator is told. + It keeps each condition it said raised in its state `conditions` (template `state`, option `history` 1, one key per + condition key, its body and `since`), written before it is said and deleted when it is said cleared. At its start + it judges each kind anew from its own state and readings, says raised again each it still holds and cleared each + key of `conditions` it no longer holds. A raise or clear is said again only when what it says changes (ADR 0289 + §1). Why: A4 (today's way a module raises a condition, without G's writer); the state lets a restart clear what + it said. How it is checked: unit, a condition is written in `conditions` and published once with these fields; + a start with a key it no longer holds says it cleared; whole (manager and the router on a test bus), a raised + condition reaches the desk channel and its clearing clears it. 10. **A licence's key form.** A licence's name is its account's address, or its id where it has none, or the name given at `adopt` for an API key (today's `NameFor`); a name holds characters a key may not. Its key is the name with each `@` written as `_at_` and every other character outside letters, digits, `-` and `.` written as `_`. An @@ -140,13 +159,13 @@ does not start on them before the operator's answer is written here (playbook 02 `carried` has no key `postgres`, the manager, before it hears an event, reads a holding report, refreshes or reads usage, reads every row of the six tables of its database `claude_licences` and writes them as §12 says, then the key `carried.postgres`. Until that key exists it serves its read verbs and tools only, answering every other - with "the carry-over has not ended", and writes the finding `carry-over-failed` (urgent; "Needs you: the licence + with "the carry-over has not ended", and says the condition `carry-over-failed` (§9; urgent; "Needs you: the licence manager could not carry over its licences; no consumer is moved and no token is refreshed until it does", naming the table and the error) when a read or a write fails, cleared when the key is written. Why: a manager that ran on an empty state would take every holding report for a candidate (ADR 0206 §5) and exchange refresh tokens it - already holds; failure is loud (principle 3). Its source, the database read with the module's own credential, - waits on Q3. How it is checked: whole (manager, a store database filled from a copy of today's rows), every row - is carried and the key written; with the database unreachable the finding is raised and no vendor call is made. + already holds; failure is loud (principle 3). Its source is the database read with the module's own credential, which this milestone keeps for it alone (A3). How it is checked: whole (manager, a store database filled from a copy of today's rows), every row + is carried and the key written; with the database unreachable the condition is raised and no vendor call is made. A table that does not + exist (a fresh install) is read as no rows. 12. **What each table becomes.** - `licence` → a key of `licences` (§2) per row; each row's `sealed` grant is opened with today's `grant.key` and handed to the vault as §15 says, its generation 1; the row's other columns become the key's fields. @@ -165,44 +184,48 @@ does not start on them before the operator's answer is written here (playbook 02 log's last entry for that subject and skips rows at or before its `at`). Why: ADR 0437 §7. How it is checked: whole, a second start writes nothing; a start stopped after `bindings` and started again leaves every count equal to the rows read. -14. **The database's fate, and that this delivery moves data.** Once `carried.postgres` exists and §20's scan finds - no grant left outside the vault, the database `claude_licences` is retired: the provision is retiring once no - build a want names requires it, and its account is removed only on the operator's Approve (ADR 0570 §5); the - database is renamed aside by the postgres module's `postgres_retire_database` and deleted only by a person - (ADR 0230 §14). The delivery of this milestone moves data, so it is said to the operator before it starts +14. **The database's fate, and that this delivery moves data.** In this milestone the database `claude_licences` + stays, read once by §11 and never written. Once `licences_carried` (§44) shows the carry-over done and §20's scan + finds no grant left outside the vault, the module's next change drops `requires postgres-database` and + `grant-key`; the provision is then retiring, its account removed only on the operator's Approve (ADR 0570 §5), + and the database is renamed aside by the postgres module's `postgres_retire_database` and deleted only by a + person (ADR 0230 §14). The delivery of this milestone moves data, so it is said to the operator before it starts (which module, its machine, that its licences, bindings and grants move to the bus and the vault), and the - manager's machine is backed up before the module moves, as for every module that holds data. Why: ADR 0570 §5, - ADR 0230; the operator's instruction that a delivery moving data is said first. How it is checked: the delivery's - plan names the backup; after the delivery `mesh-store.databases` lists `claude_licences` until the operator's - Approve, then its renamed-aside form. + manager's machine is backed up before the module moves, as for every module that holds data. Why: A3, + ADR 0570 §5, ADR 0230; the operator's instruction that a delivery moving data is said first. How it is checked: + the delivery's plan names the backup; after the delivery `mesh-store.databases` still lists `claude_licences`. ## Grants and tokens in the vault -*Lines §15 to §20 build ADR 0550 §3 and wait on Q1.* +*Lines §15 to §20 build ADR 0550 §3, on the tool runner's account (A1).* 15. **The vault holds two secrets per licence, handed to it by the manager alone.** `licence..grant` holds, for a subscription, the refresh token, the access token and both expiries, and for an API key the key; `licence..access` holds a subscription's access token and its expiry, or the API key. The vault serves a new verb on its seat, `licence-put`: `licence`, `part` (`grant` or `access`), `generation` and the value - sealed to the vault's key; callable by the holder of the seat `anthropic-licence-manager` only, refused for any - other caller naming the rule; it keeps the newest generation and refuses an older one; and it says its event + sealed to the vault's key; answered for a call from the machine that holds the seat `anthropic-licence-manager` + (as the seat's placement names it), and refused from any other machine naming the rule, a guard against a + mistaken caller rather than a permission (A1); it keeps the newest generation and refuses an older one; and it says its event `secret-changed` with the secret's name and generation, never the value. The manager calls it after every adoption, rotation and carry-over (§12), the `grant` first. Why: ADR 0550 §3 (the manager hands what it gets back to the vault, keeping nothing at rest); the names, the verb and its fields are the design's choice. How it is - checked: unit (vault), `licence-put` from another caller is refused and an older generation is refused; unit + checked: unit (vault), `licence-put` from another machine is refused and an older generation is refused; unit (manager), a rotation calls it twice and keeps no token after the call returns. 16. **The manager reads a grant from the vault when it needs one, and keeps it only for that act.** To refresh, adopt again or read usage, the manager calls `mesh-vault.get` for `licence..grant`, sealed to its own public key, under the licence's lease (§4), and drops the value when the act ends. Why: ADR 0550 §3, ADR 0183 §6. How it is checked: unit, the manager's state and log hold no token after a rotation. 17. **A consumer's agent module gets its token from the vault, which serves only the licence a binding names.** The - vault answers `mesh-vault.get` for `licence..access` only to an asker whose machine is the node the - asking consumer's binding names, and only for the licence that binding names. It learns the bindings from the + vault answers `mesh-vault.get` for `licence..access` to the tool runner's account of a machine, + and only when that machine is the node the asking consumer's binding names and the licence is the one that + binding names: a guard against a node applying another's licence by mistake, not a permission (A1). Its rule that a + value goes only to a module under a bus account of its own (ADR 0349 §11) does not apply to the licence + secrets. It learns the bindings from the manager's `binding.changed` (§21), which it consumes, and at its start from `anthropic-licence-manager.bindings`, its first view, keeping them in its own state `licence-bindings` (template `state`, one key per consumer, the licence and generation, deleted on a release). The agent module never receives a refresh token (ADR 0206 §11). - Why: ADR 0550 §3; ADR 0596 §2 (the vault asks the manager, never reads its state); the state is the design's - choice. How it is checked: unit (vault), a token is refused to a machine no binding names and for a licence its + Why: ADR 0550 §3; A1; ADR 0596 §2 (the vault asks the manager, never reads its state); the state is the + design's choice. How it is checked: unit (vault), a token is refused to a machine no binding names and for a licence its binding does not name (the main file's acceptance test), and given after a `binding.changed` naming it. 18. **`current` answers which licence and its health, never the token.** The seat verb `current` takes `consumer` and answers `licence`, `kind`, `generation`, `health` (`ok`, `failing`, `expiring`) and `accessExpiresAt`; its @@ -248,7 +271,8 @@ does not start on them before the operator's answer is written here (playbook 02 ## Switching on usage -*Lines §24 to §33 are the operator's answers of 2026-11-11 and wait on Q2's record.* +*Lines §24 to §33 are the operator's answer of 2026-11-11, which replaces ADR 0183 §21 and ADR 0206 §16 and §18 for +this module (A2): the manager moves consumers by itself on its own readings, and says every switch as an event.* 24. **The manager reads each subscription's usage on a cadence, and keeps the reset times.** Every `usage_minutes` (§33), for each licence of kind `subscription`, the manager reads the vendor's usage with the @@ -283,33 +307,32 @@ does not start on them before the operator's answer is written here (playbook 02 says it once per act that moves consumers, by `switch` or by §26: `from`, `to`, `consumers` (the list), `by` (a person's name or `usage`), `why`, and for §26 `window` (`session` or `weekly`), `pct`, `resetsAt` and the target's `room`. The same fields are the log entry `switched` (§7). Why: the operator, 2026-11-11 ("each switch said as an - event"); this changes ADR 0206 §16 (Q2). How it is checked: unit, one event per act with the consumers moved. + event"); this replaces ADR 0206 §16 for this module (A2). How it is checked: unit, one event per act with the consumers moved. 28. **Every switch the manager makes is told to the operator.** After an act of §26 the manager calls `operator-channel.tell` (ADR 0380): `subject` the seat `anthropic-licence-manager`, `headline` " moved to ", and one line " reached % of its window, which resets at