bba93718326f56fcd95077a1eb7bbda7cd98af9c
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1111bd84d7 |
Establish the repo for the completed Phase 0-3 build
Settles the design repository now that the self-upgrade build is on main: - Records the two decisions that shipped without a record — ADR 0077 (the controller/foundation/node vocabulary) and ADR 0078 (the store and broker are ordinary modules); accepts ADR 0075 and 0076, which shipped work rests on. - Fills issue 051's amended-design and wires ADR 0078 into 07-the-foundation. - Sweeps the repo rename (mesh-control -> mesh-controller) into the mutable docs now that the forge repo is renamed; updates the glossary note and repos.md. - Fixes the six broken links from the design-doc renames, indexes the glossary, regenerates the decisions reading order. Both checks (records.py, index.py) are green. Statuses stay honest: the build is on main and lab-proven but not deployed as the production mesh, so the to-be docs remain in-progress and the as-is layer (the hal mesh) is unchanged — graduation to implemented + as-is belongs to deployment, not merge. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx |
||
|
|
333356cff3 |
Order the records the way the system is learned
Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception. |
||
|
|
e1febe8e0f |
Renumber the records 1 to 23
The consolidation left a sparse sequence -- 1, 4, 6, 7, 9, 10, 12, 15, 16, 18, 19, 25, 34, 35, 36, 37, 40, 42, 44, 45, 48, 49, 58 -- where the gaps were only the archaeology of what used to be there. Renumbered contiguously. Renames run in ascending order, so every target number is already free and no two files ever collide. The reference rewrite is one simultaneous pass rather than a sequence of replacements. Numbers moved into slots other numbers were vacating -- the node host went 37 to 16 while the lab went 16 to 9 -- so replacing one at a time would have cascaded and silently pointed things at the wrong record. Seven plain-text references survived the merges as prose rather than links, naming records that no longer existed: the enrolment token, the link boundary, what a declaration is, reachability, the repository structure. Each mapped to the consolidated record that now holds it. Verified rather than assumed: every [ADR NNNN](path) link now has matching text and target, checked across the whole repository, and the checker passes. Frontmatter `consolidates:` lists dropped -- they named records that are gone, and each consolidated record already says in prose what it absorbed. |
||
|
|
77f3a4cea7 |
Consolidate: 65 decision records to 23
Every remaining cluster merged. Each was one design that had been split across
several records because it was worked out over days rather than at once.
the node host 8 -> 1 applies not decides, depends on nothing,
per operating system, root service, the
launcher, episodic, what a declaration is,
actions from the bundle only
a node and how it joins 4 -> 1 what a node is, joining, the link as
security boundary, the enrolment token
modules and the graph 7 -> 1 everything is a module, no domain modules,
three edges, provisioning, the core library
substrate and control 6 -> 1 the test, seven contexts, one control plane,
plane the authority is not a database, the named
products, the pinned bundle
connectivity 3 -> 1 a route is a grant, reachability declared,
filter rules
delivery 5 -> 1 reconciliation not a pipeline, artifacts,
the three silos, a failed step, the verdict
the lab 5 -> 1 (earlier)
how this repository 10 -> 1 (earlier)
works
Nothing was dropped. Each consolidated record carries the reasoning of the ones
it absorbs -- the measurements, the incidents, the alternatives rejected --
because that reasoning is the only reason to keep a record at all. What is gone
is the fragmentation: eight files to read to understand tier 0, when tier 0 is
one component.
The four superseded records went too. They existed to point at their
successors, and the successors now contain what they said.
The checker made this safe. Each merge left dangling links -- 38 files after
the host merge alone -- and it named every one. Nothing was found by reading,
and a manual pass would certainly have missed some, including references inside
AGENTS.md which every session loads.
|
||
|
|
9d091c81e0 |
A build edge, a core library that is a domain, and 0063 corrected
Three things from walking a real dev cycle through 0063, all of which Jochen caught by pushing on where I had glossed. 0064 -- a build edge is a third kind. Research 011 established presence and instantiation, and both are RUNTIME edges: they answer what a module needs in order to run. Delivery needs a different question -- what has to be rebuilt when this changes -- and that relationship is fixed inside an artifact rather than negotiated when it runs. So the graph as designed could not drive delivery, which is the real reason 0063 was not approvable. It is derived rather than declared, read from what a module actually imports, because a declared list and the imports it describes drift and the imports are the true ones. The runtime edges stay declared, and that asymmetry is not an inconsistency: a runtime edge is an intention somebody has, a build edge is a fact about code that exists. It also makes design quality measurable. A module with many inbound build edges is one whose every change is expensive, and the current shared library is exactly that -- nobody could see it because nothing drew the edges. 0065 -- the core library is the mesh's domain. Jochen disagreed with 0030's "types, not behaviour" and was right: that guard is aimed at the wrong thing. A library everything depends on is a hub whether it holds types or code, and the fan-in is what makes a change expensive. So types ship with the module that owns them -- trading one wide edge for several narrow ones -- and the core library holds what is true of the mesh regardless of context, which research 011 already found: a module, a node, an assignment. The test is "would this still mean the same thing in a context that had never heard of the one it came from". A node does; a pipeline stage does not. Domain-driven is the point rather than the label: "who else might want this" always answers yes, which is how the current one grew. And it changes the check for the better. "The build output contains no runtime code" would have enforced a rule now withdrawn. Inbound build edges is a measurement rather than a prohibition, and it is visible while a hub is forming rather than after. 0063 revised on both counts, plus a third: I had written "the lab judges it" as though that were a step. A lab run takes tens of seconds, occupies a VM, and fails for environmental reasons -- and a shared-library change produces dozens. One expensive non-deterministic gate fails both ways, and neither failure looks like itself. Verdicts are now tiered, and a run that failed environmentally is explicitly not a verdict. 0063 also now carries what must exist before it can be implemented, rather than leaving that to be discovered. |
||
|
|
0531d6fc38 |
ADRs 0044 and 0045 — the module design, closed; 011 graduates
011 opened asking what a graph deletes and found the graph already existed. The work became design, worked through twenty cases and one provider in full. Two decisions close it. 0044 — a module declares presence, instantiation and exclusion. Two kinds of edge because a game wanting a database is not a game wanting postgres to exist: one creates something per consumer, carries credentials back, can be revoked, and leaves the provider holding state. Names are concrete unless providers are genuinely substitutable — `terminal` passes, `database` fails, and the adapter is what creates an interface. Where there is no contract there is a tag, which describes and does not bind. Exclusion is a third relation and is not derivable. A node provides names too, which makes capability checking stop being a separate mechanism and makes the host's detection an input to resolution. Constraints, never placement. Scope decides which provider and the binding is written down and sticky, in a place that follows the scope. And there are three entities, not two — the assignment carries what belongs to neither end, which is what node-agnostic modules ran out of. 0045 — a context owns its store, exclusively. No shared writes and no read roles on another context's store, because reading couples you to its layout just as firmly and invisibly. The unit is the CONTEXT, not the process: a board showing the mesh's own data is the mesh showing its own data. Asking or subscribing is derived from ADR 0036 rather than chosen. And it is the first clear list of what the design removes: grant kinds, table ownership, cross-context migration ordering, and a class of permission modelling. 0017 is superseded rather than narrowed — its text unchanged, its status changed. Folders assert relationships where edges record them, and the domain module goes with it. Left explicitly undecided in both: what a resolver delegates rather than reimplements, how many instances a module should have, and what a provider hands back. |
||
|
|
a4ab3e15c2 |
011: one interface, many contexts — and the constraint that hides in it
The objection is right: if every context runs its own service with its own interface, the board is coupled to N interfaces instead of N schemas, something has to compose them, and composition is logic — which tier 3 says a surface does not hold. That moves the problem up a layer rather than solving it. The skeleton already answers it, and the previous entry talked past it. `work` and `knowledge` are not separate services; they are contexts INSIDE the control plane, alongside the record, inventory and delivery — and `api` is listed there as the one interface every surface speaks to. So the board speaks to one interface. Behind it the contexts stay separate, integrating through the record, but they are one tier, one repository, one deployable — and coupling within a tier is not what the tier rule forbids. The problem does move up a layer, and the layer it moves to already exists and has this as its job. The caveat is load-bearing and now recorded as an open question: this holds only while the contexts are not separate deployables. The moment one becomes its own service with its own interface, the board is back to N clients, something must compose them, and the composition has nowhere to live that tier 3 permits. That is a real constraint on how far the control plane may be split, and it is worth knowing before splitting rather than after. |
||
|
|
fa62c7f0e4 |
011: correct the rule — contexts, not processes
An earlier version argued a dashboard reading a dozen stores was caught by exclusive ownership, because a dashboard is a surface and surfaces speak to an interface. Wrong, and it drew the line in the wrong place. The mesh's own board showing nodes, modules and deployments is not a separate context reaching across a boundary — it is the mesh showing its own data. Requiring it to go through an interface to reach facts its own context owns is ceremony. The rule is that a CONTEXT is granted what it exclusively owns. Everything inside it — service, surface, tools — reads that store freely. What is forbidden is a different context reading it. Which is what the consumer count already showed: the problem was never surfaces, it was three other contexts keeping their tables in the mesh's database. |
||
|
|
e71d532c2e |
011: request or subscription is derived, not chosen
Asked what the distinction actually is, and the SQL half needed correcting first: under exclusive ownership SQL runs against your own database and nothing else, whatever transport a query might travel over. Both options are the mesh's own channel and both ride the broker, so the transport is not the distinction. The distinction is where the answer lives when you need it. A request asks at the moment and waits — always current, costs a round trip, cannot answer when the other side is down. A subscription keeps a local copy — instant, works offline, as current as the last event received, and you must handle what you missed. What decides is not taste. ADR 0036 makes disconnection an ordinary situation rather than an exception, so anything that must keep working while disconnected CANNOT use a request: there is nobody to ask. And the converse — anything where a stale answer is worse than no answer cannot use a subscription. A display can lag; a decision about whether a grant is still valid cannot. So an apparently open question turns out to be derived from a decision already taken. What stays open is narrower: what a consumer does about the events it missed while disconnected — replay from a point, ask once for a full picture and resume, or rebuild. The question every projection has. Also recorded: separate databases are required in the new design, and the shared registry is a leftover rather than a pattern. |
||
|
|
7e83723b7b |
011: rewrite the question table, which had gone stale silently
Several edits to the overview matched nothing and returned success, so the question table still carried answers superseded two or three exchanges ago — "when two modules provide one name, who chooses" was still open in the table while answered in the file it pointed at, and nothing recorded the instantiation edge, instance counts, grants, bootstrap provisioning, the tool audience, or the registry consumer check. That is the fault this repository catalogues, committed by the thing cataloguing it: a string replacement that found no match, reported nothing, and left the document claiming a state it did not have. Rewritten from what the documents actually say rather than patched again. Nine questions settled, fourteen live, and the split is now visible instead of implied. |
||
|
|
13c6068874 |
011: the provider shape generalises, and two things differ inside it
The broker has all nine properties the store has. So do the object store and the image registry. A substrate service is a SERVICE PLUS A FACTORY, there are four of them, and the pattern generalises past the substrate: anything granting something per consumer has this shape. Two differences matter more than the similarity. The broker cannot be managed over the broker. ADR 0001 makes it the channel every node takes work from and ADR 0039 makes it the security boundary, so the module providing it is also the way modules are managed — a declaration cannot be delivered to it over itself. Nothing else has that property; the store is consumed by the control plane but is not how the control plane REACHES anything. This is what the carried bundle exists for: the broker is raised from what the host carries because there is no other way to raise it. A constraint on one module, not a general rule, and a schema with no way to say so hides it. And two modules of identical shape want opposite instance counts. The broker is one per mesh by decision. The store cannot be, because a node that must keep working while disconnected cannot depend on a database elsewhere. Which settles what cases.md left open: how many instances is NOT derivable from what a module is. It is a per-module decision, it has to be declared, and nothing in provides, requires or excludes says it. Revocation differs in consequence too. Dropping a database leaves data until something removes it — a leak, recoverable. Dropping a virtual host loses whatever was undelivered — silent, and not. Same relation, different blast radius, which argues for the provider deciding what revocation means rather than the mesh applying one rule. File renamed: it was never really about postgres. |
||
|
|
f160b28a71 |
011: postgres worked through, and "one kind of edge" was wrong
The tidy version said a module provides names and requires names and that is the only edge. Working postgres through completely disproves it. A small game wanting to store data does not require postgres to EXIST. It requires postgres to MAKE IT A DATABASE and hand back credentials. Those are different relations in every way that matters: one creates something per consumer, carries a payload back, can be revoked, and leaves the provider holding state about who was granted what. The other creates nothing. So: two kinds of edge, one graph. Instantiation implies presence; presence does not imply instantiation. The current system already had exactly this split — `dependencies` for presence, `requires: provision:` for instantiation, with the resolver deriving one from the other. analysis.md called that derivation a convenience. It is not: it is the correct relationship between two genuinely different relations, and the design had collapsed them. Postgres also turns out to be nine things, not one. A container. Persistent state where moving nodes is a migration rather than a reschedule. Configuration partly derived from the machine's hardware. A tool surface. A provisioner. Its own bookkeeping about what it granted, which is not the data it stores. An exposure decision per node it runs on. Credentials it generates, which means a provisioning edge carries a secret. And health that is not "the container is up". Four questions the worked example makes concrete rather than abstract. WHICH postgres, when there are two — a consumer of `terminal` does not care and a consumer of a database cares permanently. How many instances a module should have, which cannot be a global rule because one-per-mesh is wrong for a store a disconnected node needs and one-per-node is wrong for the mesh's own registry. What happens to a grant when its consumer is removed, where dropping is data loss and keeping is a leak. And whether a declaration is composed PER NODE from what that node reported — because tuning follows hardware the control plane cannot know, and the alternative is the host deciding, which ADR 0037 forbids. |
||
|
|
c9c2dfe686 |
011: what a feature is, and what it splits into
The operator wants features gone, and 006 left it open. Measured, and the answer is that nothing replaces them because they were never one concept. A feature is a kind of content a module carries, detected from its directory: twenty-one of them, each with a handler owning six stages — build, publish, install, configure, start, verify. The structural finding: EVERY handler implements EVERY stage. `configs` writes files onto a node, has nothing to build, and has a build stage. `npm` publishes to a registry, has nothing to start, and has a start stage. One interface spans build-time and apply-time, so every kind of content must implement both halves and most do nothing in one — and a stage that does nothing looks exactly like a stage that failed to do anything. They split four ways, across three tiers. Artifacts built once per version and published, where no node is involved — delivery. Resources that are desired state on a machine, which is what ADR 0043 already describes and the host already does — tier 0. Actions run once against something that is not this machine, like a migration against a database on another node — delivery, and seeds go entirely. And checks: the prerequisites are REQUIREMENTS IN DISGUISE, a module saying what must be true before it can be installed, which is what an edge in the graph says; the verifiers are the read-back the host already performs. So `feature` is one word for four things spanning three tiers, which is why the pipeline is hard to reason about. One property must survive the split, and it is the thing the current design got right: content is DETECTED, relationships are DECLARED. A module that says it has migrations and has none is a fault nobody sees until it matters — but what it requires and provides is not visible in a directory and has to be said. |
||
|
|
ae099482a9 |
011: twenty cases, and two axes nothing covers
Before settling a schema, what a module can actually be. Twenty kinds of thing, with the hard ones at the end because they are the point. The ordinary nine are unsurprising: a supervised service, a system package with configuration, an application a person launches, a command-line tool, a library that never runs, a one-shot task, a scheduled one, an adapter, and a standalone application whose only difference is where its source lives. The eleven that break a naive schema are where the work is. Something that is a service AND an application — a git forge is consumed as a remote and operated through a web interface, and neither reading is wrong. Something that provides and consumes, because provider and consumer are ends of edges rather than kinds of module. Something the mesh installs that then becomes a node CAPABILITY, which means a node's provides-list is partly derived from what is installed on it and not only detected. Something that must be adopted rather than installed. Something that is a set rather than a thing. Something with exactly one instance for the whole mesh, where assigning it twice is not redundancy but two meshes. Something that is not software at all — a firewall policy, a DNS record, pure desired state, which fits the host's declaration model exactly and an installable package model not at all. An agent. The host itself, which is not a module and needs a schema that can say so. And the things the mesh depends on and does not control, which are why a node can be perfectly configured and still not work. Nine axes come out of it. Two are covered by nothing anyone has proposed: HOW MANY INSTANCES a thing may have, and WHETHER TWO CAN COEXIST — `excludes` covers part of the second and nothing covers the first. And one question the cases sharpen: is "runs" a property or a kind? The axes say property — one schema with a field saying how it runs, `never` included. The alternative is several kinds of module with different schemas, which is the taxonomy this effort already rejected once for services and applications. |
||
|
|
f55ecc1a47 |
011: an abstract name needs providers that are actually substitutable
Two corrections from the operator, and the first improves the design rather than narrowing it. `database` is not an edge. The test it fails, and the test the proposal was missing: can a consumer be switched from one provider to another WITHOUT CHANGING? A module speaking Postgres does not speak MongoDB or SQL Server — different wire protocol, dialect, driver — so a consumer declaring `requires: database` and handed any of them breaks. The name promises what no provider can deliver, and the resolver would report a requirement satisfied that is not. `terminal` passes: anything that runs a command in a terminal works and the consumer never learns which it got. So the ADAPTER is what creates an interface. `ai-assistant` is legitimate exactly because adapters normalise what is behind it. Without one there is no interface, there is a category — and a category is a TAG. Tags describe, edges bind, and keeping them apart is what stops the catalogue acquiring a second kind of relationship that looks like a dependency and is not, which is what a folder named after a domain already was. And the domain module goes. A `networking` module gathering a firewall, a resolver and a proxy under one name came from an older shape and does not fit — there is no such thing to install. There is core infrastructure: concrete modules named individually, not flavourable, with no grouping module standing in front of them. Fixed three places where the revision left the old rule standing, including an example manifest still requiring `database` — the kind of contradiction that would have been read as the design rather than as a leftover. |
||
|
|
e20a09ae80 |
011: one kind of edge
The design, rather than an account of what exists. A module provides names and requires names, and that single relation absorbs three things this effort had listed separately: requiring another module is requiring a concrete name, requiring a resource is requiring an abstract one, and an interface is simply a name with more than one provider. Nothing has to declare that it is an interface — it either has one provider or several. The move that does the most work: a NODE provides names too. Its profile is a set of them — display-server, container-runtime, an architecture — so a module requiring a display server is satisfied by the node exactly as one requiring a database is satisfied by another module. One resolution instead of two, and a graphical application cannot land on a node without a display server for the same reason, through the same code, that it cannot land without its libraries. Which makes the host's capability detection an input to resolution rather than something a person reads. It was built to be read; it turns out to be a provides-list. `excludes` is the one genuinely new relation, because it is not derivable: two modules that both provide message-bus look interchangeable when installing both would break the machine. Constraints are not placement. They say what must be true of a node, never which node — which is the mistake the measurement found in the current catalogue, where a module pins its database to a named node so a second node cannot provide it without editing the consumer. What it deletes, for the design: the module/resource distinction, the interface as a kind of thing, capability checking as a separate mechanism, domain grouping — folders assert relationships where edges record them, so a domain becomes a query over the graph rather than a directory somebody keeps true — and possibly tiers, if a tier is just a computed level. What it does not delete, stated so it is not discovered later: a resolver still has to exist, with version constraints and conflicts, and the design owes an answer on what it delegates rather than reimplements. |
||
|
|
c3a2984b3e |
011: measured, and the premise was wrong — the graph is not missing
The effort was opened to ask whether the catalogue's missing structure is a graph. It is not missing. 126 manifests, 103 edges, no cycles, nothing dangling, deepest chain of five — and a resolver in the SDK that topologically sorts them, already called by the tool loader at startup, the installer when syncing modules onto a node, and the delivery coordinator when expanding what a change affects. It already does something this effort assumed would need designing: a requirement on another module's provision is treated as an implicit edge to the module that provides it. So "ordering by the graph", which ADR 0043 makes the control plane's job, is a thing to call rather than a thing to build. The one place the graph is wrong, it is wrong about the substrate. A module needing a database declares `provider: postgres` inside `provisions:` — which is what a module OFFERS — so the resolver, which reads `dependencies:` and `requires:`, never sees it. Three edges are invisible this way, and they are the mesh's own database, the mesh's own broker, and the work engine's database. The consequence is measurable: computing what a working mesh needs from the declared graph gives registry -> sdk -> mesh -> meshware. Four modules, four levels, no database. Arithmetically correct and obviously wrong, for exactly one reason — a field that means "depends on" is not read as one. That is 04-ISSUES/003 in a new form: not a key nothing reads, but a key read as something other than what it means. Two latent defects, both contrary to ADR 0008 and both in the component ADR 0043 makes responsible for ordering a host will apply without question: a cycle warns and falls back to input order, and a dependency that does not exist warns and continues. Neither has fired, because the catalogue currently has no cycles and nothing dangling, which is why nobody has noticed. And placement is decided in the catalogue: a provision pins itself to a named node in the manifest. Which node runs what is an inventory decision — tier 2 by the skeleton's own test — so a second node cannot provide the mesh's database without editing the module that consumes it. What the graph would DELETE is currently nothing. What it would add is three declarations that no manifest uses today: excludes, a required node capability, and an interface with adapters. Whether they would be used is not measured, and zero usage is equally consistent with nobody needing them and nobody being able to express them. |
||
|
|
902739acb6 |
Research 011 — the module graph
The proposal to split modules into provisioning services and applications was worked through and abandoned, for a reason worth keeping: it cannot be filed consistently. A git forge is consumed as a service and operated through a web interface; an analytics service grants tracking identity and is a dashboard. The operator's correction is the sharper form — what runs on the machine is a supervised container, not something a user started. That is a fact about HOW a thing runs, not about what kind of thing it is. So it is a facet, and 0002 survives: everything is a module. What the catalogue is missing is not a taxonomy but a graph. Grouping asserts relationships; a graph records them. Five declarations, of which two exist: requires/provides a resource (yes), requires/excludes another module (no), requires a node capability (no). Plus interface modules that carry no implementation, with adapters providing them. Recorded because it matters: this is a package manager's model, and pacman already has all of it — depends, conflicts, and provides as virtual packages, which is exactly the interface/adapter idea. Arriving there independently is evidence for the shape. It is also a warning about what not to reimplement. Working position on capabilities, to be tested: intrinsic ones (hardware, architecture, network position) are detected and never installed, and a module requiring one it lacks is impossible rather than unresolved. Provided ones (a display server, a container runtime) are not a separate kind of thing — they are modules that provide a capability, so "may the mesh install a capability" is not policy, it is dependency resolution. Issue 007 then bears directly: an installed package is not a capability. Also captured: the operator's assessment that the machinery around a module — scheduled tasks, hooks, migrations, config and env — is worth keeping, seeds are not, and the integration is wrong enough to need a major refactor. Research 005 found supporting evidence from another direction, that the densest apparent coupling in the catalogue is manifest boilerplate churn. The first open question is the one that decides whether this is progress: what does the graph DELETE? If modules gain declarations and lose nothing, it is motion. |