Florete

Network Clock

Rete time — Coordinator-referenced, agent-disciplined, bounded like any ctrl decision

The ms-class half of Time Synchronization: where rete-wide time comes from, how it reaches every node, and why it is not an NTP deployment. The µs domain — slot alignment for scheduled MACs — is deliberately not this page's business: see Slot Clock. Terms: Glossary.

Baseline for every rete

Unlike the slot clock (design-only until a scheduled-MAC vendor integration), the network clock applies to every rete from day one — wired, CSMA, and cloud nodes included. It must therefore assume nothing about the media underneath.

What actually needs time — and what deliberately doesn't

Florete's correctness story is built on counters, not clocks: artifact ordering is version numbers, ctrl-vs-mgmt freshness is obeys_mgmt_version, revocation is membership in signed state (Ctrl Plane) — none of it depends on wall-clock time. That is a design asset, and this page preserves it. What remains for the network clock is modest:

  • Certificate validity windows (mTLS notBefore/notAfter) — seconds-class accuracy suffices, but this consumer is security-relevant and bootstrap-critical (below).
  • Timestamps of telemetry reports and logs — cross-node correlation at the Coordinator; ~10 ms rete-wide consistency serves every ctrl-timescale computation. (The radio-level events inside reports are stamped in slot-clock units — see the bridge.)
  • Coordinator internals (B1+: consensus rounds, leases) — the Time service's own affair inside its replica set, out of scope here beyond one sentence below.

Target: rete-consistent to ~10 ms; UTC-anchored whenever any source provides it. Internal consistency is the primary goal; UTC accuracy is secondary (certificates and operator-facing audit want it only loosely).

The design: Coordinator-referenced, agent-disciplined

One sentence: the Coordinator's Time service is the reference; each agent measures its offset to it over the connection it already maintains; the estimate disciplines a per-rete virtual clock. Unpacked:

  • The reference is the Coordinator. The rete already has a logically centralized, movable, partition-tolerant coordination point — time is one more thing it coordinates. No peer-to-peer time protocol runs between nodes, for the same reason ranks are ctrl state in the slot clock: where the architecture has an authority, use it; peer-to-peer is the fallback shape, not the baseline.
  • Measurement rides the existing channel. The agent already holds an authenticated mTLS connection to the Coordinator for artifact sync and telemetry. A time exchange is a four-timestamp request/response on that same channel — NTP's on-wire arithmetic, kept; NTP's deployment, declined (below). Raw samples are jittery (userspace, multi-hop, radio segments); a filter over sample history — minimum-RTT selection, regression for offset and drift, the chrony/FTSP family12 — turns them into a stable estimate. Cadence piggybacks on the agent's sync cycle; the traffic is a few bytes per exchange, negligible even at MVP scale.
  • Discipline targets a per-rete virtual clock — the OS clock is never touched. The agent maintains rete time as an offset-plus-rate function over the host's monotonic clock; flor components stamp reports and check certificate validity against rete time. Three reasons this is a rule, not a preference: a node enrolled in several retes needs several network clocks (one per trust domain — the multi-rete precedent); disciplining the OS clock needs privileges and collides with whatever else the host runs (the operator's business, not the rete's); and it mirrors the domain separation the bridge already imposes — each clock owned at its layer. The virtual clock is monotonic: small corrections slew, backward steps never happen, a forward step is allowed only at bootstrap.
  • Uncertainty is part of the value. The clock API returns an interval, not a point: rete time t, ± ε — where ε is the node's uncertainty about its offset to the reference (rete-internal consistency), from the filter and growing in holdover. Uncertainty about UTC is a strictly larger, separate quantity: it adds the reference's own UTC-anchoring uncertainty (Sources), which a well-synced node cannot see and does not need for rete-internal work. Spanner's TrueTime3 made the interval shape famous; here it lets the Coordinator weigh telemetry by the quality of the clock that stamped it, and makes holdover degradation explicit instead of silent.

Reading rete time on a node process

The agent maintains the clock, but the processes that stamp — the vertices — are separate. Shipping a timestamp from agent to vertex per stamp would fold the IPC round-trip (a systematic bias plus jitter) into exactly the value we are keeping clean. So the agent ships not timestamps but the conversion:

  • Every process on the host already shares one zero-cost, unadulterated time source: the OS monotonic clock (CLOCK_MONOTONIC, mach_absolute_time, QueryPerformanceCounter) — a vDSO read, no syscall round-trip, no privilege. It is undisciplined and carries no absolute time; it just ticks steadily and identically for every process on the host.
  • The virtual clock is an affine function of that shared tick — an offset and a rate (skew) over a monotonic origin t₀, plus an uncertainty that grows with elapsed monotonic time. Its whole state is a small coefficient set (t₀, offset, rate, ε₀, ε̇).
  • The agent computes those coefficients from its Coordinator exchanges and publishes the coefficient set into a small, read-mostly block any flor process reads locklessly — updated at ctrl cadence over the supervision relationship through which the agent already hands down verified configs in C1. A process then stamps by reading its own monotonic clock and applying the coefficients locally — no IPC in the stamping path, so no delay bias. (Reading the monotonic clock is not disciplining it — "the OS clock is never touched" means never stepped or slewed, not never read.)

It is the same discipline the R8 bridge applies to slot time: stamp at the source, or convert from a shared counter — never restamp across a delay. The radio stamps RX events below R10 for that reason; a vertex stamps against the shared monotonic clock for the same one. Two properties follow:

  • No timestamp protocol over the socket. Coefficients travel at ctrl cadence — they change slowly (offset and rate are ppm-scale) and sit outside the stamping path. A late coefficient delivery is not a stamping error, only a slightly staler correction, because agent and vertex refer the coefficients to the same monotonic origin t₀. Even if the agent dies, a vertex keeps producing rete time from its last coefficients with honestly growing ε — its own local holdover, nested inside the node's holdover against the Coordinator.
  • Multi-rete stays clean — and it has to be userspace. One virtual clock per trust domain is just one coefficient set per rete over the single shared monotonic clock. There is no kernel alternative that would hand us a per-rete named clock the agent disciplines and processes read: POSIX clock IDs are a fixed enum; the only pluggable ones — PTP dynamic clocks via FD_TO_CLOCKID — need a kernel driver as their backing time source (a custom software-PHC module, non-portable and root-only); and the one facility that virtualizes clocks per-context, Linux time namespaces, offsets only CLOCK_MONOTONIC/BOOTTIME, by a static offset, never a disciplined REALTIME. So a per-rete disciplined wall clock has no portable, unprivileged kernel home. Synthesizing it over the shared monotonic read is barely even a workaround: it is exactly what the kernel already does for the single global CLOCK_REALTIME — a userspace daemon (adjtimex) disciplining offset and frequency over the clocksource — just replicated per-rete, in userspace, because the kernel offers only one and time namespaces decline to multiply it.

Our processes, and everyone else. The scheme fully covers flor's own processes: a vertex links a flor library call that reads the coefficient block and the monotonic clock — nothing intercepted, nothing leaked, and this is all the MVP telemetry path needs. Making an unmodified third-party workload read rete time is the harder, genuinely open case: its clock_gettime/gettimeofday must be interposed. LD_PRELOAD over the same shared coefficient block is the obvious candidate and works for dynamically-linked libc callers — but it leaks, because Go binaries issue vDSO/syscalls without libc and bypass it, static binaries likewise, and seccomp-notify cannot rescue it since the vDSO read never traps to a syscall. Full transparency is thus an open OS-integration question, deliberately deferred: the stampers that matter now are flor's vertices, a typical third-party payload (a video server) wants its own wall clock rather than the rete's, and the real future consumers — distributed apps correlating logs across nodes — are what make it worth solving later.

Why not an NTP/PTP deployment

To be fair first: NTP's architecture — client-server, hierarchical, a reference disciplining leaves — is exactly ours, and its algorithms are proven; we reuse them. What is declined is the deployment shape:

  1. A parallel, unauthenticated trust surface. Time influences certificate validity, so time input is security input — it must arrive authenticated. Plain NTP isn't; NTS4 fixes that with a parallel credential system, which is precisely what Florete forbids elsewhere (the R3 rule: no credentials outside the rete's X.509/SPIFFE trust). We already own an authenticated channel to the natural reference; using anything else creates a second trust plane for a security-relevant input.
  2. A daemon and config outside the facade. chrony/ntpd per node means per-node configuration that no compiled artifact describes and no signature covers — exactly the drift the config model exists to prevent. In this design, time distribution has no configuration surface of its own: it inherits the agent's.
  3. PTP assumes the path cooperates. IEEE 1588's precision comes from hardware timestamping and transparent/boundary clocks at every hop5; our hops are userspace proxies. PTP over an overlay degrades to NTP-quality while keeping PTP's operational weight.
  4. TDMA paths bias the math. Two-way delay estimation assumes symmetric paths. A half-duplex or TDMA segment gates the return direction (the E3 effect), turning asymmetry into a systematic offset error, not noise. Generic NTP can neither see nor correct this. Our shape can: the error stays within the ms-class budget for MVP; the Coordinator can correct it where it matters, since it uniquely knows topology and per-link telemetry, radio latencies included (an assist no generic time server has — future refinement); and the slot-clock assist bypasses the biased segment entirely.

In one line: decline the protocol, keep the math.

Time is a bounded ctrl decision

The Florete-native piece. Wrong time is not merely inconvenient — a clock dragged far enough manipulates certificate validity, so time assertions get the same treatment as forwarding tables: ctrl-class decisions, verified locally against operator-signed bounds before the agent applies them.

  • Mgmt bounds on time: maximum step, maximum slew rate, maximum accepted uncertainty — a bounds vocabulary grown feature-by-feature like every other (the typed-rule discipline).
  • The signed floor: every verified mgmt/ctrl artifact carries a signed generated_at — a cryptographically backed lower bound on now. The agent never accepts rete time earlier than the newest signed artifact it holds; ordinary operator activity keeps raising the floor for free.
  • Oscillator sanity: an offset trajectory implying absurd local drift (a host crystal is ppm-class, not percent-class) is rejected on physics alone.
  • Out-of-bounds assertion → reject, hold last-known-good estimate, raise an alarm — the standard ctrl-rejection behavior.

This slots time directly into the hijacked-CP analysis: a hijacked Time service can skew rete time only slowly (slew bounds) and only forward of the floor — and since Florete revocation is membership in signed state rather than certificate expiry, stretching validity windows buys an attacker little. What remains is telemetry-correlation skew: observability damage. The class-level summary holds — access survives; observability degrades — which is the DoS-class outcome the bounded-CP model promises.

Sources: how the Coordinator gets its time

The slot clock's ladder pattern, applied centrally — a selection policy at the Time service, nothing on the wire:

  1. External UTC at the reference — the Coordinator's host is NTP-disciplined (cloud) or fed by operator time infrastructure. UTC discipline happens at the reference only; it is never deployed rete-wide.
  2. A GNSS-fed member node — the source flows upward: an agent whose host carries GNSS/PPS reports it as a candidate source with quality metadata; the Time service adopts it. The reference stays the Coordinator; the source is a node — the same distinction as rank-0 designation being a ctrl decision.
  3. None — the Coordinator's host clock free-runs. Rete time stays internally consistent — every node tracks the Coordinator regardless of what disciplines it — and simply drifts from UTC as a whole. Nodes cannot tell this case from an externally-disciplined one and do not need to: nothing rete-internal (telemetry correlation, the generated_at floor, cert validity within its margin) depends on the anchor. What is lost is only UTC anchoring, and that loss is a Coordinator-level fact — the Coordinator knows it has no external source, and can surface a growing rete-wide UTC-uncertainty on its operator/northbound API. That is a distinct quantity from the per-node sync uncertainty ε above, which stays small throughout: a node is well-synced to a reference that is itself adrift from UTC.

Degradation

  • Coordinator unreachableholdover: the agent free-runs on its last offset-and-drift estimate, ε growing at oscillator rate (~20 ppm ≈ 1.7 s/day) — telemetry correlation degrades gracefully and certificate checks tolerate days of it. No re-election machinery at baseline: nothing at ms class needs a fast replacement reference, which is exactly why the slot clock's rank-tree machinery is not replicated here. If coordinator-less operation ever needs tight cross-node time, that machinery — a rank tree over links, local rule, monotonicity — is the ready-made shape, at ms scale; deliberately deferred.
  • Partition (B1+): an emergent partition-local Coordinator (Coordinator evolution) brings a partition-local time reference with it; on merge, rete time re-converges like any other ctrl state — slew-bounded, no step.
  • Distributed Coordinator (B1+): replicas agree on time internally (consensus among themselves — the Time service's own problem) and present one logical time outward; nothing on the agent side changes.

The slot-clock assist (extension, not baseline)

Where a scheduled radio sync domain exists, radio time is a µs-class timebase shared by every member — and the frame-N ↔ host-time mapping is already telemetry. That composes into a time-transfer path: a node holding a good network-clock estimate publishes its mapping; any member of the same domain hears frame N and transfers rete time across the radio at µs quality — bypassing the TDMA path-asymmetry bias entirely, since no round trip is involved. Constraints keep it an extension: it works only within a sync domain, only on scheduled MACs, and the baseline must close without it — the exact status GNSS has in the slot clock's ladder. (The symmetry is stated in the hub: each domain can accelerate the other; neither depends on the other.)

Bootstrap

The circularity: validating the Coordinator's certificate needs time; getting time needs that connection. Breaking it uses material the node already holds:

  • The signed floor, again. The enrollment bundle and any previously verified artifacts give a signed lower bound on now before any connection exists — enough to reject long-expired material and to pass validity checks in the common case. This is Roughtime's6 coarse-then-tight shape, with the coarse bound derived from already-pinned signed material instead of a new protocol.
  • Step once, then slew. First contact validates the pinned identity (ADR-0011) against the floor-based clock with a relaxed validity window; the first in-bounds time fix may step the virtual clock forward once; thereafter strict validation and slew-only discipline apply. The exact relaxed-window semantics are an open question below.

Known approaches

SystemWhat it isWhat we take
NTP / chrony1The Internet's client-server time hierarchy; chrony's regression-based disciplineThe on-wire arithmetic and the filter/discipline algorithms — run inside our authenticated channel; the deployment shape declined
NTS4Authenticated NTP (TLS-derived keys)The confirmation that time needs authentication — and why we decline it: it is a parallel credential system, and we already own an authenticated channel
IEEE 1588 PTP5Hardware-timestamped master-slave sync with on-path transparent clocksThe reminder that precision comes from the path's cooperation — which an overlay doesn't have; inapplicable here
Roughtime6Signed coarse time, designed for certificate-validity bootstrappingThe bootstrap shape: coarse-then-tight; our coarse bound comes from already-signed artifacts instead of a new protocol
Spanner TrueTime3A clock API returning uncertainty intervals, GPS+atomic-backedUncertainty as first-class: rete time is (t, ± ε), and ε grows honestly in holdover
HLC7Hybrid logical clocks — causal counters bounded to physical timeRelated art for "correctness from counters": if telemetry ever needs causal ordering, HLC-style stamps slot in without touching this design

Open questions

  • Wire shape of the exchange (piggyback on the sync cycle vs a dedicated lightweight stream) and the cadence policy.
  • Exposing rete time to node processes: the coefficient block's transport (shared memory vs the still-undesigned supervision/telemetry socket), its shape, and push cadence; and transparent interposition for unmodified third-party workloads (LD_PRELOAD with its Go/static-binary leaks, or a more robust mechanism) — deferred past MVP, where flor's own vertices are the only stampers.
  • Filter/discipline algorithm choice and the uncertainty model — what ε the API reports, its two components (offset-to-reference vs the reference's UTC-anchoring), and how each composes with holdover.
  • The bounds vocabulary for time (max step, max slew, max uncertainty) — grown like all bounds; which parts are mgmt vs ctrl vs local constants.
  • GNSS-node-as-source: the reporting format, quality metadata, and PPS handling at the host.
  • Coordinator-assisted asymmetry correction from topology + telemetry — value vs complexity, and whether the slot-clock assist makes it moot on radio segments.
  • Bootstrap details: relaxed-window semantics at first contact, the step-once rule, interaction with enrollment tooling.
  • Telemetry stamps: plain rete time or HLC-style causal stamps.
  • The Time service's internals in the distributed Coordinator (consensus time) and its northbound API — B1 (Coordinator open questions).

Footnotes

  1. RFC 5905 — NTPv4; chrony is the modern reference implementation, notable for regression-based offset/drift estimation that outperforms the classic PLL under jittery links. 2

  2. The same regression family as FTSP's drift estimation — see the Slot Clock's known approaches.

  3. Corbett et al., Spanner: Google's Globally-Distributed Database (OSDI 2012) — TrueTime exposes clock uncertainty as an interval API backed by GPS and atomic references. 2

  4. RFC 8915 — Network Time Security for NTP — authenticates NTP with TLS-derived keys; a parallel credential system from Florete's standpoint. 2

  5. IEEE 1588 Precision Time Protocol — sub-µs over cooperating LANs via hardware timestamping and transparent clocks at each hop. 2

  6. Roughtime — cryptographically signed coarse time (hundreds of ms), designed so a device can validate certificates before it has trustworthy fine time. 2

  7. Kulkarni et al., Logical Physical Clocks (OPODIS 2014) — hybrid logical clocks: causality-preserving counters bounded to physical time, used where ordering matters more than instants.

On this page