7 Commits
Author SHA1 Message Date
zeekayandHanzo Dev cc40119d0a Name the reusable at the path v1 has
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 00:34:32 -07:00
zeekayandHanzo Dev c50322594b Move the workflows to .github/workflows
The forge reads .github/workflows natively and github.com reads nothing else,
so one directory serves both planes instead of two conventions that disagree
about which one is live. The reusable in hanzoai/ci already lives there, so a
caller naming that path now resolves to a file that exists.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 18:49:40 -07:00
zeekayandHanzo Dev 5f46efd6e2 ci: canonical pair — hanzo.yml + forge caller
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 02:38:03 -07:00
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
zeekayandHanzo Dev f16f998889 Add Of: the namespace a billing subject owns
An account's subject (hanzoai/account Account.Subject) already names "org or
user" — a bare slug for an org, "<org>/<name>" for a person or project — and it
is what the ledger keys on. Deriving the namespace from that one string means
the wallet and the database cannot disagree: a request that bills hanzo/alice
reads and writes hanzo/alice's data by construction.

The alternative is a table mapping each account kind to a namespace kind, which
is one more place for the two vocabularies to drift.

The org half is kept via the existing group, so acme/alice and globex/alice are
different namespaces — and therefore different databases — and a person's
namespace differs from their org's, which is what stops every personal account
in the signup org from sharing one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 11:21:30 -07:00
zeekayandHanzo Dev f9fcf0e65d Carry the namespace on the context
A namespace names the entity a piece of work acts for, so it has to reach the
store that opens a database or builds a key. Threading it through every call
between the handler and the store means one missed parameter reads another
entity's data.

Every service needs this, so the carrier belongs here. A service-local copy is
how two services end up disagreeing about what an unset namespace means: the
zero value is not carried, an unset context reports ok=false rather than
standing for everything, and spanning entities is the explicit system kind.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 10:42:56 -07:00
zeekayandHanzo Dev 1a3cc15421 namespace: a value that names one entity database
The fleet stores OLTP data as one SQLite file per entity, and the name of
that file is a path -- <dir>/<ns>.db locally, a key prefix in object
storage. orm/db canonicalised it with path.Clean plus "must begin with a
letter", which leaves a namespace an opaque string that every consumer
re-derives structure from and re-validates: orm/replicated re-split and
re-checked each segment, commerce/db has splitTenant.

So make it a value. Namespace wraps an unexported string, the only routes
to one are the constructors and Parse, and the shape is kind[/id][/group].
Segments are [a-z0-9][a-z0-9_-]*, which leaves no spelling of a separator,
a dot-segment or a hidden name to reject later, and ids fold to lowercase
because on a case-insensitive filesystem org/Acme and org/acme are two
namespaces over one file.

This package is what a namespace IS; orm/db.Namespaces resolves one to a
handle.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 09:39:30 -07:00