visor

package
v1.801.150 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

board.go — GET /v1/fleet: the org's compute, from every source, on ONE board, each unit carrying its latest utilization.

The fleet is already visible, just never in one place: Visor's machines (/v1/machines), the BYO workers that dialed in (/v1/fleet/workers), the BYO clusters (/v1/clusters) and the agent run-targets (/v1/agents/targets) each answer for their own plane. This unions them behind the tenant's ONE question — "what compute do I have, and how hot is it?" — and overlays the utilization series (clients/samples) that no source used to keep.

GET /v1/fleet          the org's units + their latest sample  -> {units:[fleetUnit]}
GET /v1/fleet/samples  one unit's / the org's time series      -> {samples:[sampleView]}
GET /v1/fleet/workers  the raw BYO inventory (fleet.go)        -> unchanged

It lives in visor because visor already owns /v1/fleet/workers and the compute surface — this is that surface completed, not a rival face.

FAIL-SOFT BY SOURCE. Every source is folded in independently and a broken one contributes an empty slice, never an error: a wedged Visor, an unmounted agents subsystem or an absent warehouse each cost the board THAT source's rows and nothing else. A tenant's own dialed-in GPU must never disappear because an upstream they do not use is down. The board 500s for exactly one reason: no validated tenant.

ISOLATION: principal.Org is the ONLY tenant key, taken from the validated IAM claim (never a client field) and passed to each source's own org-scoped read.

bots.go mounts the bot-MACHINE surface (/v1/compute/bots) plus the machine agent-binding proxies (/v1/machines/:id/{bind-agent,agent-binding}, /v1/agent-bindings). It is the SIBLING of machines: a bot machine is not a new state this subsystem owns, it is a composition of two things vm already owns — a kind=bot Machine and an AgentBinding. So every route here is a thin, org-scoped translation over the SAME Visor client the machines routes use (client.go), never a second store.

The noun is the MACHINE that hosts a bot runtime — distinct from the bot RUN at /v1/bots (clients/bots), which is a task the runtime executes. Two values, two namespaces: this one nests under /v1/compute (visor's domain) because what it rents you is compute.

A bot machine = Agent (cloud /v1/agents) + Machine (vm, kind=bot) + the binding between them. Composition, one way per verb:

launch  = vm POST /v1/machines/launch {kind:bot}  THEN vm POST .../bind-agent
list    = vm GET  /v1/machines?kind=bot           joined with the org's bindings
get     = vm GET  /v1/machines/:id                joined with its binding
delete  = vm DELETE .../agent-binding (unbind)    THEN vm DELETE /v1/machines/:id
message = the AGENT path: run the bot's bound agent via /v1/agents/:agent/run
stop    = vm DELETE .../agent-binding — halt the bot's @hanzo/bot runtime
pause   = the same halt: DigitalOcean/vm expose no VM-suspend primitive, so a
          bot's stop and pause are one honest capability (detach the agent
          runtime); powering the underlying machine off/on is a machine-lifecycle
          concern handled by launch/delete, not a fabricated bot state.

Tenancy is identical to machines: the org is the VALIDATED principal (principal.Org, taken from the IAM owner claim), forwarded to vm as ?owner=<org>, so a caller can only ever read or mutate its OWN bots. No validated principal ⇒ 403, before anything reaches vm.

client.go is the ONE HTTP path from this subsystem to Visor (the cloud OS at visor.hanzo.svc:19000 that owns compute — machines and DOKS node pools). Every handler in visor.go routes through this client, so the wire contract (base URL, auth, the casibase {status,msg,data} envelope, error mapping) lives once here and can never drift between six hand-rolled fetches.

AUTH (one rule): a request carries a Visor identity that is EITHER the service credential (VISOR_CLIENT_ID + VISOR_CLIENT_SECRET, KMS-sourced, sent as Basic auth so Visor's ApiFilter authorizes cloud as the `app/<visorApp>` subject) OR the caller's forwarded Authorization bearer when no service credential is configured. The tenant is ALWAYS pinned by ?owner=<org> (the validated principal's org, never a client field) plus the forwarded identity headers, so Visor scopes to exactly the caller's tenant on both paths.

ENVELOPE: Visor (casibase) returns HTTP 200 with {status:"ok"|"error", msg, data}. A logical failure is status:"error" at HTTP 200, NOT a 4xx/5xx — so a bare status-code check would read an error as success. call() inspects the status field and surfaces msg as an honest error; it never fabricates data.

fleet.go — BYO ("bring your own") compute: the operator's OWN machines that dialed IN via `hanzo gpu connect`, as opposed to the DOKS/DigitalOcean machines Visor provisions. A BYO worker is an outbound agent behind NAT: it can't be listed by Visor (Visor never provisioned it), so its presence lives as a heartbeating standalone activity in the org's `fleet` namespace of the ONE in-process tasks engine (cloud.EmbeddedTasks). This file reads that registry and folds it into the SAME machineView / gpuView the console already renders, tagged provider="byo", so the existing Machines and GPUs pages light up for free — no parallel UI. It also serves the raw list at GET /v1/fleet/workers.

Registration is written by the CLI over the public tasks surface (POST /v1/tasks/namespaces/fleet/activities + heartbeat) — this subsystem only READS, and only ever the caller's own tenant (principal.Org → org shard).

k8s.go is the UNIFIED /v1/k8s surface — the ONE Kubernetes noun on api.hanzo.ai: list clusters, one cluster's detail (node pools + worker nodes), DEPLOY (create) and delete DOKS clusters, and the fleet-wide worker NODES. Every route is a thin, tenant-scoped proxy to Visor (which OWNS the DigitalOcean lifecycle); this client fabricates nothing — a cluster row is a real DOKS cluster, its nodes are real droplets, and an honestly-absent field is omitted, never invented.

Surface (org taken verbatim from the validated IAM owner claim, never a client field, so a caller only ever sees or mutates its OWN tenant's clusters):

GET    /v1/k8s/clusters        list the org's DOKS clusters (+ BYO fold-in) -> {clusters:[clusterView]}
GET    /v1/k8s/clusters/:id    one cluster's detail: pools + worker nodes   -> clusterDetailView (404 if absent)
POST   /v1/k8s/clusters        provision a DOKS cluster    (ADMIN-GATED)     -> clusterView (201)
DELETE /v1/k8s/clusters/:id    destroy a DOKS cluster      (ADMIN-GATED)     -> 204
GET    /v1/k8s/nodes           every DOKS worker node as a machine          -> {nodes:[machineView]}

READS are org-scoped (any validated member of the org). MUTATIONS (create/delete) are admin-gated — a SuperAdmin (platform sudo) OR an OrgAdmin of the owning org — because provisioning spends real infrastructure on Hanzo's house account. The gate is the SAME principal predicate the rest of the cloud mutating surface uses.

types.go holds the Visor wire structs (what upstream returns) and the console view structs (what this subsystem emits), plus the PURE mapping between them. The view JSON keys mirror the console normalizers EXACTLY so the Machines, GPUs and Clusters pages render with no front-end change:

  • machineView -> console src/lib/api/visor.ts normalizeMachine
  • gpuView -> console src/lib/api/compute.ts normalizeGpu
  • clusterView -> console src/lib/api/platform.ts Cluster + NodePool

Every field is a REAL Visor value or an honest omission. Telemetry Visor does not carry (GPU utilization/temperature/power) is left off the gpuView so the UI shows "—", never a fabricated 0.

Package visor mounts the Hanzo Cloud COMPUTE surface: the tenant's machines, GPUs and DOKS clusters, served as clean REST off the unified cloud binary and fronting Visor (the cloud OS at visor.hanzo.svc that OWNS compute). It exists so the console's Machines / GPUs / Clusters pages read real per-org compute from ONE place (api.hanzo.ai/v1/*) instead of the god-mode /paas admin proxy that 501s until a service token is wired.

This subsystem OWNS no compute state — Visor does. It is a thin, tenant-scoped translator: it maps Visor's verb-style + resell endpoints to the clean REST the console already speaks, and re-shapes Visor's objects into the exact JSON the console normalizers consume (see types.go). It never fabricates: a GPU row is a real GPU machine's accelerator, a cluster is real node pools, and telemetry Visor does not carry is honestly omitted (renders "—"), not invented.

Surface (every route org-scoped by the validated principal; HIP-0026):

GET    /v1/machines                          list the org's machines        -> {machines:[machineView]}
POST   /v1/machines                          launch (or dryRun quote)       -> machineView | quote
GET    /v1/machines/:id                       one machine by name            -> machineView (404 if absent)
DELETE /v1/machines/:id                       terminate a machine            -> 204
GET    /v1/gpus                              per-accelerator inventory      -> {gpus:[gpuView]}
GET    /v1/gpus/alerts                       GPU alerts (honest empty)      -> {alerts:[]}
GET    /v1/clusters                          DOKS clusters (from pools)     -> {clusters:[clusterView]}
POST   /v1/clusters/:clusterId/pools          add a node pool                -> nodePoolView
POST   /v1/clusters/:clusterId/pools/:poolId/scale  scale a node pool        -> nodePoolView
DELETE /v1/clusters/:clusterId/pools/:poolId   delete a node pool             -> 204
POST   /v1/machines/:id/bind-agent            bind a cloud Agent to a machine -> agentBinding
GET    /v1/machines/:id/agent-binding          the machine's agent binding    -> agentBinding (404 if none)
DELETE /v1/machines/:id/agent-binding          unbind the agent               -> 204
GET    /v1/agent-bindings                     the org's agent bindings        -> {agentBindings:[agentBinding]}
GET    /v1/compute/bots                       the org's bot machines (kind=bot) -> {bots:[botView]}
POST   /v1/compute/bots/launch                launch a bot machine (machine+bind) -> botView | quote
GET    /v1/compute/bots/:id                    one bot machine by id          -> botView (404 if not a bot)
DELETE /v1/compute/bots/:id                    terminate a bot machine        -> 204
POST   /v1/compute/bots/:id/:action           stop|pause|message the bot      -> action result

The tenant (principal.Org) is passed to Visor as ?owner=<org>, so a caller can only ever read or mutate their OWN tenant's compute; the org is taken from the validated IAM owner claim, never a client field.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app *zip.App, deps cloud.Deps) error

Mount wires the compute surface onto app per HIP-0106. visor is a "complex" mount (it keeps a "compute"-provider meter and a fleet-scoped sub-logger that both need deps at construction), so it builds the Service value directly.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL