From 6e3373c879cd9391d5992e9a8f8774a35a49f04b Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 25 Sep 2026 22:46:10 +0200 Subject: [PATCH] Design pass: address the review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0113 — the plaintext claim was false under its own mechanism: handing a provider's answer to the controller puts every secret on the broker and in the controller in the clear. The provider now seals each secret field itself, to the consumer node's public key the mesh hands it, and the controller carries sealed fields it cannot open. That is stricter than today, where the controller holds every minted credential in the clear. Option 3 (plaintext to the controller) is recorded and rejected. The foundation exception now covers root-secret rotation (0085) and forms like the broker admin's hash, so no phase claims to remove the broker's bootstrap step. To-be 24 and 13 are named among what it amends. 27 — resolution is consistent with 0110: co-location and the only provider apply only where no seat delivers the provision, so an unheld seat is refused even with one provider. The secret-field rule now matches 0086 exactly (a declared env-file, never a container environment value). The seat placeholder is the controller's, and the one module reading it moves to a host port. Contracts are held by the controller and written down in phase 1, so they can be checked; every rule has a check. An operator's secret is still the operator's, with the vault as custodian. Which seats a module holds is listed as not settled. 0110 — the unheld-seat-with-one-provider case and the one-answer-for-everyone rule have checks; the claim about moved manifests is corrected. 26 — the table governs and the code catches up, not the reverse; scope and capacity agree with the glossary; moving a seat is described as it really is today. 0112 — aligned with 27, and lists 0049 and 26 among what it changes. Issue 118 is renumbered 119: another branch took 118 first. 'Control-plane' is gone from 0110 and 0111. --- ...s-a-module-assignment-from-a-closed-set.md | 10 +-- ...d-source-is-on-the-git-seat-or-external.md | 2 +- ...e-definition-names-no-node-mesh-or-path.md | 21 ++++-- ...hat-it-provides-and-the-mesh-carries-it.md | 66 +++++++++++------ 03-DESIGN/01-to-be/26-the-seats.md | 28 ++++--- .../27-a-module-requires-the-mesh-resolves.md | 74 ++++++++++++------- .../00-report.md | 2 +- 7 files changed, 128 insertions(+), 75 deletions(-) rename 04-ISSUES/{118-a-module-definition-decides-where-its-files-live => 119-a-module-definition-decides-where-its-files-live}/00-report.md (98%) diff --git a/02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md b/02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md index a0d18cc..6800228 100644 --- a/02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md +++ b/02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md @@ -24,8 +24,7 @@ The names in use were each invented by the module that claims them: `the-showcas **Nothing can say what a mesh has, or who fills it.** There is no seat table and no command that lists seats. Holdings are assembled while planning, one node at a time, and discarded afterwards. The only way to answer "which seats does this mesh have, and which module holds each" is to read -every manifest in two repositories, because the core modules' manifests moved into the controller's -own repository ([ADR 0069](0069-a-module-is-a-repository-and-a-path.md)), and then the controller's +every manifest in two repositories, because the controller's own manifest lives in its own repository ([ADR 0069](0069-a-module-is-a-repository-and-a-path.md)), and then the controller's code, because one module it ships has its manifest composed there. While this record was being prepared, that enumeration was done by hand, and it missed both of the last two sources: eleven claims were reported where there are thirteen. @@ -163,10 +162,11 @@ question real. | Rule | Checked by | |---|---| -| The set is closed, and every entry names its decision | A control-plane unit test asserts the set's size and a non-empty decision for every entry. | +| The set is closed, and every entry names its decision | A controller unit test asserts the set's size and a non-empty decision for every entry. | | A claim outside the set is refused | Manifest-validation tests for an unknown seat, the wrong scope, and a delivering seat whose claimant does not provide. | -| Every module in use claims a seat in the set | A control-plane test parses every catalogue manifest and fails on any refused claim. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). | -| The holder answers among several providers | Resolution tests: two providers with the seat held, two with a pin overriding the seat, two with the seat unheld (refused). | +| Every module in use claims a seat in the set | A controller test parses every catalogue manifest and fails on any refused claim. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). | +| The holder answers for a provision its seat delivers | Resolution tests: two providers with the seat held; a pin overriding the seat; a second provider on the consumer's own machine, where the holder still answers; the seat unheld with two providers, and with **one** provider, both refused naming the seat. | +| A seat delivers only a one-per-mesh provision | A controller unit test: `mesh-store` and `mesh-broker` deliver nothing, so a database consumer is still served by co-location. | ## References diff --git a/02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md b/02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md index 0f087af..b021ea5 100644 --- a/02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md +++ b/02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md @@ -84,7 +84,7 @@ the controller's job, because only the controller knows where the seat's holder | Rule | Checked by | |---|---| -| A seat source records no address | A control-plane test resolves a seat source and asserts the recorded repository is the path alone. | +| A seat source records no address | A controller test resolves a seat source and asserts the recorded repository is the path alone. | | The URL comes from the holder | A test composes the clone URL from a holder's node address and served `git` scheme and port, and a second where the port was moved on the node. | | An unheld seat refuses self-hosted builds only | A test with no holder: `--self` is refused naming the seat; an external URL passes through unchanged. | diff --git a/02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md b/02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md index afd4469..f2743c3 100644 --- a/02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md +++ b/02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md @@ -11,7 +11,7 @@ extends: 0046-a-module-configuration-is-its-assignments-not-its-manifest.md ## Context -[Issue 118](../04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md) found +[Issue 119](../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md) found **789 host-path strings in 70 of the catalogue's 71 module definitions.** Each definition chooses where on the machine its directories, mounts, bindings, secrets, env-files and received files live, and often repeats that path in an environment variable or in code. Mounts are checked against what @@ -67,19 +67,20 @@ unresolved requirement and what could answer it, all at once. |---|---|---| | **another module** | a database, a bucket, a vhost, a secret, a route | provisions and bindings | | **the node's host** | a directory, a port, facts about the machine | resource paths, `${port:}`, `${machine:}`, facts | -| **the mesh** | the module's identity and names, and the delivery of every answer | derived logins, minted delivery | +| **the mesh** | the module's identity and names, and the delivery of every answer | derived logins and generated names; the controller's delivery | | **the operator, through the assignment** | a value a person chooses: a public name, a greeting, an external key | settings, carried literals | A module provider is chosen as [ADR 0084](0084-which-provider-serves-a-consumer.md) and [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say: a pin, then the holder of a -seat that delivers the provision, then co-location, then the only one. A host provider is always the -module's own node, because a host path or a port means nothing on any other. An operator value is the -assignment's, or the requirement's default, or unresolved. +seat that delivers the provision; for a provision no seat delivers, co-location and then the only one. +A host provider is always the module's own node, because a host path or a port means nothing on any +other. An operator value is the assignment's, or the requirement's default, or unresolved. **A person's value stays cheap.** An operator requirement's contract is a type and, optionally, a default. It needs no provider module, no grant and no credential. An operator value that is secret, -like an external API key, is kept by the vault as an operator-delivered value -([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)). +like an external API key, is still the operator's: the vault is where it is *kept*, as an +operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)), not who +provides it. **What a provider answers with is its contract's fields**, made by the provider and carried back to the consumer by the mesh ([ADR 0113](0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)). @@ -127,6 +128,10 @@ On acceptance, each of these is amended by a record of its own, not edited: checked as resolved rather than as a path the definition declares. - [ADR 0084](0084-which-provider-serves-a-consumer.md): a provider is a (node, instance) pair, not a (node, module) pair, so a consumer can name one of two instances on one node. +- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md): a login is built from the + instance, which gets a short form under the same rules as a slug, so it still fits the tightest + backend. +- [To-be 26](../03-DESIGN/01-to-be/26-the-seats.md): a seat's holder is an instance. - The [glossary](../00-META/glossary.md): *provision* widens from "a service one module provides" to a requirement answered by any of the four providers, and *instance* is added. Neither lands while this record is only proposed, because the glossary is the authority on the words in use, not on words @@ -168,7 +173,7 @@ On acceptance, each of these is amended by a record of its own, not edited: ## References -- [Issue 118](../04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md): the evidence +- [Issue 119](../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md): the evidence - [ADR 0038](0038-the-mesh-assigns-the-port.md), [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md), [ADR 0084](0084-which-provider-serves-a-consumer.md): the parts already unified - [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md): which module provider answers diff --git a/02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md b/02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md index b31d10a..fe81eb3 100644 --- a/02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md +++ b/02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md @@ -27,7 +27,9 @@ repeatedly: back, and say so in their code. - **Some contracts need a value the controller cannot make.** The broker needs its admin password in a hashed form the mesh's plain secret delivery cannot produce, so a module-specific bootstrap step - was written to derive it. A provider making the value to its own contract would not need one. + was written to derive it. That admin is a foundation credential, which genesis makes, so this + record does not remove that step. It is the clearest instance of the general problem, though: a + contract can require a form only the party that understands the software can produce. - **The vault is a ledger.** A module's own secret is "a `secret` provision the controller mints and the vault records" ([ADR 0085](0085-a-secret-is-a-provision.md), as amended). The one module whose job is secrets generates none, and cannot apply a policy (length, form, lifetime) because it never @@ -43,14 +45,21 @@ this one kind of answer on the provider's behalf" is a special case the model wo Rejected. The vault stays a ledger, contracts needing a derived form keep needing bespoke steps, and a data provision stays unable to answer at all. -**2. The provider makes the value and hands it to the consumer itself.** Rejected. This is the fault -0048 fixed. The provider cannot seal to the consumer's node, the two may be on different machines, -and a key both ends hold is the distribution problem one level down. +**2. The provider makes the value and hands it to the consumer itself.** Rejected. The two may be on +different machines with no path between them the mesh has agreed to, and a provider reaching +consumers directly is a second delivery system beside the mesh's. 0048's objection, a symmetric key +both ends hold, is not what rules this out: node keys are asymmetric, and a provider can seal to a +node's public key without sharing anything. -**3. The provider makes the value and gives it to the mesh, and the mesh carries it to the consumer.** -Chosen. The provider answers over its own scoped account. The controller, which already seals to -every node, seals each secret field to the consumer's node and delivers it the way it delivers -everything else. +**3. The provider makes the value and gives it to the controller in plaintext, which seals and +delivers it.** Rejected. It works, and it puts every secret in the controller's memory and on the +broker in the clear, a surface today's design does not have for provider-side values. + +**4. The provider makes the value, seals each secret field to the consumer's node itself, and the +mesh carries the sealed answer.** Chosen. The mesh already tells a provider who each consumer is and +where; it also hands it the consumer node's public key. The provider seals to it, and answers over +its own scoped account. The controller delivers the sealed fields as it delivers everything else, +and can open none of them. ## Decision @@ -58,11 +67,13 @@ everything else. the fields its contract names: a password, an access key, a site id, a registered name, a hashed admin secret. The vault generates the secrets it provides, to their contract, and rotates them. -**The mesh carries the answer back.** The provider hands its answer to the controller over its own -scoped broker account. The controller seals every field the contract marks secret to the consumer's -node, and delivers the answer as the consumer's resolved values. A provider never reaches a consumer -directly. Plaintext exists on the provider's machine, as it does today, and on the consumer's, and -nowhere between. +**The provider seals, and the mesh carries.** With each consumer, the mesh hands the provider that +consumer node's public key. The provider seals every field its contract marks secret to it, and +hands the answer to the controller over its own scoped broker account. The controller delivers the +answer as the consumer's resolved values, and can open none of the sealed fields. A provider never +reaches a consumer directly. A secret's plaintext exists where it is made, on the provider's machine, +and where it is used, on the consumer's, and nowhere between. That is stricter than today, where the +controller holds every minted credential in the clear when it makes it. **Who a consumer is stays the mesh's.** The login a consumer presents is the mesh's derivation, which both ends agree on by construction ([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md), @@ -72,9 +83,11 @@ issue 023). A provider makes what a consumer is *given*, never what it is *calle and the mesh redelivers it. An operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)) is still never replaced by the mesh: its provider is the operator. -**Genesis is the one exception.** The foundation's own credentials and the vault's own access exist -before any provider can answer. Genesis mints those itself, seals them to the operator key as today, -and hands them to their holders. Nothing else is minted by the controller. +**The foundation is the one exception.** The foundation's own credentials, the vault's own access and +the mesh's root secrets exist before any provider can answer. The controller mints those: at genesis, +and when an operator rotates a root secret ([ADR 0085](0085-a-secret-is-a-provision.md)). It seals +them to the operator key as today, including any form the foundation's software needs, such as the +broker admin's hash. Nothing else is minted by the controller. ## What this changes in earlier records @@ -85,7 +98,11 @@ On acceptance, each of these is superseded or amended by this record, not edited option 2 is rejected here. - [ADR 0085](0085-a-secret-is-a-provision.md) is amended: the vault generates a module's own secret rather than recording one the controller minted. "The vault stores no plaintext, ever" stands. It - makes a value, hands it to the mesh and keeps only what it keeps today. + makes a value, seals it, hands it to the mesh and keeps only what it keeps today. +- [To-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) and + [to-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) are amended: both describe + the controller minting a module's credentials and rotating them. After this, the controller mints + only the foundation's, and a provider rotates by answering again. ## Consequences @@ -96,11 +113,11 @@ On acceptance, each of these is superseded or amended by this record, not edited - The provider harness in the SDK changes: an adapter's create answers with its contract's fields instead of returning nothing, and every provider in the catalogue moves to it. This is one migration per provider, the cost 0048 named for changing the contract, and it is paid once. -- The controller gains the return path: receiving an answer on a provider's account, sealing its - secret fields, and delivering them. Data provisions gain the same path, so the analytics and DNS - providers can finally answer. -- Bespoke derivation steps, like the broker's admin-hash bootstrap, become the provider's own - answer and can be removed. +- The controller gains the return path: receiving a sealed answer on a provider's account and + delivering it. Each contribution gains the consumer node's public key. Data provisions gain the same + path, so the analytics and DNS providers can finally answer. +- A contract needing a derived form is met by its provider. The broker admin's hash stays with the + controller, because that admin is a foundation credential. - **What got harder:** a provider that is down cannot hand out credentials, where today the controller could mint one in its absence. That is honest, because a credential for a resource that does not exist yet was never usable, but it moves a failure from later and silent to earlier and visible. @@ -109,10 +126,11 @@ On acceptance, each of these is superseded or amended by this record, not edited | Rule | Checked by | |---|---| -| A provider's answer reaches the consumer sealed to its node | A controller test delivering a provider's answer: each secret field is sealed to the consumer's node key and to nothing else. | +| A provider's secret fields are sealed before they leave it | An SDK test: an answer whose contract marks a field secret cannot be handed over unsealed. A controller test: an unsealed secret field in an answer is refused, not delivered. | +| A provider's answer reaches the consumer sealed to its node | A controller test delivering a provider's answer: each secret field opens with the consumer node's key and with no other, the controller's included. | | A consumer's identity is still the mesh's | A resolution test: the login a consumer presents is the mesh's derivation, whatever the provider answers. | | A consumer waits for its provider | A resolution test with no answer yet: the requirement shows as waiting on a provider, and nothing is delivered. | -| The controller mints nothing outside genesis | A controller test: outside genesis, no code path mints a credential. The minting function is reachable only from genesis. | +| The controller mints only the foundation's credentials | A controller test: the minting function is reachable only from genesis and from root-secret rotation, and never for a module's provision. | | The vault generates and rotates | A vault test: a requested secret is generated to its contract, and a rotation answers with a new value. | ## References diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 215ae4b..719442f 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -28,7 +28,7 @@ A seat has four properties, fixed by the mesh rather than by any module: | property | is | |---|---| | name | what a manifest claims, and what a person reads in the list | -| scope | node, site or mesh: where there may be only one holder | +| scope | node, site or mesh: where its capacity applies. Every seat in the set has a capacity of one, so one holder per scope. A bench, a seat with several holders, is a word the glossary keeps and no seat uses yet | | delivers | the provision its holder answers for, or nothing | | decision | the record that made it a seat | @@ -63,8 +63,10 @@ argued for is an entry nobody can explain. | `the-showcase` | node | — | the showcase module | The controller holds this set in code, and a test asserts both its size and that every entry names -the record that made it a seat. This document follows the code, not the reverse. If the two disagree, -the test has been changed without this table, and the table is what is wrong. +the record that made it a seat. **This table and [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md) +govern, and code that disagrees is what is wrong.** The implementation in progress predates two +things here: the `mesh-vault` seat, and the rule that `mesh-store` and `mesh-broker` deliver +nothing. It is brought to this table before it merges. ## A seat that delivers a provision @@ -89,14 +91,22 @@ one is the mesh's, and co-location answering first would let any second provider machine take over for that consumer, silently. So a second provider can run beside the holder and harm nothing. The forge holds `npm-package-registry`. An npm proxy may provide the same provision on another machine, and a module -requiring an npm registry is still served by the forge, without anybody pinning it. Moving the role -to the proxy is moving the seat: unassign the claim from one, assign it to the other, and every -consumer follows. +requiring an npm registry is still served by the forge, without anybody pinning it. + +**Moving the role is changing which module claims the seat, and today that is a definition change.** +A claim is part of a module's definition, so the proxy's definition must claim the seat and the +forge's must stop. The forge cannot simply be unassigned, because it holds `git` as well. Every +consumer follows once the claim moves. Making *which* seats a module holds the assignment's choice, +with the definition saying only which seats it *can* hold, is the consistent answer, and +[27 — A module requires, the mesh resolves](27-a-module-requires-the-mesh-resolves.md) lists it as +not yet settled. **What a consumer receives is a grant**, the same as for any provision: where the provider answers, -what it serves, and a credential where one is minted. A consumer never reads the seat directly. The -one exception is the controller itself, which reaches the store and the broker through a narrow -seat placeholder because it made them before any module existed and cannot be their consumer. +what it serves, and a credential. A consumer never reads the seat directly. The one exception is the +controller itself, which reaches the store and the broker through a narrow seat placeholder, +because it made them before any module existed and cannot be their consumer. One foundation module +also reads it today, to find its own server's port. [27](27-a-module-requires-the-mesh-resolves.md) +moves that to a host port requirement. ## A seat that delivers nothing diff --git a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md index 606c5ef..42b4b4a 100644 --- a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md +++ b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md @@ -23,7 +23,7 @@ beside the model, no literal it carries. This replaces six mechanisms that grew separately: provisions read through bindings, settings, assigned ports, machine facts, minted or accepted secrets, and literals in the definition. Each resolved, validated and failed in its own way ([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), -[issue 118](../../04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md)). +[issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md)). ## A requirement @@ -36,9 +36,12 @@ A requirement has three parts: | provider kind | which of the four kinds of provider answers it | **A contract is shared, not per module.** A database's contract is the database's, whoever requires -it. The mesh knows each contract, and a provider is checked against the one it claims to answer. A -module's own specification may narrow a contract (a password of at least this length, a directory -owned by this user) and never widen it. +it. **The controller holds every contract**, one per provision name, declared where the provision is +defined in the catalogue. Today contracts are implicit in each provider's served fields; the first +phase below makes them explicit, because nothing can be checked against a contract that is not +written down. A provider is checked against the contract it claims to answer. A module's own +specification may narrow a contract (a password of at least this length, a directory owned by this +user) and never widen it. ## The four kinds of provider @@ -49,7 +52,7 @@ can come from and a reviewer has to know every one. |---|---|---|---| | **a module** | a database, a bucket, a vhost, a route, a secret | the rule below | provisions and bindings | | **the node's host** | a directory, a port, a fact about the machine | always the module's own node | resource paths, assigned ports, machine placeholders, facts | -| **the mesh** | the module's identity and names | the controller | derived logins, generated names | +| **the mesh** | the module's identity and names, and the delivery of every answer | the controller | derived logins and generated names; the controller's delivery | | **the operator** | a value a person chooses | the assignment, else the requirement's default | settings, carried literals | ### A module provider @@ -61,9 +64,10 @@ Which module answers, in order: 2. **the holder of a seat** that delivers the provision, where one does. Co-location does not apply to these: the seat is the mesh's one answer for everyone ([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [26 — The seats](26-the-seats.md)); -3. **the provider on the consumer's own node**, for a provision no seat delivers; -4. **the only provider** in the mesh; -5. otherwise **refused**, naming the candidates, or the unheld seat. +3. for a provision no seat delivers, **the provider on the consumer's own node**; +4. for a provision no seat delivers, **the only provider** in the mesh; +5. otherwise **refused**: naming the unheld seat, for a provision a seat delivers, even when exactly + one provider exists; naming the candidates otherwise. The provider makes what it provides and answers with its contract's fields. The mesh carries the answer back to the consumer, sealing every secret field to the consumer's node @@ -112,9 +116,11 @@ A value a person chooses: a public name for an endpoint, a greeting, how many wo needs no provider module, no grant and no credential. If asking a person for a value took more than that, module authors would route around it, and the literals this replaces would come back. -**A secret operator value**, such as an external API key, is held by the vault as an +**A secret operator value**, such as an external API key, is still an operator requirement: its +provider is the operator. What differs is where it is kept. The vault holds it as an operator-delivered value ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)), -never stored as a setting. +never as a setting, because anything secret belongs in one place that can seal and audit it. The +vault is its custodian, not its provider. **An endpoint** is an operator value inside a route requirement: the public name is chosen on the assignment, and the route provider answers. A public name already held by another assignment is @@ -128,13 +134,17 @@ name in a configuration file writes the same thing: the requirement's name and t controller fills it at resolution. This one form replaces the placeholders that exist today, one per mechanism: bound values, secrets, -ports, machine facts and seats. The seat placeholder the controller uses to reach its own foundation -stays, because the controller cannot be a consumer of a foundation it made before any module -existed. It is the controller's own and no module uses it. +ports and machine facts. -**A secret field reaches a process as a file**, as today ([ADR 0086](../../02-DECISIONS/0086-a-secret-reaches-a-process-as-a-file.md)). -Reading one into a plain value, such as an environment variable, is refused when the definition is -parsed, unless the definition declares the exception 0086 allows, with its reason. +**The seat placeholder stays, for the controller alone.** The controller composes its own +declaration and reaches the store and broker it made before any module existed, so it cannot be +their consumer. One module reads the placeholder today: the store module, to find its own server's +port. That is its own port, so it becomes a host port requirement in phase 3, and after that no +module uses the seat placeholder. + +**A secret field reaches a process as a file**, as [ADR 0086](../../02-DECISIONS/0086-a-secret-reaches-a-process-as-a-file.md) +decided. The one exception 0086 allows is a declared env-file with its reason; a secret field as a +value in a container's environment is refused when the definition is parsed, with no exception. ## An instance @@ -190,12 +200,14 @@ removed from the parser. Each phase ends at a check that holds, so none of them leaves a mechanism half-replaced. -1. **Resolution and the new form.** The controller resolves requirements from the four providers, - refuses as above, and fills the one form. Old mechanisms keep working beside it. *Ends when* a - definition written entirely in the new form installs on a lab machine. -2. **Providers answer.** The SDK harness answers with contract fields, the controller carries answers - back and seals them, and the vault holds its seat and generates. *Ends when* the analytics and DNS - providers answer their consumers and the broker's bootstrap step is removed. +1. **Contracts, resolution and the new form.** Every provision's contract is written down and held by + the controller. The controller resolves requirements from the four providers, refuses as above, + and fills the one form. Old mechanisms keep working beside it. *Ends when* a definition written + entirely in the new form installs on a lab machine. +2. **Providers answer.** The SDK harness answers with contract fields and seals the secret ones, the + controller carries answers back, and the vault holds its seat and generates. *Ends when* the + analytics and DNS providers answer their consumers, and a module's own secret is generated by the + vault and rotated by it. 3. **Definitions move.** Every catalogue definition is rewritten, adopted and running assignments placed where their data already is. *Ends when* the list of definitions using an old form is empty, and the old forms are removed. @@ -208,10 +220,15 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r |---|---| | Every requirement has one of the four provider kinds | The parser refuses any other. | | A definition names no host path, node or mesh | The catalogue tests of [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md). | -| A module provider is chosen by pin, seat, co-location, only one, refusal | Resolution tests for each step, and for a second provider on a consumer's own machine when a seat delivers the provision. | +| A module provider is chosen by pin, seat, co-location, only one, refusal | Resolution tests for each step; for a second provider on a consumer's own machine when a seat delivers the provision; and for an unheld seat with exactly one provider, refused. | +| A provider answers within its contract | A controller test: an answer carrying a field its contract does not name, or missing one it does, is refused and not delivered. | +| A module narrows a contract and never widens it | The parser refuses a module specification that loosens a contract's field. | | A host requirement is answered on its own node | A resolution test placing one elsewhere: refused. | -| An operator value needs no provider | A resolution test: a requirement with a default resolves with no module assigned anywhere. | -| A secret field reaches a process as a file | The parser refuses a secret field read into a plain value, unless the definition declares the 0086 exception with a reason. | +| An operator value needs no provider module | A resolution test: a requirement with a default resolves with no module assigned anywhere. | +| A secret field reaches a process as a file | The parser refuses a secret field as a container environment value, and accepts it in a file or a declared env-file with its reason. | +| A public name already held is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. | +| Everything keyed by a module is keyed by its instance | A resolution test: two instances of one module on one node get two directories, two containers, two logins and separate settings. | +| A consumer waits for its provider | A resolution test with a provider that has not answered: shown as waiting, and nothing delivered. | | Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. | | The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. | @@ -221,5 +238,8 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r - The layout a node's default root uses beneath it, beyond one directory per instance. - Whether a module provider's answer can change without the provider being asked, for example a provider moving. The rule so far is that it cannot, and moving is re-resolving. -- A contract registry: where contracts live, and how a new provision gets one. Today contracts are - implicit in each provider's served fields. +- **Which seats a module holds.** Today a claim is part of the definition, so moving a seat is a + definition change ([26 — The seats](26-the-seats.md)). By this document's own logic it belongs to + the assignment: a definition says which seats a module *can* hold, and the assignment says which it + *does*. That changes [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), + and is its own decision. diff --git a/04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md b/04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md similarity index 98% rename from 04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md rename to 04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md index 7a8e416..f5c5c90 100644 --- a/04-ISSUES/118-a-module-definition-decides-where-its-files-live/00-report.md +++ b/04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md @@ -6,7 +6,7 @@ fixed-by: amended-design: --- -# 118 — A module definition decides where its files live on the machine +# 119 — A module definition decides where its files live on the machine ## What was observed