Files
zeekayandHanzo Dev 555c41b82e A namespace naming a database, and that name becoming a location
Naming the database and rendering that name as a place were one primitive
living in two repositories: hanzoai/cloud held the slugger, the layout and
the resolver, and this package held only the value they produced. A fact with
two homes is a fact that can disagree with itself, and these particular strings
are directory names on live volumes and keys in live buckets — a disagreement
does not fail, it opens an empty database beside a real one.

So the whole primitive moves here and cloud imports it:

  Sanitize     one injective slug for a name a person chose
  OrgProject   the door those names walk through to become a namespace
  MustOrgProject
  Key          a namespace rendered as the place one subsystem's file lives
  Path         Key resolved against a data directory

Sanitize refuses a name carrying an invisible rune (nothing downstream can be
trusted not to trim it) and disambiguates every other fold with a hash of the
raw bytes, so two spellings are two files. OrgProject is named for what it
takes; Of already means something else here — the namespace a billing subject
owns — and clobbering it would have been the same collapse in miniature.

Every golden value in the tests was captured from the implementation that
wrote the files already on disk, before the code moved, so the rendering is
byte-identical and no live data is orphaned.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 18:14:05 -07:00

74 lines
2.7 KiB
Go

// Copyright (C) 2026, Hanzo AI. All rights reserved.
// See the file LICENSE for licensing terms.
package namespace
import (
"fmt"
"path"
"path/filepath"
)
// orgsRoot is the single directory every namespace's files live under, and
// platformSlug is how the system namespace renders inside it — a leading "_",
// which Sanitize never emits, so the deployment's own partition cannot collide
// with an entity's however the slugger changes.
//
// Both are spelled once, here, because these strings are on disk and in object
// storage: a second spelling is a second answer to "where is this store", and
// the files that already exist only have one place.
const (
orgsRoot = "orgs"
platformSlug = "_platform"
)
// Key renders a namespace as the place one subsystem's file for it lives:
// relative to a data directory on disk, and byte-identically the object key
// durability ships it to. One rendering, two consumers, so the local file and
// the remote slot are the same name by construction.
//
// org/{id} → orgs/{id}/{subsystem}.db
// org/{id}/{group} → orgs/{id}/projects/{group}/{subsystem}.db
// system → orgs/_platform/{subsystem}.db
//
// The subsystem is a separate argument rather than part of the namespace
// because one store is one subsystem: which subsystem is a property of the
// registry that opens the file, not of the entity the file belongs to.
//
// Every other kind is an error. User and repo namespaces are real namespaces,
// but this layout has no place for them, and inventing one silently is how a
// second convention starts.
func Key(ns Namespace, subsystem string) (string, error) {
if subsystem == "" {
return "", fmt.Errorf("namespace: empty subsystem")
}
switch ns.Kind() {
case KindSystem:
return path.Join(orgsRoot, platformSlug, subsystem+".db"), nil
case KindOrg:
if g := ns.Group().String(); g != "" {
return path.Join(orgsRoot, ns.ID(), "projects", g, subsystem+".db"), nil
}
return path.Join(orgsRoot, ns.ID(), subsystem+".db"), nil
case "":
return "", fmt.Errorf("namespace: the zero namespace names no database")
default:
return "", fmt.Errorf("namespace: no database layout for a %s namespace", ns.Kind())
}
}
// Path is Key resolved against a deployment's data directory, in the host's own
// separator. An empty dir is an error rather than a relative path, because a
// store that opened relative to whatever the working directory happened to be
// would be a different file per invocation.
func Path(dir string, ns Namespace, subsystem string) (string, error) {
if dir == "" {
return "", fmt.Errorf("namespace: empty dir")
}
key, err := Key(ns, subsystem)
if err != nil {
return "", err
}
return filepath.Join(dir, filepath.FromSlash(key)), nil
}