Files
compute/service/playground.go
zeekay fc6fc9ff73 service: register launched bots into the org playground node registry
A launched fleet bot booted @hanzo/bot and connected to the gateway but never
appeared in playground.hanzo.ai/v1/nodes: nothing stamped it with an org and
nothing registered it into the node registry. The @hanzo/bot runtime that could
self-register lives in the external hanzobot/core image, so visor could not rely
on it.

visor is the org-attribution authority (it resolves the org from the fleet
launch), so it now performs the registration itself, additively next to
registerBotUser in LaunchOrgMachine:

  - registerPlaygroundNode POSTs the node to <playground>/v1/nodes/register with
    the org in the body, so the bot appears in its org space the moment the
    machine is created — independent of the external runtime. Best-effort and
    idempotent (the registry upserts by node id); honest status (registered as
    unknown/starting, the runtime promotes to active on heartbeat).
  - buildBotUserData now injects HANZO_ORG into the cloud-init env so the runtime
    knows its authoritative org for its own gateway connection and heartbeats.

Endpoint resolves from PLAYGROUND_URL, then app config playgroundEndpoint, then
the production default. Unit tests cover the org injection and the registration
body/no-op/endpoint resolution.
2026-07-02 17:49:02 -07:00

90 lines
3.2 KiB
Go

// Copyright 2024 Hanzo Industries Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package service
import (
"bytes"
"encoding/json"
"net/http"
"os"
"strings"
"time"
"github.com/hanzoai/visor/conf"
)
// playground.go — a launched bot IS a playground node. registerPlaygroundNode
// plants each launched bot in the Hanzo playground node registry
// (<playground>/v1/nodes) at launch so it appears there attributed to its org —
// the same registry the bot's own @hanzo/bot runtime heartbeats into once up.
//
// visor is the org-attribution AUTHORITY: it resolved the org from the fleet
// launch (server-injected, never trusted from the client body), so it is visor
// — not the external @hanzo/bot runtime — that stamps the node's org. The
// runtime keeps the node live via heartbeats; visor registers it, already
// scoped to the right org, the moment the machine is created. This is why a bot
// appears in its org's space immediately, independent of what token (if any)
// the runtime later presents to the gateway.
//
// Best-effort by design (mirrors registerBotUser): a registration failure never
// fails the launch — the machine + bot still run, and the node reconciles when
// the runtime heartbeats. Idempotent: the playground upserts by node id, so a
// re-launch of a same-named fleet member no-ops on the existing node.
//
// Honest status: it does NOT assert health — the node is registered with the
// registry's default (unknown / starting). The @hanzo/bot runtime promotes it
// to active once it actually heartbeats, so the registry never claims a bot is
// healthy before it is.
func registerPlaygroundNode(org, name string) {
if org == "" || name == "" {
return
}
body, err := json.Marshal(map[string]any{
"id": name,
"org_id": org,
"deployment_type": "long_running",
})
if err != nil {
return
}
req, err := http.NewRequest(http.MethodPost, playgroundEndpoint()+"/v1/nodes/register", bytes.NewReader(body))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return
}
_ = resp.Body.Close()
}
// playgroundEndpoint is the base URL of the Hanzo playground node registry. Env
// (PLAYGROUND_URL) wins for local/dev overrides, then the app config
// (playgroundEndpoint), then the production default. The trailing slash is
// trimmed so the "/v1/nodes/register" join is always exact.
func playgroundEndpoint() string {
e := strings.TrimSpace(os.Getenv("PLAYGROUND_URL"))
if e == "" {
e = strings.TrimSpace(conf.GetConfigString("playgroundEndpoint"))
}
if e == "" {
e = "https://playground.hanzo.ai"
}
return strings.TrimRight(e, "/")
}