Files
compute/object/tenant_context.go
zeekayandHanzo Dev b3323bfdf7 say what the code does now, not what it was ripped from
Four comments described a service that no longer exists, and two of them were
simply false: telemetry claimed to import "beego's logger" when it imports
visor's logs (log/slog), and tenant_context described writing to "the beego
request context" when it writes zip.Ctx locals. A comment that misdescribes the
code is worse than none — it is the version a reader trusts.

logs keeps the rationale and drops the archaeology: the signature is a format
string first because the framework logger it replaced took interface{} and
reached for Sprintf only when arguments followed, so logs.Info("failed: ", err)
lost the error into %!(EXTRA …) at runtime, in the log, where nobody was
looking. That is why the shape is a compile-time fact now. The rest of the
history is in git.

tenant_context gains the thing worth knowing: org and project OVERLAP
zip.CallerOf, which reads the same X-Org-Id/X-Project-Id off the same request.
What zip does not carry is app/tenant/actor/env, the whitelabel-hostname org
fallback and tenant-defaults-to-org — so this stays, and an op that needs only
org should ask zip rather than grow a second accessor here.

LLM.md records the state: one framework and the list that keeps it; ONE ORM
(hanzoai/xorm is indirect through hanzoai/orm, and the `xorm:` struct tags are
the tag hanzoai/orm itself reads — renaming them breaks the schema); the plane
and its asynchronous bind; and, plainly, the 72 routes still untyped and why
converting them is a two-repo change against the casibase envelope rather than
a rewrite anyone can do alone.

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

96 lines
3.9 KiB
Go

// Copyright 2025 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.
// tenant_context.go is the ONE source of truth for the tenant-hierarchy request
// scope (org > app > project, plus tenant/actor/env). The router filter
// (routers.TenantContextFilter) copies the gateway-injected X-*-ID headers onto
// the zip request context (Ctx locals) under these keys; every consumer
// (controllers) reads them back through the getters here. Keeping the keys and
// their accessors in one shared place — imported by both routers and controllers
// — means no consumer re-derives a key string: the scope is a value, not a place.
//
// Org and project OVERLAP zip.CallerOf, deliberately and not by accident: zip
// reads the same X-Org-Id / X-Project-Id off the same request, so a typed op can
// have the caller's org without any of this. What zip does NOT carry is the rest
// of visor's hierarchy — app, tenant, actor, env — nor the two rules underneath
// it: org falls back to the whitelabel hostname's filter, and tenant defaults to
// org. Those are visor's, so this stays until an op needs only org, and then it
// should ask zip rather than grow a second accessor here.
package object
import (
"strings"
"github.com/zap-proto/zip"
)
// Tenant-scope keys under which the filter stows each header on the request
// context (zip.Ctx locals). Exported so the single writer (the routers filter)
// and every reader (controllers) share the exact same key — never a duplicated
// literal.
const (
TenantContextOrgIDKey = "tenant.orgId"
TenantContextAppIDKey = "tenant.appId"
TenantContextProjectIDKey = "tenant.projectId"
TenantContextTenantIDKey = "tenant.tenantId"
TenantContextActorIDKey = "tenant.actorId"
TenantContextEnvKey = "tenant.env"
)
// SetTenantContextValue stows a non-empty scope value on the request context
// locals. Empty values are skipped so an absent header leaves the key unset and
// every getter falls through to "" — the caller decides whether a missing scope
// is fatal (org) or optional (app/project).
func SetTenantContextValue(c *zip.Ctx, key, value string) {
if c == nil || value == "" {
return
}
c.Locals(key, value)
}
// GetTenantContextValue reads a scope value back, trimmed; "" when unset or when
// c is nil. This is the ONE read-back; the typed getters below are thin adapters.
func GetTenantContextValue(c *zip.Ctx, key string) string {
if c == nil {
return ""
}
text, _ := c.Locals(key).(string)
return strings.TrimSpace(text)
}
// GetTenantOrgID returns the request's owning org scope (or "").
func GetTenantOrgID(c *zip.Ctx) string {
return GetTenantContextValue(c, TenantContextOrgIDKey)
}
// GetTenantAppID returns the request's optional app scope beneath org (or "").
func GetTenantAppID(c *zip.Ctx) string {
return GetTenantContextValue(c, TenantContextAppIDKey)
}
// GetTenantProjectID returns the request's optional project scope beneath app (or "").
func GetTenantProjectID(c *zip.Ctx) string {
return GetTenantContextValue(c, TenantContextProjectIDKey)
}
// GetTenantActorID returns the request's acting principal scope (or "").
func GetTenantActorID(c *zip.Ctx) string {
return GetTenantContextValue(c, TenantContextActorIDKey)
}
// GetTenantEnv returns the request's environment scope (or "").
func GetTenantEnv(c *zip.Ctx) string {
return GetTenantContextValue(c, TenantContextEnvKey)
}