onefrontieronefrontier.ai

docs · launch-kit contract

package/docs/functional_requirements/functional_requirements_launch-kit.mdrendered from source at build

The launch kit’s function contract

Functional Requirements — the onefrontier launch kit

package/ — the open-source kit for renting a GPU on vast.ai, serving a model on it, and reaching it over a mutual-TLS QUIC tunnel. The laws' home is package/AGENTS.md; the structural map is docs/architecture_onefrontier.md §The planes (Kit). This file is the kit's function contract.

Must

  • One command (package/launch/snipe.py, default profile 4x6000-glm-flash) with zero tool round trips: the very next action after a qualifying offer is seen is the create PUT, from the same bundles response.
  • Single source of truth: every filter number, geo country, name rule, port law, watch band, and model fact reads package/launch/snipe-params.yaml — no value in the kit duplicates a home the params file owns.
  • On-demand listings only: every bundles query pins type: on-demand — an interruptible (spot) ask is never a candidate; its rate dies with the host's first preemption, and a sniped cell must hold its rate.
  • Fit gate before create: weights/card (checkpoint/TP) must fit under the profile's headroom of the offer card's VRAM — the paid clock starts at loading, never at the OOM. A candidate failing the gate is skipped; a market where nothing passes halts with the fit reason, no re-scan, no PUT.
  • Vanish tolerance: offer gone between GET and PUT, instance gone or dead between create and provisioning → walk to the next candidate from the same response; the market is re-scanned only when the candidate list is exhausted (--rescans, default 1). A known-fatal host failure in the instance's status_msg (a broken container daemon, a full host disk — the two signatures seen live) ends the provisioning wait early instead of burning the whole budget on a box that will never come up.
  • Pin = preference: --offer reorders the qualifying list; a dead pin warns and falls back. Never a halt on a vanished id.
  • Derived identity: zone from the recorded offer's geolocation (token match against the params geo map), a box name minted from the pattern in naming.pattern (a name minted before the create carries no id, so a dry run must mark the serial it prints as provisional); port pairs (ssh, qqt client) and the qqt UDP tun walked past every collision source (live listeners TCP+UDP, named config files, systemd unit binds and -r forwards). A profile may publish extra ports (a portal front) via create.publish_ports; any publishing profile (tun or portal) gates its offers on direct_port_count >= 1 — a host with no direct port cannot map the publish.
  • Instant report: offer id, instance id, price the instant create returns; ssh coords the moment the account record carries them; a record JSON under $XDG_STATE_HOME/onefrontier/snipe/<profile>/.
  • Watch (log-only): package/launch/watch.py polls the market on a cadence using the profile's own filter and prints a HIT line per qualifying offer, keyless (no Authorization header), tagging RISKY any host whose reliability2 sits under watch.risky_below — named, never filtered.
  • Price-lock (one-shot): package/launch/watch.py --arm fires the snipe the instant a qualifying offer appears — a vast.ai rate is fixed at contract creation, so the lock is the create. One fire per run; a second armed watcher is refused by the lock file, and an arm is refused while a live cell of the same profile sits in the account — no stacking, ever.
  • Verify: package/launch/verify.py waits out provisioning (a single absent account-list poll is never a verdict — the listing flickers a just-created contract; only a sustained absence halts), then checks over ssh that /v1/models answers with the served name and a chat completion returns content, and stamps the record verified.
  • Offline suite green: python3 -m pytest package/launch/tests/ -q — mocked vast HTTP only; the suite enforces the HTTP law structurally (the snipe: bundles GET, instances GET, asks PUT; the log-only watcher: bundles GET — an armed watcher adds one account read, the stacking check, before firing; the verifier: instances GET only) and pins the create body, fit gate, naming/port walk, watch band, and verify stamps.
  • --model resolution: every profile serving the model is merged and walked cheapest-first; the fit gate decides what RUNS — cheaper hardware that cannot hold the model is skipped and named.
  • qqt tunnel, config-gated: a profile with create.publish_quic: true publishes the box's UDP tun port in the create env and bakes a guarded qqt server start into the onstart (anchored guard, cold-boot safe); publish_quic: false yields a plain ssh cell. The guard arms the box's loopback metrics endpoint (--enable-metrics) and the stderr stats line (--stats), both carried by the kit's own qqt source — a box running an older binary dies at arg parse, and the guard's || swallows that, so the door is silently absent with one line in server.log. That is the reason the kit ships its own qqt rather than using whatever the image carries; reachable only through the tunnel; a publishing profile's offer query additionally requires direct_port_count >= 1 (the publish maps nothing on a port-blocked host). Both launch surfaces (the snipe, the CLI launcher) render the same publish + guard from the same profile fields.
  • Both ends are observable with no infrastructure: qqt server and qqt client take --stats [secs], which writes one logfmt line to stderr on the interval (10 s default; --stats 0 is refused, since a zero interval prints as fast as the writer allows) — the same numbers the Prometheus endpoint serves, on the stream that already carries the log. One line prints at startup, so a --stats run names itself before the first interval elapses, and one prints on a clean exit marked final=1. A terminal, journalctl -u <unit> -f, and a redirected log file all show it, so a box with no collector still answers what the tunnel is doing. Either flag alone arms the shared 1 Hz sampler that derives bandwidth and jitter, so --stats alone reports real rates and --enable-metrics alone emits no line; --enable-metrics stays the pull interface for a scraper and the two compose. qqt up renders the same flag into the remote start line, so a rented cell's server log carries it too. Two line keys are line-only rather than /metrics series: loss, the ratio a scraper divides from the packet totals, and the final marker. The closing line is owed by every path that returns successfully — a signal on either role, a signal during the initial connect (so an operator abandoning a hung start is not read as a crash), and the server's endpoint closing — and it is ordered behind the last sampler tick. Cumulative qqt_*_total values never decrease: a departing connection is counted once, since a falling counter reads to Prometheus as a reset. Design: the tunnel crate's docs/rationale_qqt-stats.md.
  • Host KV tier (HiCache), profile-field-gated: a profile's create.hicache row (size_gib_per_rank plus the write-policy, io-backend, mem-layout knobs) is the host KV tier's single home. Every launch surface (the snipe, the CLI launcher) renders the engine flags from the profile field — never hardcoded — and a profile without the field launches with no host tier. The size is PER RANK (the box holds TP copies): the pinned pool is size × TP plus the model's DSA indexer sidecar, and the charged ceiling is the box's cgroup memory.max — read live on the box, never derived from the offer's advertised cpu_ram (the cpu_ram filter floor does not gate this charge; the captured sizing evidence lives in the catalog rows).

Must Not

  • No stop, no destroy, no terminate — no code path and no HTTP verb in any component; a failed or timed-out cell is left billing and named for the operator, who destroys by hand in the vast console.
  • No stacking: a provisioning timeout halts rather than launching a second cell against a maybe-live one.
  • No inter-step recon: no re-GET of the market between create attempts; no tool round trips anywhere in the launch path.
  • No house dependency: zero personal credentials, zero external hostnames/ports/brands, zero fleet references in anything the package carries — fleet joins (a door, a collector, gateway rows) are consumer-side and never enter the package.

Should

  • --dry-run (snipe and armed watch) runs the full coded path (filter, fit, naming, ports, body) with no key and no PUT.
  • The E2E proof profile (2x4090-test) stays cheap (~$0.80/h EU) so the mechanism can be re-proven live at will.
  • The catalog (package/catalog/) records every hardware class (hardware.yaml: provider, GPU/CPU/RAM/disk/net, price bands), every model (models.yaml: quant, checkpoint, known-good engines with images/versions/flags, traps), and every measured cell (performance.yaml: numbers only from captures, never estimated — package/catalog/README.md is the upkeep law); a profile row is the executable form of a catalog row.

The gateway cell (package/gateway/)

The kit's front door: one OpenAI-compatible endpoint (chat completions + models) over every live cell. The cell runs the digest-pinned upstream sgl-model-gateway image in IGW mode (--enable-igw): the request's model selects that model's worker, and the registry boots EMPTY — a fresh container serves nothing until the sync runs once.

  • The sync is the only writer of worker membership: sync.sh reads the fleet state, rewrites the sync-owned workers: block of workers.yaml, and converges the running gateway's registry on the file through its control API (GET/POST/DELETE /workers, bearer GATEWAY_API_KEY). No other path adds or removes a worker; a manual registry call is a divergence the next tick erases.
  • Cadence: one tick by default, --loop N every N seconds, or a systemd timer firing the one-shot (examples/). The tick IS the contract: a vanished cell's row drops within one tick, a new cell's appears the same way.
  • Row shape: workers.yaml carries the user-owned sections — console: (fleet-console endpoints whose /api/cells rows contribute a worker when they carry a url) and static: (hand rows: any OpenAI-compatible endpoint, url plus optional model pin) — and the sync-owned workers: block, one row per live cell: url, model (the served name), source (static | launch-record | console), updated. Derived rows come from the snipe's launch records (a record with a ports.quic_pair yields http://$WORKER_HOST:<pair0>; a record without one is an ssh-only cell the gateway cannot dial — named and skipped).
  • The door probe is the join proof: a row joins only when its own /v1/models answers — the model id is read from that answer, and a door that fails drops its derived row this tick (a static row is kept in the file and retried). Registry health is not serving health; the probe is what keeps a registered-but-dead row off the door.
  • The reload path is the control API, never a restart: membership changes commit as POST/DELETE calls; the gateway container is never bounced to pick up a row (a restart re-reads compose but serves an empty registry until the sync runs).
  • Sanitize law holds inside the cell: the compose file names no endpoint but the loopback door (GATEWAY_HOST/GATEWAY_PORT, default 127.0.0.1:8080), and workers.yaml ships as an example with zero rows — every real row is user state, gitignored.

The catalog CLI (package/cli/)

The read surface over the knowledge base (package/catalog/): what models exist, what hardware fits them, what was measured, which class is the best buy — and, since the launcher, the one create PUT fired from it. Its build and usage steps live in package/cli/README.md; the manual is -h's.

  • Manual-first interface: -h prints the typeset manual page (never the framework's default help), -V the version; a bare invoke or a usage error exits 2, a runtime failure exits 1, success exits 0.
  • Machine rows on every read subcommand: --json emits machine rows wherever the catalog is read (model and hardware listings, the fit verdict, the measured performance table, the ranking).
  • Model ids resolve their aliases: every command that reads a model id takes the full id or one of its aliases: short forms — one alias names one model (a duplicate claim fails the load), and catalog models <alias> answers with the canonical row. The default catalog dir resolves without --catalog: the cwd's package/catalog, else a walk up from the binary's own path (a build in package/cli/target finds its checkout's catalog; a deployed binary with no checkout above it falls through and its load error names the path to fix).
  • The fit verdict is the FIT-001 gate law: weights/card = checkpoint/TP, PASS under 70 percent of the card's VRAM, HALT over it — a HALT exits 1 so launch scripts gate on the status; a launch profile's fit.headroom_pct pins its own ceiling over that default (validated 1..=100 — anything else fails closed), and the arithmetic is the same law the snipe enforces before the create.
  • best ranks the fitting classes cheapest-first by the observed price-band low, only where the model's minimum TP is satisfiable, with the engine image, version, and flag set the model row pins.
  • Reads parse the frozen schemas exactly — field names are the catalog's, never re-derived; an unmeasured number is a null slot named as such, never an estimate. A YAML null in a required string field is a loud parse error (a join key can never fabricate the id null), and a hostile catalog is refused before the parse (nesting-depth guard).
  • The gate fails closed: a non-finite or non-positive checkpoint refuses the fit verdict outright — never a zero-weights PASS.
  • best names rejected pins with their reason — a pinned-but-filtered model is never reported as unpinned, and a NaN price band can never win the ranking.
  • advise is the thin wizard/chat over the catalog — a workflow, not a surface: it owns no data, it composes the reads the kit already has. A one-shot ask (onefrontier advise "<ask>") or a chat (no ask and no --model: one ask per stdin line, EOF or exit ends) resolves a model (a light token match over the catalog's own ids, their tails, and their aliases — an alias matches exactly), an optional class, and an optional --budget $/h ceiling, then answers with the recommended class and TP, the FIT-001 verdict, the measured rows on that shape, and the exact launch command. The verdict, the exit code, and the emitted command all describe ONE class — the one the command commits on: a named class the launcher cannot target (an off-profile class on a profile-backed model) is named with a caveat and the command commits on the profile's class, and the verdict follows it. The pick is the cheapest fitting class within budget; a pick over budget is kept and marked, never dropped. A model with no pins still gets a target where one is provable: its profile's class, or its newest measured row's class. A HALT exits 1 in the one-shot form; an unknown ask or --model is a miss (1); a chat never exits on a bad line. --json emits the recommendation, the verdict, the measured rows, and the rejected pins. A catalog gap (an unobserved class VRAM, a missing checkpoint, no pin) degrades to a spoken fit gate: cannot answer line with the answer still rendered — never a bare error.
  • launch is the instant launcher: catalog template → fit gate (FIT-001) → the one create PUT, cheapest offer first, one scan with no re-scan, no post-launch checks (verify.py takes the cell from the record the launcher writes in the snipe's own state root, under the profile's dir — a composed launch, with no profile, records under launch-<slug>). The template's fit facts (checkpoint, class, VRAM) come from the catalog; its executable shape (image, env, engine args, onstart, engine port) from the model's launch_profile row in launch/snipe-params.yaml — the file's own header names it the single home read by any launch surface, and the class joins through hardware.yaml's launch_profiles row. A model with no launch_profile composes a best-effort body from its catalog engines row: the instance (class, VRAM, disk, image) is the irreversible decision, the engine start is not — an engine restart post-launch is acceptable, a lost instance to a wrong template is not — and a composed --tp never duplicates one the measured flags already carry. The model may be named by an alias; the record the launcher writes carries the profile's published tun in its ports map (tun_port) — the client-side pair commits when the operator wires the launch host's unit, and the gateway sync joins on that pair, never on the box-side tun. --dry-run runs the full path and stops before the PUT: no key, no billing; the transcript prints the ordered sequence FIT VERDICT: → CREATE DECISION:.
  • launch's HTTP surface is the kit's own: bundles GET, the read-only instances GET (the stacking check — a live cell of the same template halts before anything is created, the armed watcher's own check), and the one create PUT; a failed or vanished create walks to the next candidate from the SAME response. The console's launch_dry_run setting gates the real path whenever the always-on config is discoverable and its DSN resolves: a set brake forces the dry path (the FORCED DRY-RUN line names it), an ABSENT row refuses the real launch — a stack the operator never configured has an unknown intent — until the setting is written explicitly, and an unreadable setting halts; the gate fails closed, and a standalone launch with no config is the operator's own authority. A publishing profile's cell carries its box-side qqt tun from the moment it exists; the launch host's client unit is the operator's wiring, and until it commits the gateway sync names and skips the cell.
  • The one-shot and always-on commands: launch (the instant launcher), watch (the daemon), serve (the console), snapshot (the capture), hook (the box-side lifecycle) — the same binary, one config (onefrontier.yaml), contracts below; the command names are frozen.
  • Standalone build: the crate carries zero house references — the umbrella's sanitize greps stay empty over package/cli/.

The baked cell image (package/cell/)

The kit's pre-built validated cell: a published container image that freezes one (model × quant × class × flags) row of the catalog into a boot-ready cell — the weights, the engine at the pinned image, and the exact measured flag set — so a launch is a pull and a run, not a 100+ GiB download plus a per-boot config step. The catalog is the single home for every fact: build.sh reads the model's launch_profile (the executable shape) and the class join, and bakes them; the image carries no fact of its own.

  • One image per validated cell: package/cell/build.sh <model> <class> resolves the profile's image, engine args, engine port, tensor parallel, and served name from ../launch/snipe-params.yaml and the class join from ../catalog/hardware.yaml, pins the engine image by digest, and builds. A model whose class does not name the profile (or whose profile serves another model) refuses the build — the join law is enforced at bake, not at run.
  • The validated state is baked, never re-applied per boot: the profile's onstart.prelude fix (the QSA SM120 gate sed, guarded exactly as the profile guards it) moves to a build RUN, so the boot is a plain exec of serve-cell.sh rendering the exact validated serve line. Every field is env-overridable (CELL_MODEL_PATH, CELL_SERVED_MODEL_NAME, CELL_ENGINE_ARGS, CELL_ENGINE_PORT), so a launch retunes without a rebuild; the engine binds 0.0.0.0 on the cell's engine port and stays unpublished — the launch contract owns the door.
  • A published ref stays sanitized: the image ref is neutral (ghcr.io/onefrontier/… by default; REGISTRY is the only knob), so the catalog row citing it carries no house host. The catalog's cell_image: field (class -> ref, additive to the frozen schema) is the machine record; best and catalog models <id> name it where a cell exists, and the CLI parses it.

The tunnel (its own repo: cd/quickquic, ~/ventures/quickquic)

The kit's transport: one small Rust binary, the same binary run as server and as client, forwarding TCP over a mutually-authenticated QUIC connection. It needs no config file and one server-side UDP port. It is a product of its own and a repository of its own; the kit consumes the binary and never owns the crate. Its full contract, manual and measured record ship there.

  • ssh -L and ssh -R parity. -l binds a listener on the client and dials the target from the server; -R asks the server to bind a listener and dials the target from the client. Both ride the one connection. Whoever receives a forwarded stream dials that stream's target — the two shapes are one frame with the roles mirrored.
  • The client certificate is the access control. The server admits only a handshake whose client chain reaches a CA in its trust bundle; there is no password, token, or anonymous mode. The holder may make the server dial anything the server can reach, and with -R bind anything the server process can bind.
  • Cert custody: qqt gencert mints a CA plus the two role sets on the operator machine; the CA key never leaves it. A client set's client-cert.pem carries the leaf and its issuing CA, so the server can evaluate the whole chain and so a CA entry on the revocation list matches what a client sends. Leaf certs carry a validity window the handshake enforces (a CA's own window is not date-checked by the verifier, so a CA is retired by removal or revocation, never by expiry).
  • Revocation is enforced against the server's own trust bundle, not the chain a client chooses to present. qqt revoke + server --revoked kill a leaked leaf at the next handshake; revoking a customer CA kills every cert it signed, including one minted afterwards, even when the client omits its CA block. A trust bundle that cannot be read refuses every connection rather than admitting with CA-level revocation undecided.
  • Reverse listeners are per-connection state. A -R bind lives exactly as long as the client's connection and is re-asked on a redial; a clean exit drops it at once, a crashed client's is reaped by the idle timeout, and the redial's in-use bind retries across that window. A bind the server cannot make is a warning, never a failed client.
  • qqt up ships, starts, and verifies the server on a remote box over ssh; the server cert set is pre-flighted before the old server stops, and the mTLS handshake against --verify is the proof the door is live. The remote start line carries --stats 10 by default, because the box has no journal. The ship verifies the transferred byte count before the rename, so a truncated transfer fails there rather than leaving a partial binary. Its ssh lane runs with -F /dev/null: no ssh_config is read, so a host alias does not resolve and only default id_* keys are offered unless --ssh-key names one.
  • The client's exit is a contract. Without --reconnect it exits 1 the moment the connection dies, and a supervisor owns the retry — the shipped unit carries --reconnect and Restart=always with a 2 s pace. --reconnect moves that retry into the client instead: -l listeners stay bound across the redial, -R binds are re-asked, and cumulative counters do not reset. Exit 0 means a clean stop (SIGTERM/SIGINT), a usage error exits 2, and a runtime failure exits 1.
  • Dial and stop are both bounded. A dial that cannot handshake in 10 s fails with an error naming the address (and retries on the same pace under --reconnect), so a wrong -r is a 10 s answer rather than a silent hang. A clean server stop closes the endpoint, which sends CONNECTION_CLOSE to every client at once; a restart is seen immediately, not after the idle timeout.
  • IPv6 is an ordinary address family. A server bound to a wildcard IPv6 address must answer clients of both families, and that reach must not depend on a host sysctl: a listen that serves IPv4 on one box and not on another is a failure. A process-wide -4/-6 request must filter every lookup, bind and dial it makes — a family filter that reaches the tunnel's dial but misses a forward listener is the failure. A name resolving to several addresses must be attempted address by address, and a refused family request must name the addresses the name did carry rather than dialing the wrong one. A link-local literal must carry and honour its zone on every surface that takes an address. The design and its OpenSSH exemplar are the crate's docs/rationale_qqt-address-family.md.
  • The stream cap is real, not nominal. One connection carries up to 1024 bidirectional streams; the shipped units raise LimitNOFILE to 65536 so the cap is reachable, and the accept loop logs the open fd count when EMFILE or ENFILE fires.
  • The CLI surface is the crate's own and complete. qqt -h prints the manual page — NAME through SEE ALSO, every subcommand, flag, and argument — and completions/_qqt completes all of it, including the <bind>:<port>:<target-host>:<target-port> grammar field by field. Both are gated against the clap tree by cargo test, so a flag cannot land without its documentation and its completion; the crate builds, runs, and completes from a bare checkout (the rule: the crate's own docs/rules_qqt-standalone.md).
  • Observability belongs to the component. The flags, cadence, refusal of --stats 0, and the two independent switches are the kit's --stats requirement above; what this component adds is that the closing line is ordered behind the last sampler tick, that cumulative qqt_*_total values never decrease (a falling counter reads to Prometheus as a reset), that every shipped and deployed unit carries --stats (or --enable-metrics where the console scrapes), and that an unreadable trust bundle or a failed metrics bind degrades loudly rather than silently.

The always-on side (package/cli/ watch/serve/snapshot/hook, package/deploy/, package/console/)

The daemon, the console it serves, and their contracts. Config is the user's onefrontier.yaml (template package/deploy/onefrontier.yaml.example): discovery --config, then $ONEFRONTIER_CONFIG, then ./onefrontier.yaml; the compose overrides the DSN through ONEFRONTIER_DSN so the credential channel is the environment. The launch filters, geo lists, and model facts stay owned by package/launch/snipe-params.yaml — the daemon records the market its query names (default: the modern-GPU classes), it never applies launch filters.

  • One daemon loop, three jobs on their own cadences: the market (keyless bundles GETs — VAST_API_KEY rides along only when the operator's account requires it; by default one query per catalog class, because the provider's single result page is capped small and cheapest-first and never reaches the pricier classes; offers become price_point rows, cheapest per hardware-slug × geo per tick, the offer id as the row's source; every offer is pinned to the class slug client-side — the server's gates are an optimization, never the filter, proven live by gtx-1060 rows surviving the server-side compute_cap gate), the fleet (each configured endpoint's /api/snapshot committed whole as a fleet_tick row, its GPU-bearing workloads derived into cell rows), the catalog (performance.yaml rows upserted as perf_sample). A tick that fails logs and skips — the daemon never dies on a transient.
  • The sniper fleet is the daemon's fourth job: proxy_fleet: in onefrontier.yaml names N HTTP CONNECT proxies (squid-style; no SOCKS shape exists in the kit); one task per proxy, each on its own interval + uniform-jitter cadence (the deadline set before the poll, so a slow proxy never stretches its cadence, and the draws never align on the same instant). Every poll is a keyless, anonymous market GET with the config's user_agent — no API key ever rides a proxy. Every observed offer of every tick commits whole as a proxy_tick row (full history, never compressed — the market job's cheapest-per-pair law is untouched), the proxy's id on every row; proxy_fleet.query overrides the broad cheapest-first default, and retention_days 0 keeps every tick forever while >0 prunes. The proxy list is user-side deployment config: it lives in the gitignored onefrontier.yaml and never enters the package.
  • GET-only against providers (the no-destroy law): bundles GET and snapshot GET are the whole external read surface; the writes anywhere on this side are the console's own (ingest, settings) — every provider-facing verb the daemon or console issues is a GET.
  • The schema is frozen (package/cli/migrations/): price_point, cell, perf_sample, fleet_tick — plus one forced deviation, cell.url, the door address a gateway sync keys on (the committed consumer contract). The streams this build appends are their own tables, added as new migrations in filename order (a schema change is a new migration, never an edit): proxy_tick (the sniper fleet's raw observations, every offer of every tick, full history) and setting (the console's persisted settings, one row per setting). Deliberately absent from perf_sample: engine, tp, price_hourly_usd — those stay in performance.yaml, where the CLI's perf reads them; the upsert key is (ts, model, hardware, source), source NOT NULL with the catalog's null→empty-string convention (a NULL source could never conflict and would re-insert per tick).
  • The hardware slug is one law: the GPU name minus vendor marketing tokens and any parenthetical, joined on -, plus the card count (b200-x4, rtx-pro-6000-x2), pinned against hardware.yaml's own slugs (exact, then longest token-prefix walk) by a test that fails when the catalog's wording drifts. An unpinnable GPU keeps its derived slug — its price history stays honest, simply unjoined.
  • Cells derive, they never editorialize: tp is the GPU count (best-effort — PD-role workers skew it), quant/engine_version are null when the source does not know them, the door url is public_url first (endpoint_url is an internal hostname a remote consumer cannot dial), and a cell absent from recent ticks is stale/gone at the API layer — derived from last_seen, never a written state.
  • The two cell-id spaces are disjoint by design: polled cells are <endpoint-id>:<workload-id>, hook-reported cells are <provider>:<instance-ref> — a polled fleet host and a self-reported rented box are different cells with no shared natural key; the timeline/samples views key on their own space.
  • The cell page renders the box's network: /api/cells/:id carries networks — the endpoint's per-interface rx_mbps/tx_mbps, pulled from the recorded fleet ticks' host_stats.networks with the source's own field names, scoped to the interfaces the newest tick carries (a churned interface drops with its container, never rendering a dead veth's last rate as live), windowed to the recent 360 ticks and bounded to the newest 3000 rows (older ticks drop whole before any current value; the oldest retained tick is itself cut wherever the bound falls). A rate the source could not window — its first collection, a vanished base — stays null end to end and renders as --, never a fabricated zero; a value no float8 can hold, or a drifted non-numeric rate, reads as absent the same way and never errors the payload (the guard is pg_input_is_valid — the stack's postgres floor is 16, and the compose pins 18); a chart spans such a gap per the console's shared charting law (a skipped tick is a stretch, not a cliff). The current table lists every live interface; the charts carry the busiest eight interfaces with measured rates (ranked over the fetched tick window) on a trailing-hour wall-clock view, the mounts persistent across the page's polls.
  • Retention is bounded: fleet_tick rows pruned past fleet.retention_days (default 30); /api/ticks re-serves stamps only, never snapshot bodies; /api/prices and every history view are windowed (?since=, LIMITs).
  • The console's reads carry no app-level auth — the bind is the boundary (a loopback publish fronted by the operator's TLS proxy).
  • The console's write paths are two: the hook ingest and the settings toggle (POST /api/settings) — both take the bearer token when one is configured, and the boot warning names them both on a tokenless non-loopback bind.
  • The templates page is the launch plane's read surface: /api/templates emits the catalog's own model and hardware rows — the same rows the CLI's catalog commands print, read per request (saving-is-publishing) — and the page's settings band carries the launch_dry_run brake. Absent = on: a stack the operator never touched sits with real launches disabled until the console flips it; the launcher reads the same row, so the toggle is the setting's single home.
  • /ws is the live scan feed: the socket receives the recent proxy_tick rows on connect, then every row the sniper fleet commits, pushed as it commits — the pump polls the id watermark inside serve, because the daemon and the console share only the database, never a process.
  • snapshot is the snapshot-pull slice of a capture, in the corpus convention: a dated dir, hcapi/<name>.json per endpoint, a README naming the runner, the host set, and every offline endpoint (skipped, never fatal; zero captures is the only failure). The full capture runbook composes its other artifacts around it.
  • hook facts ride flags, never a provider-echoed environment: a token baked into a launch onstart is visible in provider metadata, so the ingest token belongs to the operator's config. onstart emits the cell-facts JSON and POSTs when an ingest url is configured (a dry config emits only, exit 0); onready probes /health + /v1/models and fails when an asked-for served name is absent; every exit code is honest.
  • The open-source default builds from source (package/cli/Dockerfile, the compose cell in package/deploy/: db + daemon + console, the console loopback-published, the db on an internal bridge with no publish); a host-built-binary deployment overrides mounts and networking host-side — the override and the real onefrontier.yaml are gitignored and never enter the package, so the sanitize greps stay empty over package/console/ and package/deploy/ on any host.