Files
compute/k8s/deployment.yaml
zeekayandHanzo Dev bc02f7fe26 visor answers to its own name, and a probe that can fail
Two things visor could not do. It bound no socket, so a sibling reaching it
through zip.SocketPath got ErrNoPeer and cloud kept talking HTTP to
visor.hanzo.svc:19000 — there was nothing on the plane to convert to. And it
declared no typed op, so it was in no registry: no OpenAPI schema, no MCP tool,
no CLI verb, nothing zip.Here could call.

--zap now defaults to zip.SocketPath(visor.Name), so the canonical socket comes
up beside the HTTP edge and zip.Serving("visor") resolves. Name is stated once,
in pkg/visor, and is both the app name and the socket's — a second spelling of
a name is a peer nobody can find. --zap="" opts out.

/v1/health is the first typed op, and it fixes a probe that could not fail. The
liveness and readiness probes requested /api/health, which is not a route:
outside /v1/ it fell to TransparentStatic, which serves index.html as a 200. So
both probes measured the static file server, and a pod with its database face
down stayed in the Service. /v1/health answers 503 when object.Ready() cannot
reach the store — Ready lives in object because `store` is unexported there and
anywhere else would be a second copy of the backend-selection rule. It pings the
shared engine, not every org's: a check whose cost grows with the customer list
is an outage waiting for a big enough customer list.

Health registers AHEAD of the filter chain, and the position is the design.
ApiFilter would judge a credential-less probe as "anonymous" and authz.IsAllowed
panics on a nil Enforcer — so an authz mistake would read to kubelet as every
pod being sick. RecordMessage is the other half: an audit row per request, twice
every ten seconds, describing nothing. The routers test proves the bypass with a
control case, because an assertion about a bypass is only meaningful against a
route that is still gated.

The contract test is retargeted rather than retired. It was written as the
migration's acceptance criterion; the property it actually pins outlives the
migration — what visor serves equals what visor says it serves — so it now reads
that way and covers both registration functions. 73 routes, GET 32.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 23:38:21 -07:00

68 lines
1.5 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: visor
namespace: hanzo
labels:
app: visor
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: visor
template:
metadata:
labels:
app: visor
spec:
serviceAccountName: visor
imagePullSecrets:
- name: ghcr-secret
containers:
- name: visor
image: ghcr.io/hanzoai/visor:latest
ports:
- containerPort: 19000
protocol: TCP
env:
- name: RUNNING_IN_DOCKER
value: "true"
envFrom:
- secretRef:
name: visor-env
optional: true
volumeMounts:
- name: config
mountPath: /conf/app.conf
subPath: app.conf
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
livenessProbe:
httpGet:
path: /v1/health
port: 19000
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /v1/health
port: 19000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
volumes:
- name: config
secret:
secretName: visor-config