Florete

Design

What A0 does where the C0 design assumes an agent or a coordinator

A0 is the first Florete release: a rete run by one operator, with no agent and no coordinator on any node. This page is its design, written as a delta on C0 — where A0 behaves as C0 does, it says so and links there, and it describes only what differs (DSGN-A0-01). How flor reaches a host and how a scope comes to run under systemd is the Installer; where compiled artifacts rest, and what a write must satisfy before one is kept, is the Artifact Store. What A0 is for, and what it leaves out, is its Scope.

If you are implementing A0, the sections after the Shape follow a node through its life — running, installed, enrolled, changed — and then cover what holds across it: hosts with several nodes, validation, logging and names. If you are changing A0's design, start at the Shape and the Decision Log. If you are operating an A0 rete, the commands and their order belong to the operator playbook and the user guide rather than to this page.

Shape

STRUCTURE

  subject     A0 as C0 narrowed — a rete whose only long-lived process is one
              flor-vertex per node, whose only operator tool is `rete` on one
              workstation, and whose only channel between them is a couriered
              bundle

  components  rete         init, validate, compile, issue-bundle —
                           offline in every command
              flor         activate, deactivate, version, enroll
              flor-vertex  the node's network proxy
              one release version over every executable, rete included on
              every node

  supervisor  the systemd user manager
              unit       flor@.service, a template named for the scope,
                         written by flor activate (Installer)
              instance   flor activate <node>.<rete>; nothing is rendered per
                         scope
              exec       <dir of flor>/flor-vertex --scope %i and nothing else —
                         no FLORWL_* is injected, and none is read
              policy     Restart=on-failure, RestartPreventExitStatus=78

  scope root  <state-dir>/retes/<node>.<rete>/, narrowed from C0
              receipt.json  same as C0
              scope.lock    the scope's exclusive lock, held by flor-vertex for
                            its life and by flor enroll for its run
              identity/     same as C0
              artifacts/    the node's artifact store
              no other files and dirs of C0

  bundle      same as C0, with enroll in the intake's place: it checks each
              artifact entry's name against the envelope it carries

  operator    same `~/.rete` as C0, except no config.toml

  dev env     processes run directly, with no service manager
              several nodes of one rete on one host are several scopes, each
              <node>.<rete>, told apart by the listen addresses in their YAMLs
              each hand-run process logs to its own stderr

FLOW

  install &   on the Installer page
  upgrade

  genesis     once per rete
                1  create the repo, then rete init <name> inside it
                2  seed the collections; declare the nodes, services and users
                3  rete compile, then commit
                4  from here it is an ordinary enrollment, per node

  enroll      follows C0 process with changes
                 no publish
                 activation by flor activate
                 artifacts are handed to the artifact store, opened with
                   what the identity store's check accepted; no intake

  rollout     a change to any node's state is that node's re-enrollment, and
              there is no other channel
                1  edit YAML, rete validate, rete compile, read the diff
                2  commit
                3  issue-bundle every node the compile touched
                4  courier, stop, enroll, start — per node
              the fleet does not converge on its own; the operator walks it

  revocation  1  remove the principal from YAML
              2  rete compile, then commit
              3  re-enroll every node whose set the removal touched
              a live key opens nothing once the last of those nodes is done

RULES

  narrowing     A0 removes mechanisms, never rules

  verification  same as C0, and every envelope is verified where it is
                admitted, by the artifact store

  channels      one mgmt channel, the couriered bundle, applied by enroll

  freshness     a stale bundle cannot roll a node back: the artifact store
                refuses it
                artifacts carry the node's set version, stamped by the
                compiler against the committed tree
                the commit is the gate on issue-bundle

  partiality    material without an artifact is inert, and every revocation
                leaves some behind
                an artifact without material is degraded, and in A0 a defect
                rather than a state, one bundle carrying both planes; the
                vertex's warning is where it surfaces

  ownership     the channel and flor activate own the host, the vertex owns the
                scope while it runs, and enroll is the one act that crosses
                between them
                enroll refuses a live scope rather than quieting it, naming the
                holder and the systemctl stop that clears the vertex's one

  locking       same as C0, with flor-vertex in the agent's place: it holds
                its scope's lock for its life, and exits 69 when it finds the
                lock held

  activation    enroll activates nothing; flor activate is the only activation

  paths         artifacts carry no filesystem references
                the vertex derives its paths from the scope alone, and reaches
                its artifact through the artifact store, by its own name

  vertex name   a node declares exactly one vertex, named hop, which the
                validator requires and the vertex defaults to

  logging       every process opens its own log sink, from its flag, its
                environment or the default
                the class is what a process opened, and nothing may say otherwise
                a destination absent, unreachable or of the wrong socket type
                stops the process rather than degrading it
                every record is framed flor-<workload> and carries its scope as
                node and rete, written by the formatter
                flor's commands log to syslog, rete to stderr and off unless
                asked
                no counters and no flood-safe Layer: events carry closed values
                as fields, and the journal's per-unit rate limit is the flood
                bound

  platforms     Linux with systemd on x86_64, user mode only

A node without an agent

An A0 node runs one long-lived process, flor-vertex which is supervised by systemd. flor activate <node>.<rete> writes a single template unit, flor@.service, and enables and starts the scope's instance; flor deactivate stops and disables it, and stopping or restarting a running scope is systemd's (Installer). systemd restarts the vertex when it fails, except on exit 78, which says the scope cannot be served as it stands.

The unit tells the vertex one thing, its scope — flor-vertex --scope %i, by the absolute path flor activate wrote — and the vertex works out the rest (DSGN-A0-24, DSGN-A0-10). It takes its scope's lock exclusively and holds it while it runs, exiting 69 when another process holds it. It reads receipt.json and refuses a scope that was never enrolled, is mid-enrollment, or was written by a release it cannot read. It knows its own name, hop, and asks the artifact store for the artifact addressed to it, exiting 78 when the store has none; every other path it derives from the scope. No FLORWL_* variable is set or read. These are jobs C0 gives the agent, and in A0 they are the vertex's.

What a node holds follows from the same absence. The scope root is C0's without what the agent owns, its artifacts/ being the node's artifact store and nothing else (DSGN-A0-25). The compiler emits no agent.json, the bundle carries no node principal, and nothing is created in the runtime directory (DSGN-A0-02). A node's identity is its name as the manifest states it, pinned in identity/scope.json.

The vertex serves every principal it holds material for, as in C0. In A0 a principal named without its material is a producer's mistake rather than a passing state, and the vertex's warning is where it shows (DSGN-A0-05).

Installing

A0's executables reach a host, and a scope comes to run under systemd, as the Installer designs.

Enrolling and changing a node

A rete begins as it does in C0 — a repository, rete init, the collections, a compile and a commit — with nothing that has to exist first: there is no management node to bootstrap and nothing to publish to. Every node then joins by an ordinary enrollment.

Enrollment follows C0's, and the differences are few. It verifies the bundle as C0 does, checks each artifact entry's name against the envelope that entry carries — the one job the absent intake leaves it — and hands the artifacts to the artifact store, which verifies and admits them (DSGN-A0-22, DSGN-A0-25). It opens the store with the trust domain, node and anchors the identity store's check has just accepted, so a first enrollment and a later one take their values the same way, and nothing is written until both checks have passed. And it activates nothing: its last line is the flor activate command that starts the scope, and on a live scope it refuses, naming the holder and the systemctl --user stop command that stops it (in case it is a vertex).

Artifacts are signed when compiled and carry the node's set version, and rete issue-bundle refuses an uncommitted tree (DSGN-A0-23). A bundle overtaken by a newer one is refused by the store, so the walk below can be interrupted and resumed but not reversed. A rollback reverts the sources and compiles forward; reverting the compiled tree with them brings back versions every node has passed.

A0 changes a node in one way, by re-enrolling it (DSGN-A0-06). A change is compiled and committed, and every node it touched receives a fresh bundle — carried out of band, the scope stopped, the bundle enrolled, the scope started — which re-mints every principal on that node. There is no publish and nothing syncs, so the rete converges as fast as the operator walks it. Revoking a principal is removing it from YAML and re-enrolling the nodes whose sets named it, taking effect node by node on the same walk. rete is offline throughout, and has no config.toml.

Several nodes on one host

A host can hold several nodes of one rete, as in C0: each is its own scope, retes/<node>.<rete>/, and enrolling the second takes --multi-node (ENRL-C0-41). In A0 each also runs under its own unit, flor@<node>.<rete>.

The development environment is that arrangement run by hand (DSGN-A0-15). Its processes start from scripts, terminals or a process manager, with no service manager involved — on Linux, and on macOS once C0 targets it. Its nodes are told apart by the loopback addresses their YAML declares, and each logs to its own stderr. FLOR_STATE_HOME is a convenience there, never the way nodes are kept apart. Where a development node needs more than its YAML can say, that is the node-local override ENRL-C0-04 reserves.

Validating a rete without a coordinator

A rete has a management node when it declares both reserved coordinator services, and only then do the rules requiring a management node and a user holding operator apply (DSGN-A0-13). An A0 rete declares neither; declaring exactly one is an error naming the other. A0 adds one rule of its own: a node declares exactly one vertex, and its name is hop (DSGN-A0-24). The reserved names — the two services, their groups, and the node and operator roles — are reserved in every rete whether it uses them or not, and rete init writes them into a new rete commented out. Otherwise A0 validates as C0 does.

Logging

Every A0 process opens its own log sink (DSGN-A0-16). It takes the destination from --log-stderr, else FLOR_LOG_DESTINATION, else unix:/dev/log, connects the syslog socket itself, and takes its class from what it opened; nothing provisions a sink for another process, and FLORWL_LOG_SINK is not read. A destination that is absent, unreachable or of the wrong kind stops the process with 69 or 78 rather than sending its records elsewhere. Commands log as C0's do, and rete writes to stderr only when asked.

Each record says which process wrote it and which scope it belongs to (DSGN-A0-17). The syslog identifier is flor-<workload> — the vertex's, in A0 — and the formatter writes scope{node=… rete=…} at the head of every line. Under systemd, journalctl --user-unit flor@<node>.<rete> selects a scope and -t flor-<workload> a process within it; where no unit is recorded, the line's own scope says which scope wrote it.

The line format and its vocabulary are C0's, without the counters and without the flood-safe Layer (DSGN-A0-18). Events are written so both can be added without touching them: each event C0 will count carries its bounded values as fields, with a closed reason, and each event a peer can trigger at will is a tracing event with fields. A flood is bounded by the journal's per-unit rate limit, and a count of denials is a count of log lines.

Names

A supervised workload is named workload/<node>/<name>, and the supervisor workload/<node>/agent, as Identity decides (ID-C0-09); a vertex's workload takes the vertex's declared name, and an artifact is addressed to that workload. A0 supervises nothing, yet its compiler addresses the vertex's artifact and its log frames the vertex's records by this naming.

The vertex's configuration lists the TLS principals it terminates mTLS for as its attachments, the principals attached to the vertex from above (DSGN-A0-20).

From A0 to C0

A node moves to C0 by installing C0 and re-enrolling with a fresh bundle, and nothing is migrated (DSGN-A0-21). Every part of a C0 scope root that A0 lacks comes from C0's agent or from the bundle. What A0's store left under artifacts/ stays behind, inert, unless the scope root is removed first.

Decision Log

Entries are grouped by what a change would touch.

The page

DSGN-A0-01. Record A0 as a delta on C0. This page states what A0 does differently and cites C0 for the rest: where A0 follows a C0 process it says so and names only the difference. Where A0 gives the vertex or enroll a job C0 gives the agent, the job is A0's alone, and the entries do not restate what C0 does instead. One page carries the whole delta, except the installer: A0 designs it afresh rather than as a narrowing of C0's, on the Installer page. Because A0 is C0 with its agent and coordinator removed, so nearly every mechanism A0 keeps is C0's unchanged, and a restated process — or a restated C0 alternative — is a second copy that drifts from the first. Accepting that the page is not read alone, and that a change to a cited C0 process changes A0 unless an entry here says otherwise. Rejected a self-contained A0 design, which duplicates the pages it would have to track; splitting the delta by component as C0 is split, which divides something its reader wants in one read; amending the C0 records with A0 clauses, since C0's log describes C0; ending each entry with what C0 does instead, a clause that restates C0 and goes stale with it.

What the absent agent and coordinator leave

DSGN-A0-02. Emit no agent.json, mint no node principal, and create no runtime root. A node's mgmt set is its vertex's artifact alone; the bundle carries user and service principals and no node/<name>; nothing is created in the runtime directory. A node's identity is the manifest's node claim, pinned in identity/scope.json. Because each exists for the agent: agent.json carries a supervision inventory, a sync block and an endpoint map for agent.sock; the node principal authenticates the agent to the coordinator; the runtime root holds agent.sock. A0 has neither end of any of them. Accepting that the set has no root document, its coherence being the bundle's; and that expiry is warned by whatever touches a certificate (ID-C0-23). Rejected a reduced or empty agent.json, an artifact the compiler keeps filling for no reader; a node principal minted and unused, a ninety-day bearer credential couriered for nothing and minted afresh at C0's re-enrollment (DSGN-A0-21); an empty runtime root, a mode rule and a cleanup path for a directory holding nothing.

DSGN-A0-03. Superseded by DSGN-A0-22.

DSGN-A0-22. Sign every artifact, and verify every envelope where it is admitted. rete compile signs each envelope as C0 does, and the validator's mgmt-signer rule is C0's. Every envelope is verified where it is admitted, by the artifact store, on the operator's machine and on the node alike; nothing verifies an artifact when it is read. flor enroll verifies the bundle as ENRL-C0-17 does and hands the artifacts over. Because admission is a judgement about authority over rete state, and the module that owns the material is where it belongs — one place every writer passes and no reader repeats. The boundary is the one ADR-0015 draws; its holder is not: where ADR-0015 makes the agent the sole verifier, A0 makes it the artifact store, called by whoever writes. Because on a fresh enrollment the anchors arrive in the same bundle, so the check shows only that the artifacts will work, which is the test ENRL-C0-17 sets for any check added there; from the second enrollment it weighs authority against the pinned anchors. Because signing costs nothing extra: the envelope contract requires a signature, and the signer, the key handling and the canonical form serve the manifest regardless. Accepting that the check bounds delivery and not local tampering: whoever can write into a store can write the identity material beside it. Rejected verifying no envelope on an A0 node, which leaves a contract's signature unexercised and lets a mis-cut bundle's artifacts land unchecked; verification in enroll, a second verifier beside the module that owns the material; unsigned artifacts, an unsigned form of a contract that requires a signature.

DSGN-A0-04. Superseded by DSGN-A0-23.

DSGN-A0-23. Stamp set versions, gate bundles on the commit, and refuse a rollback in the store. The compiler stamps each node's set version against the committed tree, and rete issue-bundle refuses an uncommitted tree, as ENRL-C0-35 decides. The artifact store refuses a set older than the one a node holds, and nothing overrides that refusal: the way past one is a newer bundle. Because a bundle overtaken in the courier's hands — cut before a revocation, enrolled after it — would otherwise roll its node back and re-admit the revoked principal, with nothing on either side saying so. Because the refusal costs A0 nothing: a deliberate rollback reverts the sources, never the compiled tree, and recompiles them at a higher version. Accepting that a node whose compiled tree is lost for good is re-enrolled only once that tree is restored, or its scope root removed by hand, and so is a node removed from the source and declared again. Rejected applying whatever set enroll is handed, which undoes a revocation silently; a warning in place of a refusal, which lands after the set is written; an override flag, which has no case a newer bundle does not cover; dropping the version, a contract change; a warning in place of the commit gate, which a courier's delay separates from the harm it warns about.

DSGN-A0-05. Keep C0's handling of an artifact without its material, as a defect rather than a state. The vertex serves every principal it holds material for, opens no listener for one it does not, and warns once, as ENRL-C0-36 decides. In A0 the state arises only from a producer's mistake. Because A0 has one channel, and its bundle carries each principal's material together with the set naming it. Rejected refusing such a set, which lets the identity store veto the set.

DSGN-A0-06. Roll out by re-enrollment, with no publish. Every change to a node's state reaches it in a fresh bundle — compile, commit, issue, courier, stop, enroll, start — and re-mints every principal on the node. rete ships init, validate, compile and issue-bundle, all offline, with no publish, no id issue, no config.toml and no path into flor. Because A0 has no coordinator to publish to or sync from, and a command that exists is a promise. Accepting that the fleet converges as fast as the operator walks it, and that a revocation takes effect node by node on the same walk; and that a rete keeps the signer init mints for as long as it runs A0. Rejected a publish that refuses; calling the courier step a publish, which names a mechanism A0 does not have; id issue, minting a second signer, when the one init mints lasts a year by default, longer than any A0 pilot.

Installation and supervision

DSGN-A0-07. Superseded by INST-A0-01.

DSGN-A0-08. Superseded by INST-A0-08 and INST-A0-09.

DSGN-A0-09. Superseded by DSGN-A0-24.

DSGN-A0-24. Pass the vertex its scope, and fix its name. The unit runs flor-vertex --scope %i, naming the vertex by the absolute path flor activate writes (INST-A0-09). A node declares exactly one vertex, named hop, which the validator requires; the vertex defaults --name to it and reads the artifact addressed to workload/<node>/hop through the artifact store. It derives every path from the scope; the unit sets no Environment=, and the vertex reads no FLORWL_*. Wherever a command takes a scope, the flag is --scope. Because a systemd template has one instance parameter, spent on the scope, and neither flor activate, which writes one template for every scope, nor enroll, which knows the vertex's name but would render a unit per scope, can supply more. Because a consumer asks the store for one artifact by its address, so the name has to reach the vertex before the read, and A0 has no supervisor to supply it. The fixed name is the one rete init already scaffolds (ID-C0-13). Because the FLORWL_* variables describe a supervised workload, and A0 supervises nothing. Because the value is a scope handle rather than a rete name (ENRL-C0-41). Accepting that an A0 operator does not name the vertex; that the rule is A0's, dropped by a milestone whose supervisor hands the name over, which renames no rete; and that path derivation stays in one module per holder. Rejected taking the name from the one artifact present, which needs a consumer that lists a store's contents; an artifact path baked into the unit, which cannot know the vertex's name; paths built from systemd's own state-directory specifier, resolved against systemd's notion of the directory rather than flor's; honouring FLORWL_*, a consumer with no producer; --rete, the wrong one of the two names.

DSGN-A0-10. Keep the receipt and the scope lock, with the vertex in the agent's place. receipt.json is C0's (ENRL-C0-39). The scope lock is held by flor-vertex for its life and by flor enroll for its run, and enroll refuses a live scope rather than stopping it, naming the holder. A vertex that finds the lock held exits 69. Before serving, the vertex refuses a scope whose receipt is absent, reads enrolling, or carries a schema version it cannot read, exiting 78. Because what the two guard — an interrupted enrollment, and a process reading the identity plane while enroll rewrites it — does not depend on the agent, and in A0 the vertex is that process. Because a held lock is held by an enroll or another vertex, which ends, so the vertex refuses it as a condition that may resolve rather than as a scope it cannot serve. Accepting that the lock and the receipt check live in the vertex only until a supervisor holds the scope, and move to it unchanged. Rejected deciding whether an enrollment finished from the identity store, which is written before the commit point; a lock only enroll takes, which excludes no live reader; asking systemd whether the unit is active, a read outside any lock; checking the receipt at an activation step, which a reboot bypasses.

DSGN-A0-11. Superseded by DSGN-A0-25.

DSGN-A0-25. Write artifacts through the artifact store. flor enroll hands the bundle's artifacts to the artifact store, which admits them as the node's set, opened with the trust domain, node and anchors the identity store's check has just accepted. There is no incoming/, no intake, no current pointer and no sets/. The check ENRL-C0-16 gives to whoever reads an entry's content is enroll's: it compares each artifact entry's name with the envelope that entry carries. Because the checks an intake would carry — the envelope, the destination, the signature, the version — are the store's, so what an intake adds is a staging directory and nothing else. Because incoming/ serves an intake that is the agent's, and a pointer replaces a set under a running consumer; A0 has neither, and enroll never writes under a live vertex (DSGN-A0-10). Because the entry-name check belongs to whoever reads the container, and with the intake gone that is enroll — the store receives artifacts and never a bundle. Accepting that no previous set is kept on the node, rollback being sources reverted, compiled forward and re-enrolled. Rejected enroll writing files in place, which puts path arithmetic and the checks in the caller; an incoming/ admitted by the vertex, which makes the network proxy a verifier; the versioned tree and its pointer, atomicity nothing needs.

Validation

DSGN-A0-13. Apply the management-node rules only where the reserved coordinator services are declared. Where coordinator and coordinator-publisher are both declared, the rules requiring a management node and a user holding operator apply; where neither is, they do not; where exactly one is, validation fails naming the other. The reserved names — those services, their groups, and the node and operator roles — are reserved either way and required only with a management node, and rete init scaffolds them commented out. rete.yaml is C0's. Because a rete's shape is read from what it declares, and a trigger one typo could switch off would be worse than none: mistyping one of the two names lands in the failing case. Because a rete that later grows a coordinator must not find its names taken. Accepting that A0's operator is an ordinary user holding no role, and that node and operator have no members. Rejected a declaration in rete.yaml, which copies a reserved name into authored source; a profile switch on the command line, which makes a rete's shape depend on who validates it; a separate A0 rule set; releasing the reserved names.

Scopes and hosts

DSGN-A0-15. Run the development environment without a service manager. Development processes are started directly — by scripts, terminals or a process manager — on Linux, and on macOS once C0 targets it. Several nodes of one rete on one host are several scopes (ENRL-C0-41), told apart by the loopback addresses their YAML declares, and each hand-run process logs to its own stderr. FLOR_STATE_HOME is a convenience, never the mechanism. Because the test environment — one node per host, over the Internet — runs the unit, the installer and enrollment as they ship, and a development environment tied to systemd could not be the same one on a Mac. Accepting that no development machine exercises the unit file; and that the development environment will need node-local overrides beyond what its YAML says, which are for the override ENRL-C0-04 reserves to provide. Rejected development on the shipped unit, which ties it to one platform's service manager; both, selectable, two environments for one team; designing node-local overrides here.

Logging

DSGN-A0-16. Let every process open its own log sink, and stop on a bad destination. Each flor process takes its log destination from --log-stderr, else FLOR_LOG_DESTINATION, else unix:/dev/log (INST-A0-13), and connects the syslog datagram socket itself. A process's sink class is the one it opened — a socket is syslog, stderr is private — and FLORWL_LOG_SINK is not read. An absent or unreachable destination exits 69 and a stream socket exits 78; the vertex never falls back to stderr. Commands log as C0's do. Because ADR-0014 decides that a log sink is a connected datagram socket and that a supervisor provisions it for its children; A0 has no children, so the second half is dormant and the first stands. Because systemd cannot hand a process a connected datagram socket as its stderr: its syslog output is the journal's line-splitting stream, and its file output connects a stream socket. Because A0's logs are what an evaluation of A0 reads, and a vertex running without them runs unaccountably. Accepting a larger blast radius than a supervisor's refusal would have, a logging outage at start taking forwarding with it; and that two hand-run vertices writing one redirected file carry no workload names. Rejected the journal's stdout stream, which splits a multi-line record into one entry per line; a wrapper that provisions, a supervisor in all but name; honouring FLORWL_LOG_SINK, which could only contradict the socket a process holds; falling back to stderr.

DSGN-A0-17. Frame records as flor-<workload>, and write the scope into every record. The syslog identifier is flor-<workload> — flor-agent for the supervisor, flor for the commands, and nothing of flor's for a tenant workload. The formatter writes scope{node=… rete=…} ahead of the span stack on every line, from values set when logging starts; the scope never enters the identifier. Because the journal attributes a record to its sender's unit, and every process a supervisor forks shares the supervisor's unit, so the unit tells scopes apart and the identifier has to tell processes apart. Because every program on a host shares the identifier space, where a bare workload name is ambiguous; the prefix keeps flor's records its own, as flor_ does for metrics. Because where no unit attributes a record — a plain syslog file, a process run by hand — the content is the only place the scope can be, and a span entered at start never reaches events emitted by asynchronous tasks. Because node and rete are the resource-identity tokens of C0's observability, node naming the bare node, so a line carries all three with the workload in the identifier. Accepting that both attributions inside a record are the process's own claim, the unit being the trusted one where there is one; that the scope looks like a span without being one; and that a long vertex name makes a long identifier, which some daemons cut at 32 characters. Rejected the bare workload name; the executable name, which C1's two vertices share; the scope in the identifier, which a length cap cuts where the identifier is the only attribution; adding the scope only where no unit seems to attribute the record, an inference from surroundings; leaving the scope to C0, which changes the line format after operators depend on it.

DSGN-A0-18. Build no counters and no flood-safe Layer, and keep event sites ready for both. Every event C0 will count carries its bounded values as tracing fields in C0's vocabulary, with a reason that is a closed value rather than a formatted error, and every flood-prone event is a tracing event with fields. A0 builds no counter registry and declares no dependency on one. Because C0's counters are explicit calls at the event site, needing only closed values there, and the flood-safe Layer is one module keyed by call site and one field — so both are additions, and no site is rewritten. Because a registry injected later costs a dependency on the components that count, and the vertex is the only code in A0 that will. Accepting that A0's flood bound is the journal's per-unit rate limit, which drops records indiscriminately while a flood lasts; and that A0 counts denials by counting log lines. Rejected the Layer alone, the half that is cheap to add later; counters with no readout, a surface nobody reads before monitoring exists; counters derived from events, which have no event for a byte or a retried attempt and make every counted event part of a metric; an ambient registry, global state every counting test must reset.

Naming

DSGN-A0-20. Call the TLS principals a vertex serves its attachments. The vertex payload lists the principals the vertex terminates mTLS for as attachments. Because a vertex is a point of attachment, where a layer's users attach, and the word names that relation rather than a credential — holding in C1, where a mesh vertex attaches to a link vertex in the same sense. Because workload names a supervised process (ID-C0-09), and a payload field's name lasts as long as its major version. Rejected workloads, one word for two things on one node; tls_principals, ambiguous in C1 where a mesh vertex is itself a TLS principal; settling the name at C0, a major version for the same result.

Forward

DSGN-A0-21. Move a node from A0 to C0 by re-enrollment, migrating nothing. Installing C0 and re-enrolling each node with a fresh bundle is the whole step: every part of a C0 scope root that A0 lacks is created by C0's agent or delivered in the bundle. A0's artifact file stays beside C0's set tree, inert, unless the operator removes the scope root first. Because re-enrollment is already A0's only way to change a node. Accepting one leftover file per scope. Rejected migrating A0 scope roots in C0's agent, a mechanism for a step that already exists.

On this page