Florete

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-stderr redirects the stream, with FLOR_LOG_DESTINATION and [log] destination in config.toml as its environment and file forms; RUST_LOG raises the level, which defaults to error for commands and info for the daemon. A command that asks another process to act does not log, so flor agent sync is silent and the daemon records the sync it ran.
  • rete has no syslog destination. Its stream defaults to stderr and to off, and RUST_LOG turns 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. With RUST_LOG set 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.name

Notes:

  • 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/*.yml under that root (skipping .flor/, bundles/, dotfiles; honoring include/exclude in rete.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 is source.include/source.exclude in rete.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.
  • rete runs inside a rete repository or not at all — it walks up from the working directory to find rete.yaml, as git finds .git, and refuses outside one. rete init is the one exception and has to be: it is what creates rete.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 what issue-bundle gates 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 --rete flag to say which rete it means — and none carries an output path either: compile always writes <repo>/.flor/compiled/ and issue-bundle always 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 by RETE_HOME), never in the repository: one identity store per rete at retes/<scope>/identity/, holding the root CA at authorities/root.pem, the mgmt signer at principals/management-plane/<name>.pem, and scope.json naming the trust domain and no node — the same store a node keeps, differently populated (Identity). rete reads 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 issue produces an identity; a bare verb like compile or publish acts on the rete implicitly. There is no ca group in C0: rete init is 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-bundle computes which principals live on a node (from users.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's sync block already names the coordinator, and a projected value transcribed into a second place is a divergence waiting to happen.
  • --kind selects 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 gets keyUsage: digitalSignature, keyEncipherment and extKeyUsage: clientAuth, with serverAuth as well for a targetable kind; a signing kind's gets keyUsage: digitalSignature only; and workload, having no policy yet, is refused (Identity, ID-C0-04). What C0 exposes on the CLI is narrower: id issue accepts management-plane alone, C0 having no ctrl plane, and TLS-principal certs are issued as part of issue-bundle rather 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 issue and issue-bundle each mint and sign inside one process — so rete id keygen and rete id sign are named here as the shape Flow B will take, and are not implemented surface.
  • rete warns on the certificates it touches — the mgmt signer's at every compile, the root CA's at every issue-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 compile writes artifacts to .flor/compiled/mgmt/<node>/ inside the repo (agent.json and one <vertex>.json per vertex, per node, flat; plus coordinator.json on 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 publish uploads the current .flor/compiled/ tree to the rete's coordinator; nodes fetch their own artifacts on the next flor 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-bundle refuses 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 publish is the only command that needs the network, and the only one that needs the local agent. It reaches coordinator-publisher through the operator's SOCKS5 listener, discovered over agent.sock; everything else — init, id issue, issue-bundle, validate, compile — is fully offline. See rete local wiring below.
  • Nothing in rete affects 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 by rete init, and filled by hand. There is no prompt and no --operator flag, because init runs once per rete on one workstation while every other operator, and the same operator's second machine, arrives by git clone and never runs it; filling the value at init would serve only the machine that needed it least. A publish that finds the key unset fails naming the file, the key, and the principals in users.yaml carrying the operator role — 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 the operator role" does not answer it either.
  • The address is derived, never configured. rete looks up the per-rete control socket (agent.sock, under the scope's runtime root — the same socket flor agent status uses) and queries the agent's endpoint-discovery map for that principal's local inbound. The agent answers 127.0.0.1:NNNN from its compiler-projected inbounds map, staying role-agnostic — roles are compiled away, and the agent knows principals, not roles — and rete uses 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:NNNN is 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-mode rete against a system-mode agent — and the case where agent.sock has 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-publisher and their groups are reserved names the validator enforces (Coordinator), so the destination is coordinator-publisher under the repo's rete.name — a rete-scoped principal, with no node segment, which is the same value the compiler projects into every node's agent.json sync block. rete is 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 variables

flor-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 own

Notes:

  • 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 keygen and rete id sign when it lands: identity work is rete's, and a principal running rete on its own machine is running a tool the installer already put there.
  • flor enroll <bundle> behaviour is specified in Enrollment.
  • flor agent run is the long-running supervisor process per node. See Agent.
  • flor agent sync is 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 confirmed safety 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-vertex is the data-plane daemon. In production the agent spawns it with the uniform FLORWL_* environment (see Agent § Supervision contract): it reads the config FLORWL_MGMT_ARTIFACT points at (plus FLORWL_CTRL_ARTIFACT for 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 the mgmt/current pointer — 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 status reads 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; status renders it for humans, and tools read fields directly — rete uses 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.

On this page