mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
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]>
96 lines
3.9 KiB
Go
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)
|
|
}
|