CLI Surface
rete and flor command reference for C0
CLI Surface
Two binaries, split by audience:
rete— operator-facing; present on every install, used from the operator's workstation. Authors rete state: CA operations, validation, compilation, bundle issuance. Never touches a running agent. It ships everywhere because artifact composition is a set, not a per-role subset — a node that never runs it is the cost of keeping placement a config fact — but the operator's workstation is where it is used.flor— every node. The daemon plus the node-local user CLI: enrollment, apply, status. One binary serves end users, server admins, and the system service.
This matches the author-vs-participant split used by kubectl/kubelet, terraform/agent, and similar tooling — and the names mirror the layer each acts on: rete administers the rete (the trust domain — not any single flor, and not one Coordinator), while flor is the node agent, exactly as kubectl administers the cluster and kubelet runs on the node. The tool takes the name of the thing it administers, which is why it is not named for flor: it never controls a flor, it authors rete state (Enrollment § Decision Log, ENRL-C0-42). An end user installing Florete on their laptop gets flor with a small, focused command set and simply never reaches for rete, which is present but not theirs to use; operators use it on their own machine for rete-authoring work.
Both binaries share the same output rules, and differ on one (Installer § Logging). User messages go to stderr, written for the person who typed the command; stdout carries a command's data and nothing else, so flor agent status | jq stays meaningful. Where they part is the log stream, which exists for processes nobody is watching:
flor's commands and daemon log to syslog. They change host state an admin may have to reconstruct later —install,uninstall,enroll,agent enable/disable— and the daemon has no terminal at all.--log-stderrredirects the stream, withFLOR_LOG_DESTINATIONand[log] destinationinconfig.tomlas its environment and file forms;RUST_LOGraises the level, which defaults toerrorfor commands andinfofor the daemon. A command that asks another process to act does not log, soflor agent syncis silent and the daemon records the sync it ran.retehas no syslog destination. Its stream defaults to stderr and to off, andRUST_LOGturns it on. It changes rete state — whose record is the git commit — and operator-local material, and the person who ran it is watching the terminal. WithRUST_LOGset a failure appears twice, once as a user message and once as a log record; that is correct for a stream someone deliberately switched on.
rete reads none of flor's configuration, and exactly one of its locations: agent.sock, needed by publish alone.
rete (operator, authoring)
rete init <rete-name> # once per rete, inside a fresh repo: scaffold
# the YAML collections, the reserved coordinator
# entries commented out, mint the root CA and
# the management-plane/mgmt-signer, write
# the operator tree's scope.json naming the
# trust domain, and write the tool's own
# config template for this rete. The only
# way a root CA comes into existence.
rete id issue --kind management-plane
--name <name> # mint a keypair that stays in the operator's
# custody and sign its cert. Signing kinds
# only: a TLS key issued here would have
# nowhere to go; C0 has no ctrl plane, so
# management-plane alone. This is how a
# mgmt signer is rotated — the compiler signs
# artifacts with the key it produces.
rete issue-bundle --node <node-name>
# pack the per-node enrollment bundle from
# the committed compiled tree: trust anchors,
# the node identity, and every user and
# service principal YAML places on this
# node, plus that node's artifact set.
# Compiles nothing, and refuses when the
# compiled tree is not committed. Needs no
# coordinator and no network at all.
# Flow A only in C0.
# Always writes <repo>/bundles/<node>.bundle.
rete validate # schema + cross-ref + access consistency
rete compile # compile the rete; always writes
# <repo>/.flor/compiled/
rete publish # push compiled artifacts to the rete
# coordinator (see Coordinator). No target
# argument: coordinator-publisher is a
# reserved service name, so the destination
# follows from it and rete.nameNotes:
- Source discovery. The repo root is found by walking up from the working directory to
rete.yaml, never named by a flag; outside a repo every command refuses. Then recursively glob*.yaml/*.ymlunder that root (skipping.flor/,bundles/, dotfiles; honoringinclude/excludeinrete.yaml), then merge same-kind collections into one whole-view before compiling. File layout is operator-chosen — see Source Layout. There is no per-invocation file override: narrowing which files form the rete issource.include/source.excludeinrete.yaml, which is committed and reviewable, and a flag that selected a different set per run would be a second discovery rule with no repo root to anchor it. reteruns inside a rete repository or not at all — it walks up from the working directory to findrete.yaml, as git finds.git, and refuses outside one.rete initis the one exception and has to be: it is what createsrete.yaml, so it refuses when the walk-up succeeds instead — and it requires a git repository, refusing outside one, since the compiled tree's commit state is whatissue-bundlegates on (Enrollment § Decision Log, ENRL-C0-35). The scope, the paths and the rete's name all follow from that, so no command carries a--reteflag to say which rete it means — and none carries an output path either:compilealways writes<repo>/.flor/compiled/andissue-bundlealways writes<repo>/bundles/<node>.bundle. The compiled tree is an input to the next compile, being the version ledger, so redirecting it elsewhere produces a tree that compiles from the wrong history.- Operator-held material lives in
rete's own tree,~/.rete(overridden whole byRETE_HOME), never in the repository: one identity store per rete atretes/<scope>/identity/, holding the root CA atauthorities/root.pem, the mgmt signer atprincipals/management-plane/<name>.pem, andscope.jsonnaming the trust domain and no node — the same store a node keeps, differently populated (Identity).retereads the trust domain out of that record, never off the directory it found the tree in (Identity § Decision Log, ID-C0-29). The repository holds YAML source and the compiled tree and no secret and no certificate. The root key stays there in C0 — no command takes a path to it — and the hardening that would justify one is an intermediate; see Enrollment § Genesis. - A command group is named for its subject, never its actor.
id issueproduces an identity; a bare verb likecompileorpublishacts on the rete implicitly. There is nocagroup in C0:rete initis the only thing that creates a root CA, and a group for CA rotation arrives with rotation. Throughout these pages a root CA is an issuing authority and a trust anchor is what a verifier validates against; a bare "CA" is never used, because the two stop coinciding as soon as intermediates exist. rete issue-bundlecomputes which principals live on a node (fromusers.yaml+services.yaml), issues each of their certs, and packages them with the trust anchors and the node's artifact set as the committed tree holds it. One bundle per node, not per principal — a node hosting five services would otherwise mean five deliveries and five bearer files. The bundle carries no coordinator URL and no expected coordinator identity:agent.json'ssyncblock already names the coordinator, and a projected value transcribed into a second place is a divergence waiting to happen.--kindselects the SPIFFE path namespace and, through it, the X.509 extension policy (ADR-0005). Seven kinds make up the identity model: a TLS kind's certificate getskeyUsage: digitalSignature, keyEnciphermentandextKeyUsage: clientAuth, withserverAuthas well for a targetable kind; a signing kind's getskeyUsage: digitalSignatureonly; andworkload, having no policy yet, is refused (Identity, ID-C0-04). What C0 exposes on the CLI is narrower:id issueacceptsmanagement-planealone, C0 having no ctrl plane, and TLS-principal certs are issued as part ofissue-bundlerather than one at a time.- A CSR-consuming sign command belongs to Flow B and is not part of C0 (Enrollment). Nothing in C0 reaches it —
id issueandissue-bundleeach mint and sign inside one process — sorete id keygenandrete id signare named here as the shape Flow B will take, and are not implemented surface. retewarns on the certificates it touches — the mgmt signer's at everycompile, the root CA's at everyissue-bundle— when either enters the last 20% of its validity, or 14 days, whichever is longer. It is the only component whose warning reaches the person who can replace them (Identity § Decision Log, ID-C0-23).rete compilewrites artifacts to.flor/compiled/mgmt/<node>/inside the repo (agent.jsonand one<vertex>.jsonper vertex, per node, flat; pluscoordinator.jsonon the management node). Artifacts contain no filesystem references — identity is referenced by SPIFFE ID and materialized by each node's identity store (see Agent § Artifact handling). No secrets in artifacts; committing them turns the git log into an auditable shipment history. Nodes don't pull from git; they fetch from the coordinator (see Coordinator).rete publishuploads the current.flor/compiled/tree to the rete's coordinator; nodes fetch their own artifacts on the nextflor agent sync. Publish is separate from compile so the operator can review the diff (and the audit commit) before the change is visible to nodes: publish is the review gate. Publish is idempotent — re-running with the same tree is a no-op on the server.- The operator's order is
compile→ review → commit →publish→issue-bundle, and only the commit is enforced.issue-bundlerefuses an uncommitted compiled tree and requires nothing else — not a successful publish, not a reachable coordinator (Enrollment § Decision Log, ENRL-C0-35). The repository is the rete's source of truth and the coordinator is a cache of it, so a bundle is cut from the source; publishing promptly is what keeps the cache from lagging, not a precondition of issuing. That independence is deliberate and load-bearing: a dead management node is repaired by re-enrolling it, which needs a bundle, so a bundle that needed a publish would need the coordinator it is meant to replace (ENRL-C0-37). rete publishis the only command that needs the network, and the only one that needs the local agent. It reachescoordinator-publisherthrough the operator's SOCKS5 listener, discovered overagent.sock; everything else —init,id issue,issue-bundle,validate,compile— is fully offline. Seeretelocal wiring below.- Nothing in
reteaffects running agents directly. It reads and writes the rete repo plus operator-local CA material, and talks to the coordinator over the rete's own Florete. Safe to run anywhere the operator has the repo checked out and is enrolled as an operator principal.
rete local wiring
rete publish talks to coordinator-publisher over the rete's own mTLS, so it needs a way into the rete. It reaches one through the local flor agent rather than through configuration of its own.
- It is told one thing per rete: the operator's own principal name, in
~/.rete/retes/<scope>/config.toml— written as a commented template byrete init, and filled by hand. There is no prompt and no--operatorflag, becauseinitruns once per rete on one workstation while every other operator, and the same operator's second machine, arrives bygit cloneand never runs it; filling the value atinitwould serve only the machine that needed it least. Apublishthat finds the key unset fails naming the file, the key, and the principals inusers.yamlcarrying theoperatorrole — the legal values, not just the schema. That fact is not derivable — the repository describes the rete, and what is missing is which human is at the keyboard; a rete may declare several operators, so "whichever principal holds theoperatorrole" does not answer it either. - The address is derived, never configured.
retelooks up the per-rete control socket (agent.sock, under the scope's runtime root — the same socketflor agent statususes) and queries the agent's endpoint-discovery map for that principal's local inbound. The agent answers127.0.0.1:NNNNfrom its compiler-projectedinboundsmap, staying role-agnostic — roles are compiled away, and the agent knows principals, not roles — andreteuses that as its HTTP client's SOCKS5 proxy. A principal name is stable and a projected port is not, which is why the stable half is the configured half. --socks5 127.0.0.1:NNNNis the escape, and it is settable in the same file rather than only as a flag. It covers an agent that is not running, an operator who prefers not to depend on one, a user-modereteagainst a system-mode agent — and the case whereagent.sockhas not shipped at all, in which case configuring the address is the accepted degradation rather than a rejected option.- The publish target is derived, never configured and never named on the command line.
coordinator,coordinator-publisherand their groups are reserved names the validator enforces (Coordinator), so the destination iscoordinator-publisherunder the repo'srete.name— a rete-scoped principal, with no node segment, which is the same value the compiler projects into every node'sagent.jsonsync block.reteis that compiler, so publish reads a fact it has just computed rather than discovering one.
The coupling is shallow and deliberate: rete needs the rete-scope agent.sock and the repository path, and nothing else of flor's.
The decisions behind this section — what rete is told, what it derives, and why agent.sock is not a public interface — are recorded as ENRL-C0-09 and ENRL-C0-10 in Enrollment § Decision Log; this page collects the CLI of many features and keeps no log of its own.
flor (node, daemon + user CLI)
flor enroll <bundle> [--multi-node] # bootstrap a scope (see Enrollment)
# enrollment beside another node of the same
# rete is refused unless --multi-node
flor install [--system|--user] # install or upgrade this host: place executables
# + per-OS service wrappers, write the install
# receipt; run by install.sh; idempotent.
# Any release installs over any other — going
# back is installing the older one — and it
# never changes which scopes are active.
# Migrations are not its business (see Installer)
flor uninstall [--purge] # stop agents, remove what it placed: binaries,
# wrappers, runtime roots and receipt.
# Scope roots, config.toml and the flor user
# survive unless --purge (see Installer)
flor version # release version, install mode, every resolved
# directory, and the release this one replaced
flor agent run [--scope <scope>] # the supervisor daemon (run by the OS service
# wrapper); reads agent.json from the active
# artifact set; auto-detects the scope if only
# one is enrolled; supervises one
# `flor-vertex` in C0 (+ coordinator on mgmt01).
# One agent per scope, enforced by the scope
# lock
flor agent sync [--scope <scope>] # fetch + verify + install latest artifacts,
[--dry-run] # then restart the vertex. Scope auto-detected
# if only one is enrolled. --dry-run shows
# which version would install, no restart.
# (commit-timeout/--confirm land in C1)
flor agent status [--scope <scope>] # local agent state + active artifact version
flor agent enable [--scope <scope>] # activate this scope's service wrapper and
flor agent disable [--scope <scope>] # start it; disable stops and deregisters it.
# Scope auto-detected if only one is enrolled,
# as elsewhere. Needed to resume a scope that
# survived an uninstall, and to leave one rete
# without touching others (see Installer)flor-vertex (agent-spawned, data plane)
flor-vertex [--scope <scope>] # the data-plane daemon. In production the agent
[--name <name>] # spawns it with full FLORWL_* context — no flags.
[--set <version>] # Flags exist for debugging only, as explicit
# overrides of the same variablesflor-coordinator (agent-spawned, management node)
flor-coordinator # the store-and-serve management-node service.
# Spawned by the agent with the same uniform
# FLORWL_* context and no flags of its ownNotes:
- Flow B has no command in C0. The security-purist path — a principal generating its own keypair and sending only a CSR — is deferred (an open question for C1, see C1 Scope), and its commands are
rete id keygenandrete id signwhen it lands: identity work isrete's, and a principal runningreteon its own machine is running a tool the installer already put there. flor enroll <bundle>behaviour is specified in Enrollment.flor agent runis the long-running supervisor process per node. See Agent.flor agent syncis a thin client of the running agent's socket — the daemon executes the sync, see Agent § Sync.- No commit-timeout / auto-rollback in C0. A sync activates the new artifacts and restarts the vertex; there is no confirm-or-roll-back guard. The Cisco-IOS-style
commit confirmedsafety net lands in C1. In C0 a bad sync is bounded by keeping an out-of-band emergency-access path (see Distribution & Reload · Safety Net), which suffices for limited pilots that never rely on Florete as the single access path. flor-vertexis the data-plane daemon. In production the agent spawns it with the uniformFLORWL_*environment (see Agent § Supervision contract): it reads the configFLORWL_MGMT_ARTIFACTpoints at (plusFLORWL_CTRL_ARTIFACTfor a mesh vertex), resolves identity material against the scope root, and runs the workloads/io/transport-endpoint/connection-manager described there. The CLI flags are debug-only overrides of the same variables, all explicit: no auto-detection, and the vertex never reads themgmt/currentpointer — one uniform resolution path whatever the source. It does not re-verify signatures (the agent is the sole verifier); it is unaware of the agent, the coordinator, and the rest of the rete.flor agent statusreads from a per-rete Unix socket (agent.sock, under the scope's runtime root —/run/flor/<scope>/or the user-mode equivalent) exposed by that rete's agent. The socket speaks a structured protocol;statusrenders it for humans, and tools read fields directly —reteuses the endpoint-discovery map this way.--scope <scope>selects which socket to use; auto-detected when only one scope is enrolled. A future tray-icon GUI (B1+) will be another client of the same socket — CLI and GUI share the same daemon API.
Explicitly deferred from C0:
- Any YAML-mutation subcommands.
- Flow B / CSR-based enrollment wired into onboarding (
rete id keygen,rete id sign) — open question for C1. - Multi-hop paths and label allocation (C1 feature).
- SIGHUP hot reload.
- CRL / OCSP. (Revocation is by name removal +
flor agent sync.) - Delta state updates.
- iptables-based transparent outbound redirection for services that can't speak SOCKS5.