apps: the 131 subsystems move from clients/ to apps/
They were never clients. A "client" is something that CALLS a service; these
are the subsystems the cloud binary mounts and serves. The name said the
opposite of what the code does, and it sat one directory above a composition
root already named `apps` — so the tree read as though `apps/` and `clients/`
were different kinds of thing when one is simply the wiring for the other.
`apps` is a package, so its subsystems nest under it directly:
apps/apps.go package apps — Wire() returns the 118 MountSpecs
apps/git/ package git
apps/projects/ package projects
`apps.Wire()` composing `apps/git` and `apps/projects` needs no second noun.
WHAT DID NOT MOVE. `clients/*.go` (package clients — aihttp, rpc, s3vfs) is
genuinely a client package: HTTP/RPC/VFS handles the subsystems dial OUT with.
It keeps the name, because for those six files the name was always right. Only
the 131 misfiled subdirectories moved, and the import rewrite is scoped to
`hanzoai/cloud/clients/<x>` so the surviving package is untouched.
This is a MOVE, not a rewrite, and deliberately so. mk/plugin.mk already
derives every path from its own location precisely so that "an extracted
apps/<app> + cmd/<app> + mk/ keeps these paths intact" — the build contract was
written for this migration before the directory was renamed to match it. 31
hanzoai/* modules are already extracted and wired as external imports, 16 of
them with thin in-repo adapters that import their own module (verified: zero
duplicates, no forked implementations). This rename puts the remaining 115 in
the directory the extraction contract already names.
Mechanical: git mv per subdir, then `hanzoai/cloud/clients/` ->
`hanzoai/cloud/apps/` across 656 Go files, plus 23 docs/Makefiles/manifests
rewritten only where the path names a real moved app. Builds clean; projects,
git and catalog tests pass.
(cmd/admission and cmd/affiliates fail to LINK here, before and after: they
need native/flags/target/release/libhanzo_flags.a, a Rust artifact never built
in this checkout. Pre-existing and unrelated.)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
+16
-16
@@ -14,7 +14,7 @@ name: CI/CD
|
||||
# .hanzo/workflows/deploy.yml → deleted, not moved (below)
|
||||
#
|
||||
# CI gates. It does not deploy, and deliberately builds no cloud image: that
|
||||
# image and its v* tags have ONE owner, clients/platform/release.go (POST
|
||||
# image and its v* tags have ONE owner, apps/platform/release.go (POST
|
||||
# /v1/runner {release:true}) — a second builder on the same commit is the
|
||||
# double-build hanzo.yml's images: block already refuses for this reason.
|
||||
# deploy.yml claimed to do both and could do neither: it shelled
|
||||
@@ -53,18 +53,18 @@ jobs:
|
||||
tests: ${{ github.ref_type != 'tag' }}
|
||||
secrets: inherit
|
||||
|
||||
# Guards the Stage-1 byzantine ceremony containment (clients/controlplane,
|
||||
# Guards the Stage-1 byzantine ceremony containment (apps/controlplane,
|
||||
# build tag `controlplane`). Its increment-1 crypto is stub/forgeable BY
|
||||
# DESIGN (SHA256-of-public-inputs commitments, symmetric-HMAC
|
||||
# proof-of-possession, seed-derived threshold shares — see
|
||||
# clients/controlplane/doc.go) and MUST NEVER reach a release/serve binary.
|
||||
# apps/controlplane/doc.go) and MUST NEVER reach a release/serve binary.
|
||||
# Three independent checks; any one failing blocks the merge:
|
||||
#
|
||||
# 1. grep (tag) — no build/release invocation anywhere in the repo
|
||||
# (Dockerfile, Makefile, shell scripts, any workflow) may pass a `-tags`
|
||||
# value containing `controlplane` to go build/vet/run/install. The
|
||||
# package's own `//go:build controlplane` tag declarations
|
||||
# (clients/controlplane/*) are the thing being guarded, not a violation,
|
||||
# (apps/controlplane/*) are the thing being guarded, not a violation,
|
||||
# and are excluded by path.
|
||||
# 2. grep (spoof) — no build/release invocation may pass
|
||||
# `-X testing.testBinary=1` (or any -ldflags containing it) to a REAL
|
||||
@@ -73,13 +73,13 @@ jobs:
|
||||
# runtime guard in containment.go trusts that signal, so this is the one
|
||||
# concrete way to spoof it in a non-test binary. This grep is what turns
|
||||
# "someone could type this" into "CI fails the PR that types it".
|
||||
# 3. graph — no cmd/ main may reach clients/controlplane through its
|
||||
# 3. graph — no cmd/ main may reach apps/controlplane through its
|
||||
# untagged import graph (`go list -deps`), and
|
||||
# `go build ./clients/controlplane/...` with no tag must match zero
|
||||
# `go build ./apps/controlplane/...` with no tag must match zero
|
||||
# buildable packages (proves the tag still gates every file in it).
|
||||
#
|
||||
# Runtime belt-and-suspenders (defense in depth, not a substitute for the
|
||||
# above): clients/controlplane/containment.go fail-closed panics if its stub
|
||||
# above): apps/controlplane/containment.go fail-closed panics if its stub
|
||||
# crypto is ever constructed outside a go-test binary (testing.Testing()==
|
||||
# false) — see TestContainment_NonHarnessProcessRefuses in
|
||||
# containment_test.go. KNOWN RESIDUAL: testing.Testing() is a linker-set
|
||||
@@ -118,9 +118,9 @@ jobs:
|
||||
--exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.claude --exclude-dir=vendor \
|
||||
. 2>/dev/null \
|
||||
| grep -v '\.git/' \
|
||||
| grep -v 'clients/controlplane/' \
|
||||
| grep -v 'apps/controlplane/' \
|
||||
| grep -v '.hanzo/workflows/cicd.yml:'; then
|
||||
echo "::error::found a build/release invocation passing -tags controlplane — clients/controlplane's stub crypto must never enter a release/serve binary (see clients/controlplane/doc.go)"
|
||||
echo "::error::found a build/release invocation passing -tags controlplane — apps/controlplane's stub crypto must never enter a release/serve binary (see apps/controlplane/doc.go)"
|
||||
hits=1
|
||||
fi
|
||||
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
fi
|
||||
echo "OK: zen ${V} is at or above the streaming-fix floor ${FLOOR}"
|
||||
|
||||
- name: positive proof — clients/controlplane is unreachable from the default build
|
||||
- name: positive proof — apps/controlplane is unreachable from the default build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
# graph. This step used to lead with `go build ./...`, which compiles
|
||||
# AND LINKS every cmd/ main — and linking needs
|
||||
# native/flags/target/release/libhanzo_flags.a, the Rust staticlib
|
||||
# clients/flags pulls in under cgo (clients/flags/engine.go). Nothing
|
||||
# apps/flags pulls in under cgo (apps/flags/engine.go). Nothing
|
||||
# in THIS job builds it: `make native` runs in the gate job, in that
|
||||
# job's own workspace. It passed on GitHub only because the arc
|
||||
# runners reuse a workspace and target/ is gitignored, so an earlier
|
||||
@@ -201,17 +201,17 @@ jobs:
|
||||
# proof anyway — `go list -deps` reads the same untagged file set
|
||||
# without linking, and the gate job is the ONE place that builds.
|
||||
for m in $(go list ./cmd/...); do
|
||||
if go list -deps "$m" | grep -qx 'github.com/hanzoai/cloud/clients/controlplane'; then
|
||||
echo "::error::$m links clients/controlplane into a real binary — containment breach"
|
||||
if go list -deps "$m" | grep -qx 'github.com/hanzoai/cloud/apps/controlplane'; then
|
||||
echo "::error::$m links apps/controlplane into a real binary — containment breach"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
out="$(go build ./clients/controlplane/... 2>&1 || true)"
|
||||
out="$(go build ./apps/controlplane/... 2>&1 || true)"
|
||||
if ! printf '%s' "$out" | grep -q 'matched no packages'; then
|
||||
echo "::error::clients/controlplane built successfully WITHOUT -tags controlplane (containment breach): $out"
|
||||
echo "::error::apps/controlplane built successfully WITHOUT -tags controlplane (containment breach): $out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK: containment holds — clients/controlplane has zero buildable files by default and is linked into no cmd/ binary"
|
||||
echo "OK: containment holds — apps/controlplane has zero buildable files by default and is linked into no cmd/ binary"
|
||||
|
||||
|
||||
@@ -139,20 +139,20 @@ Metering+gating coverage (each meters its OWN org, debits on success):
|
||||
|
||||
| Product | provider label | fee knob | code |
|
||||
|---|---|---|---|
|
||||
| functions | `functions` | `CLOUD_FUNCTIONS_FEE_CENTS` | `clients/functions/invoke.go` |
|
||||
| functions | `functions` | `CLOUD_FUNCTIONS_FEE_CENTS` | `apps/functions/invoke.go` |
|
||||
| s3 | `s3` | `S3_*` | `clients/s3/s3.go` |
|
||||
| agents | `agent` | `CLOUD_AGENT_FEE_CENTS` | `clients/agents/agents.go` |
|
||||
| compute / GPU | `compute` | provision knobs | `clients/ml/ml.go`, `clients/visor/*` |
|
||||
| provisioning (sql/kv/vector/docdb) | `provisioning` | `CLOUD_PROVISION_FEE_CENTS[_KIND]` | `clients/provisioning/*` |
|
||||
| automations | `automations` | `CLOUD_AUTOMATIONS_FEE_CENTS` | `clients/automations/automations.go` |
|
||||
| tracker | `tracker` | fee knob | `clients/tracker/tracker.go` |
|
||||
| security | `security.scan` | — | `clients/security/security.go` |
|
||||
| agents | `agent` | `CLOUD_AGENT_FEE_CENTS` | `apps/agents/agents.go` |
|
||||
| compute / GPU | `compute` | provision knobs | `apps/ml/ml.go`, `apps/visor/*` |
|
||||
| provisioning (sql/kv/vector/docdb) | `provisioning` | `CLOUD_PROVISION_FEE_CENTS[_KIND]` | `apps/provisioning/*` |
|
||||
| automations | `automations` | `CLOUD_AUTOMATIONS_FEE_CENTS` | `apps/automations/automations.go` |
|
||||
| tracker | `tracker` | fee knob | `apps/tracker/tracker.go` |
|
||||
| security | `security.scan` | — | `apps/security/security.go` |
|
||||
|
||||
**Product/agent read axes.** The console's per-product Metrics dashboard groups on
|
||||
`metadata.product` (and `metadata.agent`). Commerce's `RecordUsage` persists the
|
||||
metering SURFACE (`provider`) and billed UNIT (`model`) but has **no `product`
|
||||
field** (its `usageRequest` drops `project`/`service`/`product`/`agent`). So the
|
||||
customer read handler `clients/billing/usage.go` is the ONE read-side adapter:
|
||||
customer read handler `apps/billing/usage.go` is the ONE read-side adapter:
|
||||
`usage()` fetches the org-scoped ledger and, on 200, injects a canonical
|
||||
`metadata.product` onto every row (`productOf`: `agent→agents`,
|
||||
`provisioning→<kind>`, token-metered→`inference`, else `provider`) so the
|
||||
@@ -178,7 +178,7 @@ no-op when the meter/commerce persist them natively (forward-compatible).
|
||||
until commerce persists an `agent` field the agents meter sets to `a.Name`.
|
||||
3. **compute split** — `ml` (predict) and `visor` (GPU) both meter `provider=compute`;
|
||||
read-side can't split `inference` vs `gpus`. Needs (1) so each sets its product id.
|
||||
4. **exec / containers** (`clients/exec`, Code Interpreter) — authed by a shared
|
||||
4. **exec / containers** (`apps/exec`, Code Interpreter) — authed by a shared
|
||||
service key (X-API-Key), NO per-org identity, so it can't meter per-org; its
|
||||
compute is billed upstream at the chat/agent layer that invokes it.
|
||||
5. **playground** — routes to `/v1/ai/*`, already metered as AI inference.
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
# IAM cutover — Casdoor pod → embedded IAM in cloud (supervised)
|
||||
|
||||
Flip `hanzo.id`'s identity plane from the standalone Casdoor pod to the clean-room
|
||||
IAM rewrite embedded in this binary (`clients/iam`, `github.com/hanzoai/iam`
|
||||
IAM rewrite embedded in this binary (`apps/iam`, `github.com/hanzoai/iam`
|
||||
**v1.33.6**). This is the last step of HIP-0106 (one binary embeds IAM + KMS + o11y).
|
||||
|
||||
**This is a single supervised session. Do not run it piecemeal or in the
|
||||
@@ -32,7 +32,7 @@ The blocking code work is **done and shipped**:
|
||||
store is single-writer/single-open). **`config.go` refuses to boot iam-enabled
|
||||
above 1 replica.** Never scale up with iam on.
|
||||
3. `CLOUD_DATA_DIR=/var/lib/cloud` on the RWO `cloud-api-data` PVC. The embedded IAM
|
||||
store is **`/var/lib/cloud/iam/iam2.db`** (`clients/iam/iam.go` `paths()`) — the v2
|
||||
store is **`/var/lib/cloud/iam/iam2.db`** (`apps/iam/iam.go` `paths()`) — the v2
|
||||
store. `iam.db` is a different database; opening it serves the wrong identities
|
||||
without failing.
|
||||
4. You have the live Casdoor store to migrate FROM and its KMS master key:
|
||||
@@ -68,7 +68,7 @@ migrate-v1 \
|
||||
`iam/cmd/migrate-v1/main.go`): a `.db` path is taken verbatim, anything else is
|
||||
treated as a data-dir and gets `/iam2.db` appended. So `…/iam/iam2.db` and `…/iam`
|
||||
are equivalent. What matters is that the written file is exactly
|
||||
`/var/lib/cloud/iam/iam2.db` — the path `clients/iam` opens.
|
||||
`/var/lib/cloud/iam/iam2.db` — the path `apps/iam` opens.
|
||||
|
||||
**Verify:** dry-run report shows expected counts for users, orgs, applications,
|
||||
providers, certs; zero drift; zero errors.
|
||||
@@ -89,7 +89,7 @@ there is no env var to add. Deploying the image IS this step. Nothing here is
|
||||
additive or reversible by a flag: plan Step 1 to complete before the next roll.
|
||||
|
||||
Apply the CR; the operator rolls the Recreate Deployment (single pod, brief blip —
|
||||
expected). On boot, `clients/iam` opens `/var/lib/cloud/iam/iam2.db` (the migrated
|
||||
expected). On boot, `apps/iam` opens `/var/lib/cloud/iam/iam2.db` (the migrated
|
||||
store), seeds new-only from `init_data.json` (idempotent — real rows already present,
|
||||
so seed only adds anything genuinely missing), and mounts the full `/v1/iam/*` surface
|
||||
IN-PROCESS. `iam_edge.go` stops mounting (`serve.go`: `if !cfg.Enabled("iam") …`), so
|
||||
|
||||
@@ -35,7 +35,7 @@ source for the generated per-language SDKs.
|
||||
## Key entry points
|
||||
- `cmd/cloud` — server binary · `cmd/hanzo` (`cli/`) — control CLI · `webui.go` — embedded console
|
||||
- `apps/apps.go:Wire()` — composition root (the one ordered subsystem slice)
|
||||
- `deps.go` / `cloud.Deps` — process-wide handles · `clients/<name>/` — every subsystem
|
||||
- `deps.go` / `cloud.Deps` — process-wide handles · `apps/<name>/` — every subsystem
|
||||
- `openapi/` — the document pipeline: the spec is a projection of the live router,
|
||||
and `openapi.yaml` at the root is a GOLDEN of it (written by `make openapi`,
|
||||
verified by `make test` — not a second source)
|
||||
@@ -64,23 +64,23 @@ and reports this 5-route plane as 29. Two products, one string prefix.
|
||||
|
||||
| Route | Noun | Owner | Tier |
|
||||
| --- | --- | --- | --- |
|
||||
| `/v1/connectors` | Custody: per-user BYO external accounts | `clients/integrations` (both planes; user scope) | Shipped — 8 ops |
|
||||
| `/v1/channels` | Transport: portable message envelope, DM pairing, send + inbox | `clients/channels` | Shipped — 8 ops |
|
||||
| `/v1/sync` | Data: bidirectional sync engine | `clients/sync` | Shipped — 7 ops |
|
||||
| `/v1/automations` | Workflows: flows/runs, goja piece runtime | `clients/automations` | Shipped — 20 ops |
|
||||
| `/v1/bots` | A bot RUN on a surface | `clients/bots` | Shipped — 4 ops |
|
||||
| `/v1/compute/bots` | A bot MACHINE (kind=bot + agent binding) | `clients/visor` — NOT `clients/bots` | Shipped — 5 ops |
|
||||
| `/v1/tasks` | Durable engine | `clients/tasks` | Shipped — 11 ops |
|
||||
| `/v1/machines` `/v1/gpus` `/v1/fleet` `/v1/clusters` `/v1/k8s` `/v1/compute` | Compute: provisioned + BYO machines, GPUs, k8s clusters | `clients/visor` (+ `clients/fleet` registry) | Shipped — 33 ops |
|
||||
| `/v1/cloud` | Cloud accounts: link DO/AWS/GCP/Azure, discover native k8s clusters, fold into the fleet | `clients/venue` | Shipped — 5 ops |
|
||||
| `/v1/blueprint` | Cost: OSS-template SBOM (compose→images) + compute-cost estimate | `clients/blueprint` | Shipped — 3 ops |
|
||||
| `/v1/templates` | Starter kits: ONE entry per template, shapes as variants | `clients/templates` | Shipped — 2 ops |
|
||||
| `/v1/iam` | Identity: users, orgs, roles | `clients/iam` | Shipped — opaque (catch-all, see below) |
|
||||
| `/v1/kms` | Secret custody: sealed secrets | `clients/kms` | Shipped — 7 ops |
|
||||
| `/v1/connectors` | Custody: per-user BYO external accounts | `apps/integrations` (both planes; user scope) | Shipped — 8 ops |
|
||||
| `/v1/channels` | Transport: portable message envelope, DM pairing, send + inbox | `apps/channels` | Shipped — 8 ops |
|
||||
| `/v1/sync` | Data: bidirectional sync engine | `apps/sync` | Shipped — 7 ops |
|
||||
| `/v1/automations` | Workflows: flows/runs, goja piece runtime | `apps/automations` | Shipped — 20 ops |
|
||||
| `/v1/bots` | A bot RUN on a surface | `apps/bots` | Shipped — 4 ops |
|
||||
| `/v1/compute/bots` | A bot MACHINE (kind=bot + agent binding) | `apps/visor` — NOT `apps/bots` | Shipped — 5 ops |
|
||||
| `/v1/tasks` | Durable engine | `apps/tasks` | Shipped — 11 ops |
|
||||
| `/v1/machines` `/v1/gpus` `/v1/fleet` `/v1/clusters` `/v1/k8s` `/v1/compute` | Compute: provisioned + BYO machines, GPUs, k8s clusters | `apps/visor` (+ `apps/fleet` registry) | Shipped — 33 ops |
|
||||
| `/v1/cloud` | Cloud accounts: link DO/AWS/GCP/Azure, discover native k8s clusters, fold into the fleet | `apps/venue` | Shipped — 5 ops |
|
||||
| `/v1/blueprint` | Cost: OSS-template SBOM (compose→images) + compute-cost estimate | `apps/blueprint` | Shipped — 3 ops |
|
||||
| `/v1/templates` | Starter kits: ONE entry per template, shapes as variants | `apps/templates` | Shipped — 2 ops |
|
||||
| `/v1/iam` | Identity: users, orgs, roles | `apps/iam` | Shipped — opaque (catch-all, see below) |
|
||||
| `/v1/kms` | Secret custody: sealed secrets | `apps/kms` | Shipped — 7 ops |
|
||||
|
||||
`/v1/bots` and `/v1/compute/bots` are two nouns with two owners; the row above
|
||||
pairs each with the package that REGISTERS it. Pairing `/v1/compute/bots` with
|
||||
`clients/bots` is the merge "Bot is three values" (below) exists to forbid.
|
||||
`apps/bots` is the merge "Bot is three values" (below) exists to forbid.
|
||||
|
||||
Custody invariants: secrets sealed in KMS, never in SQLite rows; verify before
|
||||
store. Two scopes, two paths, one rule — the path is built from the VALIDATED
|
||||
@@ -117,7 +117,7 @@ Discovery ends at `fleet.Register` — exactly where `visor.attachCluster` ends
|
||||
which is what makes "discovered" and "attached" the same kind of cluster
|
||||
afterwards. Do not add a cluster store; extend the fold.
|
||||
|
||||
**`visor` is an agent name, not a query surface.** `clients/visor` OWNS the
|
||||
**`visor` is an agent name, not a query surface.** `apps/visor` OWNS the
|
||||
compute plane, but it serves it at the nouns above (`/v1/machines`,
|
||||
`/v1/clusters`, `/v1/gpus`, `/v1/fleet`, `/v1/k8s`, `/v1/compute`) — never under
|
||||
`/v1/visor`. The only live `/v1/visor` route is `GET /v1/visor/health`, and that
|
||||
@@ -158,7 +158,7 @@ directives are load-bearing and each fixes exactly one graph hazard:
|
||||
`github.com/ugorji/go/codec` (pulled by gin) is unambiguous.
|
||||
- the `k8s.io/*` staging replace block pins every staging module to the `v0.35.3`
|
||||
line. `k8s.io/kubernetes` is a GRAPH-ONLY transitive require of
|
||||
`hanzoai/deploy/gitops-engine` (clients/deploy uses its `pkg/utils/kube`); NO
|
||||
`hanzoai/deploy/gitops-engine` (apps/deploy uses its `pkg/utils/kube`); NO
|
||||
cloud package imports `k8s.io/kubernetes`, so its staging tree never compiles —
|
||||
do not "drop k8s.io/kubernetes", the pins keep the graph consistent and it is
|
||||
never built. koanf resolves to the split modules; the `koanf v1.5.0` monolith
|
||||
@@ -176,16 +176,16 @@ Test modes: `make test` is pure-Go (`CGO_ENABLED=0`). Encrypted-at-rest OrgDB
|
||||
tests (`cek`, `CLOUD_KMS_MASTER_KEY_REF` set) REQUIRE `CGO_ENABLED=1` +
|
||||
libsqlcipher (`cek/cek.go` refuses to encrypt in pure-Go); those run only in the
|
||||
Dockerfile's dedicated `-tags libsqlite3` CGO stage, and fail under `make test`
|
||||
by design (clients/git, kms, flags, x402, cmd/kmsreseal, finance). Bundle-embed
|
||||
tests (clients/tasks/ui) need `make deploy-ui` first (real bundle is gitignored).
|
||||
by design (apps/git, kms, flags, x402, cmd/kmsreseal, finance). Bundle-embed
|
||||
tests (apps/tasks/ui) need `make deploy-ui` first (real bundle is gitignored).
|
||||
|
||||
Store-heavy subsystem tests are fsync-bound, not CPU-bound. A mount opens its own
|
||||
SQLite stores, so a test that mounts several subsystems commits many times, and
|
||||
`t.TempDir()` under `/tmp` puts every commit behind the ext4 journal — on a box with
|
||||
a concurrent build the same mount that costs milliseconds idle costs ~90s, at ~0%
|
||||
CPU, blocked in `jbd2_log_wait_commit`. Point `TMPDIR` at tmpfs to measure the real
|
||||
cost: `TMPDIR=/dev/shm/t GOWORK=off go test -p 1 ./clients/guide` runs the eight-seam
|
||||
cross-subsystem harness (`clients/guide/drivehome_e2e_test.go`) in under a second.
|
||||
cost: `TMPDIR=/dev/shm/t GOWORK=off go test -p 1 ./apps/guide` runs the eight-seam
|
||||
cross-subsystem harness (`apps/guide/drivehome_e2e_test.go`) in under a second.
|
||||
Prefer one package per `go test` invocation regardless: `./...` links every main
|
||||
package at once (`cmd/cloud` alone links >6GB).
|
||||
|
||||
@@ -239,7 +239,7 @@ and `manifest/apps.go`, so an app cannot exist in one and not the other. Prefixe
|
||||
come from, in order: the `PluginSpec` call's own arguments, a declared
|
||||
`Prefixes:` field, then the absolute paths the app's package registers — read by
|
||||
walking the call graph from the entry's Mount function (per FUNCTION, because
|
||||
`clients/account` serves two Wire entries and a package-wide scan gives each the
|
||||
`apps/account` serves two Wire entries and a package-wide scan gives each the
|
||||
other's paths). Both registration forms are read: `app.Get("/v1/x", h)` and the
|
||||
typed `zip.Get(reg, "/v1/x", h)`. The walk resolves consts, `[]string` ranges and
|
||||
concatenation, and tracks which values are Groups so `g.Get("/health")` never
|
||||
@@ -263,7 +263,7 @@ listen, and all but the first die on "address already in use".
|
||||
|
||||
CI pins both properties from `hanzo.yml`: `generated-current` re-runs the
|
||||
generator and fails on a dirty tree; `host-is-light` fails if `cmd/host`'s import
|
||||
graph reaches `apps` or any `clients/*`.
|
||||
graph reaches `apps` or any `apps/*`.
|
||||
|
||||
### Where a subsystem's binary comes from — ONE ladder, and the last rung is the network
|
||||
|
||||
@@ -334,11 +334,11 @@ published binary is the static one (hanzo.yml:23-26).
|
||||
`zip.App.ReloadTo(name, Plugin{URL, Sum})` via
|
||||
`POST /v1/admin/plugins/:name/reload`. Any on-demand or per-org upgrade path
|
||||
must drive one of those two.
|
||||
- **Reload is SuperAdmin-gated, and audited BEFORE it acts.** `clients/plugin`
|
||||
mounts four routes (clients/plugin/plugin.go:78-81): list, reload, enable, disable. Every
|
||||
- **Reload is SuperAdmin-gated, and audited BEFORE it acts.** `apps/plugin`
|
||||
mounts four routes (apps/plugin/plugin.go:78-81): list, reload, enable, disable. Every
|
||||
mutation is SuperAdmin-gated and written to the hash-chained audit trail BEFORE
|
||||
it is reported as done; a deployment with NO durable audit store REFUSES the
|
||||
operation rather than performing an unrecorded one (clients/plugin/fleet.go:154-155).
|
||||
operation rather than performing an unrecorded one (apps/plugin/fleet.go:154-155).
|
||||
Reload starts the replacement and proves it LISTENING before any traffic moves,
|
||||
so a bad build leaves the old one serving and returns an error rather than a
|
||||
hole; naming a digest this host has run before IS the rollback, and costs no
|
||||
@@ -432,7 +432,7 @@ is exactly the bug this replaced.
|
||||
|
||||
## One build contract: `mk/plugin.mk`, and an app's Makefile is its name
|
||||
|
||||
`clients/<app>/Makefile` is two lines — `APPS := <name>` and
|
||||
`apps/<app>/Makefile` is two lines — `APPS := <name>` and
|
||||
`include ../../mk/plugin.mk`. Everything an app can be asked to do lives in that
|
||||
one included file: `generate` (zipdoc lifts handler prose into `zipdoc_gen.go`;
|
||||
a prerequisite of `build` — mk/plugin.mk:62 — because it is compiled IN, so
|
||||
@@ -451,15 +451,15 @@ own standalone pass at line 178. See "Generated and frozen artifacts" below —
|
||||
this asymmetry is still live, and it is why the 15 `zipdoc_gen.go` files are
|
||||
committed.
|
||||
|
||||
Both invocations work — `make -C clients/tasks openapi` from the root and
|
||||
`cd clients/tasks && make openapi` — because `mk/plugin.mk` derives every path
|
||||
Both invocations work — `make -C apps/tasks openapi` from the root and
|
||||
`cd apps/tasks && make openapi` — because `mk/plugin.mk` derives every path
|
||||
from the including Makefile's own location, never from the caller's cwd. That is
|
||||
what makes the OSS/private split a move rather than a rewrite: an extracted
|
||||
`clients/<app>` + `cmd/<app>` + `mk/` keeps the paths intact.
|
||||
`apps/<app>` + `cmd/<app>` + `mk/` keeps the paths intact.
|
||||
|
||||
`APPS` is a list and is never inferred from the directory name — four packages
|
||||
are not named after their app (`zt`→zero-trust, `eval`→evals, `auditlog`→audit,
|
||||
`plugin`→plugins) and `clients/account` backs two mounts. Three apps (authz,
|
||||
`plugin`→plugins) and `apps/account` backs two mounts. Three apps (authz,
|
||||
licensing, metrics) are external modules with a `cmd/<app>` and no source
|
||||
directory here; `mk/fleet.mk` runs them through the same recipe by name.
|
||||
`mk/go.mk` is the toolchain contract every includer shares (GOWORK=off, TMPDIR on
|
||||
@@ -468,7 +468,7 @@ disk, `-p=2`, the dev KMS key, the FTS5 tag).
|
||||
## Framework doctrine
|
||||
|
||||
One way to do everything. Composable, orthogonal, DRY. A new subsystem is a
|
||||
package under `clients/<name>` that obeys these seams — nothing more.
|
||||
package under `apps/<name>` that obeys these seams — nothing more.
|
||||
|
||||
- **Subsystem shape.** A subsystem exposes
|
||||
`func Mount(app cloud.Router, deps cloud.Deps) error` — `MountFunc`
|
||||
@@ -509,7 +509,7 @@ package under `clients/<name>` that obeys these seams — nothing more.
|
||||
Today `o11y` is the only one — the heaviest graph in the tree (otel-collector,
|
||||
prometheus, gonum), imported by nothing else, so unlinking it is pure
|
||||
subtraction. **Unlinking means deleting the IMPORT, not just the mount**:
|
||||
`apps/apps.go` carries a standing comment where `clients/o11y` would be
|
||||
`apps/apps.go` carries a standing comment where `apps/o11y` would be
|
||||
imported, because an import there would keep its 2.7k-package graph linked
|
||||
whether or not any Wire entry referenced it.
|
||||
- `price` is POSITIONAL, ahead of the variadic prefixes, and that placement is
|
||||
@@ -679,7 +679,7 @@ document pipeline" below.)
|
||||
- **The product axis is mechanical.** The first path segment after `/v1/` IS the
|
||||
product (`openapi.Product`), tagged onto each operation so a CLI can build
|
||||
`hanzo <product> <resource> <verb>` with no judgment. It is deliberately NOT the
|
||||
subsystem name: `clients/billing` also serves `/v1/finance/*`.
|
||||
subsystem name: `apps/billing` also serves `/v1/finance/*`.
|
||||
- **What the router CANNOT tell you — do not try to fix this in the generator.**
|
||||
Method, path, path params, and product are derivable; request/response schemas,
|
||||
query/header params, status codes, and auth are NOT. The router holds a
|
||||
@@ -740,7 +740,7 @@ one before it.
|
||||
document still cannot disagree with the router. Schemas are additive metadata
|
||||
on routes that exist. A duplicate registration for one `(method, path)` panics
|
||||
at init rather than letting two declarations race. Seven declarations live
|
||||
there today, all `clients/platform` (platform.go:248-254) — this is a bridge,
|
||||
there today, all `apps/platform` (platform.go:248-254) — this is a bridge,
|
||||
not the destination; the destination is the typed op.
|
||||
- **zipdoc is why the prose exists at all.** Go drops comments at compile time,
|
||||
so the build-time pass is the ONLY way a handler's doc comment, its field
|
||||
@@ -786,7 +786,7 @@ one before it.
|
||||
- **`openapi.Weave` composes the subsets, and its only contribution is the
|
||||
REFUSAL** (openapi/weave.go). Two apps may not claim one address, and two apps
|
||||
may not mean different things by one schema name. That refusal is not
|
||||
hypothetical: `clients/git`'s `/:org/:repo` catch-all was swallowing other
|
||||
hypothetical: `apps/git`'s `/:org/:repo` catch-all was swallowing other
|
||||
apps' routes, and the manifest's call-graph walk is what caught it. The routing
|
||||
order in `Wire()` is load-bearing precisely because overlapping claims exist.
|
||||
A merge that took last-write-wins would produce a perfectly valid
|
||||
@@ -845,9 +845,9 @@ operations across 984 paths, of which 164 have a description.** The other ~1234
|
||||
are route only — no MCP tool, no CLI command, no SDK method, no schema, no
|
||||
prose.
|
||||
|
||||
The typed 15 are `clients/admin` and its eight sub-packages, plus `clients/git`,
|
||||
`clients/integrations`, `clients/marketing`, `clients/plugin`, `clients/search`,
|
||||
`clients/visor`. `clients/admin/core/typed.go` states the rule for that surface:
|
||||
The typed 15 are `apps/admin` and its eight sub-packages, plus `apps/git`,
|
||||
`apps/integrations`, `apps/marketing`, `apps/plugin`, `apps/search`,
|
||||
`apps/visor`. `apps/admin/core/typed.go` states the rule for that surface:
|
||||
every `/v1/admin/*` route is a typed op.
|
||||
|
||||
**What compensates today, and how it dies.** hanzoai/openapi carries an AUTHORED
|
||||
@@ -894,7 +894,7 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
change.** The weave gate catches it: `TestFleetIsTheWeaveOfItsApps` compares the
|
||||
composition against `openapi.yaml`, so an app whose subset no longer matches its
|
||||
routes fails there — and a MISSING subset fails immediately, naming the file.
|
||||
The fix is to re-emit: `make -C clients/<app> openapi` for one,
|
||||
The fix is to re-emit: `make -C apps/<app> openapi` for one,
|
||||
`make -f mk/fleet.mk openapi-apps` for all of them. Never edit the JSON, and
|
||||
never relax the gate. The same test also LOGS `UNROUTED: <app> serves <path>,
|
||||
which the fleet routes nowhere` — reported rather than refused, because that one
|
||||
@@ -908,7 +908,7 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
|
||||
## Cross-subsystem seams that are values, not places
|
||||
|
||||
- **The per-principal MCP plane is callable in-process.** `clients/automations`
|
||||
- **The per-principal MCP plane is callable in-process.** `apps/automations`
|
||||
decomplects tool dispatch from its front doors: `dispatchTool` is the ONE core
|
||||
(resolve `<connector>_<action>` → run with a Token bound to the VALIDATED org),
|
||||
and TWO doors share it — the HTTP JSON-RPC handler (`POST /v1/automations/mcp`)
|
||||
@@ -922,19 +922,19 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
resolves byte-identical patterns by first-registration with no panic (it MERGES
|
||||
the handlers, so counting `GetRoutes()` entries cannot see it), and visor's
|
||||
machine list silently answered the console's run list.
|
||||
(1) A bot RUN — a task the runtime executes on a surface — is `clients/bots` at
|
||||
(1) A bot RUN — a task the runtime executes on a surface — is `apps/bots` at
|
||||
`/v1/bots`. (2) A bot MACHINE — visor-provisioned compute of kind=bot plus its
|
||||
agent binding — is `clients/visor` at `/v1/compute/bots`; what it rents you is
|
||||
agent binding — is `apps/visor` at `/v1/compute/bots`; what it rents you is
|
||||
compute, so it nests in visor's domain. (3) The runtime SERVICE — the TS bot
|
||||
(channels/skills), never reimplemented in Go — is reached through
|
||||
`clients/runtime`, which is a TRANSPORT, not a domain: base address, identity,
|
||||
`apps/runtime`, which is a TRANSPORT, not a domain: base address, identity,
|
||||
framing, cleartext policy, and the `/v1/bot/*` ops face. It is named for what it
|
||||
does, not for the host it dials, and it must never import `bots`/`coding` — each
|
||||
of those owns its own wire stub (`bots/wire.go`, `coding/task.go`) and speaks
|
||||
through the seam. That isolation is what makes the HIP-0106/HIP-0120 ZAP swap a
|
||||
seam swap instead of a rewrite.
|
||||
- **Cloud owns policy; the runtime owns the run. Do not copy state you do not
|
||||
own.** `clients/bots` holds NO store. The sandbox lives in the bot runtime,
|
||||
own.** `apps/bots` holds NO store. The sandbox lives in the bot runtime,
|
||||
keyed in the runtime's own tenant store, which is the only thing that knows
|
||||
whether a run is alive — so list and stop PROXY it, gated by cloud's
|
||||
principal/org. A cloud-side registry was tried and was wrong: it minted an id
|
||||
@@ -947,7 +947,7 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
`ProjectStore` is READ-ONLY (List/Get/Exists — re-adding Create breaks the
|
||||
build). `/v1/run` RESOLVES the org's default project (424 → IAM when absent),
|
||||
never mints one. Apps whose IAM project is gone are removed by the orphan
|
||||
reaper (`clients/platform/orphans.go`) — fails SAFE (IAM unreachable ⇒ reap
|
||||
reaper (`apps/platform/orphans.go`) — fails SAFE (IAM unreachable ⇒ reap
|
||||
nothing), one existence question per (org,project), volumes left behind.
|
||||
- **An app declares storage** (`storageGb` on the platform Application): the
|
||||
deploy ensures an RWO claim `<slug>-data`, mounts it at `/data`, and forces
|
||||
@@ -962,16 +962,16 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
cross-org readers. The org remains the STORE partition (`/orgs/<org>/…`).
|
||||
- **Per-tenant KMS identity is minted, not runbooked.** On a missing
|
||||
`orgs/<org>/kms-auth/*` credential and with `IAM_SERVICE_TOKEN` set,
|
||||
`clients/platform` calls IAM's idempotent bootstrap upsert to create
|
||||
`apps/platform` calls IAM's idempotent bootstrap upsert to create
|
||||
`<org>-platform-kms` (clientId==name==audience; a surprise clientId is
|
||||
refused, never sealed; the upsert must carry `cert-<brand>` or the minted app
|
||||
cannot SIGN and its tokens 500), seals both fields, and the sync proceeds.
|
||||
In-cluster IAM base resolution is `cloud.IAMBaseURL` — the split-horizon
|
||||
policy stated once (Cloudflare 403s server-side POSTs to the public issuer).
|
||||
- **A customer IS an IAM user; marketing keeps no contact list.** Who to email is
|
||||
read IN-PROCESS from the embedded IAM (`clients/marketing/roster.go` →
|
||||
`iam/pkg/store.GetMailableUsers` over `clients/iam.DB()`), the same seam
|
||||
`clients/platform` uses for the IAM-owned Project — no HTTP hop to `/v1/iam`
|
||||
read IN-PROCESS from the embedded IAM (`apps/marketing/roster.go` →
|
||||
`iam/pkg/store.GetMailableUsers` over `apps/iam.DB()`), the same seam
|
||||
`apps/platform` uses for the IAM-owned Project — no HTTP hop to `/v1/iam`
|
||||
from inside the binary, IAM's `model.User` verbatim, read-only, masked. The org
|
||||
is `principal.Org`, which IS IAM's `Owner`, so an audience can only ever resolve
|
||||
its own tenant; `GetMailableUsers` REFUSES an empty org rather than falling back
|
||||
@@ -991,7 +991,7 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
`runtime.ErrNotServed` (the operation does not exist). Conflating them makes a
|
||||
stop that cannot fail: a runtime without the route reports absent for EVERY run,
|
||||
so "already gone" becomes permanently true. A bare 404 is 502, never success.
|
||||
- **The Business AI Guide (`clients/guide`, `/v1/guide/*`)** is the on-site launch
|
||||
- **The Business AI Guide (`apps/guide`, `/v1/guide/*`)** is the on-site launch
|
||||
checklist: a pure engine (`curriculum.go` — parse/validate/next-step/dependency
|
||||
gating over plain data) + per-org progress (`cloud.OrgStore[*Store]`) + an
|
||||
injectable auto-detect registry (`detect.go` — `acted` reads the agent action
|
||||
@@ -1000,7 +1000,7 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
curriculum is a machine-readable contract (embedded `default.yaml`; org-custom via
|
||||
PUT replaces it) so `hanzoai/marketing` can author the full `checklist.yaml`
|
||||
against the same `Step`/`Curriculum` shape.
|
||||
- **The EXPERIMENT is a composition, not a fourth engine (`clients/experiments`,
|
||||
- **The EXPERIMENT is a composition, not a fourth engine (`apps/experiments`,
|
||||
`/v1/experiments`).** A/B testing is ONE value whatever the variant KIND (feature
|
||||
flag, ad creative, email subject, model id): the primitive owns only the
|
||||
experiment registry (definition + decision); it COMPOSES three planes it never
|
||||
@@ -1013,12 +1013,12 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
event store; the analyze fold joins each subject's analytics outcome to its flags
|
||||
variant by `distinct_id`. EVIDENCE = `research.Record`/`research.List` — per-variant
|
||||
samples land as immutable `kind:"ab"` rows; significance (two-proportion z-test,
|
||||
`math.Erfc`, no dep) is a PURE function over them. `clients/campaign` runs a
|
||||
`math.Erfc`, no dep) is a PURE function over them. `apps/campaign` runs a
|
||||
creative A/B by composing `experiments.Assign`/`experiments.Analyze` — it never
|
||||
reinvents assignment or evidence. Add a new variant KIND by putting a payload on
|
||||
the variant; the primitive does not care what it is.
|
||||
- **The OSS-template compute cost is DERIVED from the compose, not a fourth ledger
|
||||
(`clients/blueprint`, `/v1/blueprint`).** A blueprint's `docker-compose.yml` is
|
||||
(`apps/blueprint`, `/v1/blueprint`).** A blueprint's `docker-compose.yml` is
|
||||
parsed to its SBOM (the bill of container images) and its services' CPU/memory
|
||||
footprint priced through ONE documented rate card (microdollars per vCPU-/GB-hour,
|
||||
DigitalOcean-droplet-derived + platform margin; tunable via
|
||||
@@ -1028,32 +1028,32 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
returns `{sbom, vcpuHr, gbHr, microUsdPerHour, estCentsPerMonth}`: `estCentsPerMonth`
|
||||
is the "~$X/mo to run" the console shows; `microUsdPerHour` is the exact rate the
|
||||
deploy path meters the deploying org on via the SAME commerce spine `resource_billing`
|
||||
uses. The author royalty (`clients/authors`, `defaultShareBps=2000`) already accrues
|
||||
uses. The author royalty (`apps/authors`, `defaultShareBps=2000`) already accrues
|
||||
20% of a deploying org's metered spend — this plane only DEFINES the compute component
|
||||
of that spend from a real rate card; it never touches the ledger or the accrual sweep.
|
||||
Distinct from `clients/sbom` (CycloneDX packages INSIDE one image, keyed by digest);
|
||||
Distinct from `apps/sbom` (CycloneDX packages INSIDE one image, keyed by digest);
|
||||
this is the bill of IMAGES a stack runs, keyed by template.
|
||||
|
||||
## Identity vocabulary is IAM-native
|
||||
|
||||
Identity is expressed ONLY in IAM-native nouns: **org, user, project, billing
|
||||
account**. The word **"tenant" is banned** in cloud identifiers, strings,
|
||||
comments, and filenames. Resolve org/project scope through `clients/principal`
|
||||
comments, and filenames. Resolve org/project scope through `apps/principal`
|
||||
(`principal.Org(c)`, `principal.Project(c)`) and user identity through `c.User()`
|
||||
— all gateway-minted, JWT-validated values (X-Org-Id / X-Project-Id / X-User-Id,
|
||||
HIP-0026); never read a raw request header for scope.
|
||||
|
||||
- **The one gated exception.** `clients/platform` derives customer-app Kubernetes
|
||||
- **The one gated exception.** `apps/platform` derives customer-app Kubernetes
|
||||
namespaces, registry image refs, and quota/limit objects from a live `tenant-<org>`
|
||||
string prefix. Renaming that prefix orphans deployed namespaces + built images,
|
||||
so the literal `"tenant-"` string (and its directly-adjacent comment) is retained
|
||||
behind a `// NAMING(gated)` note in `clients/platform/k8s.go`. The surrounding
|
||||
behind a `// NAMING(gated)` note in `apps/platform/k8s.go`. The surrounding
|
||||
identity vocabulary is org-native regardless; only the on-cluster string waits on
|
||||
an infrastructure migration.
|
||||
|
||||
## API keys are ONE noun (`/v1/keys`), and the type is a FIELD
|
||||
|
||||
`POST` creates, `DELETE` revokes, `GET` lists. `clients/account/account.go`.
|
||||
`POST` creates, `DELETE` revokes, `GET` lists. `apps/account/account.go`.
|
||||
`mint`, `issue` and `revoke` are HTTP methods, never path segments — the concept
|
||||
previously had four names (`/v1/iam/mint-user-keys`, `/v1/iam/revoke-user-keys`,
|
||||
`/v1/iam/keys`, `/v1/ingest/keys`) and the only honest one 404'd.
|
||||
@@ -1092,7 +1092,7 @@ Requires `IAM_PUBLISHABLE_RESOLVE_APPS`, which is fail-closed.
|
||||
`schema.User.AccessKey` reports "no key" immediately after a successful POST. That is
|
||||
the "key never listed" bug and it has recurred twice.
|
||||
|
||||
## Hanzo Company (`clients/company`, `/v1/company`)
|
||||
## Hanzo Company (`apps/company`, `/v1/company`)
|
||||
|
||||
The Stripe-Atlas-class incorporation + fundraising product: ONE formation state
|
||||
machine per org. `machine.go` is the PURE core — a `transitions` table with a guard
|
||||
@@ -1106,14 +1106,14 @@ machine composes them identically in prod and tests: billing → the shared
|
||||
`ResourceMeter` ($999 one-time fee); documents → `dataroom.Ingest` (new in-proc
|
||||
facade); cap table → `captable.*` (new in-proc facades: SetIncorporation /
|
||||
AddStakeholders / EnsureShareClass / IssueShares / RecordRound); equity genesis →
|
||||
a KMS-signed Hanzo-L1 anchor mirroring `clients/treasury` (honest pending when
|
||||
a KMS-signed Hanzo-L1 anchor mirroring `apps/treasury` (honest pending when
|
||||
unwired); KYC + state filing → honest stubs (no fabricated verification/filing).
|
||||
Import path (already-incorporated orgs): Google Drive → data room, a Google Sheet →
|
||||
captable, via the `google` OAuth provider now completed in `clients/integrations`
|
||||
captable, via the `google` OAuth provider now completed in `apps/integrations`
|
||||
(token custodied in KMS; the automations `google` connector shares the same token).
|
||||
Runbook: `docs/company-dogfood.md`.
|
||||
|
||||
## Deploy plane (`clients/deploy`, `/v1/deploy`)
|
||||
## Deploy plane (`apps/deploy`, `/v1/deploy`)
|
||||
|
||||
Native ArgoCD-grade GitOps console over the operator-managed fleet, parallel to
|
||||
`/v1/git`: each `hanzo.ai/v1` App CR IS the Application, and the plane OBSERVES the
|
||||
@@ -1125,7 +1125,7 @@ image to a prior semver and `/{name}/sync` requests a reconcile. SUPERADMIN-only
|
||||
`gitops-engine` (`hanzoai/deploy/gitops-engine` v0.7.2, no replace) in-process for the
|
||||
reconcile half behind `DEPLOY_ENGINE_ENABLED` (default off), with a prune-safety fuse.
|
||||
|
||||
## The index (`clients/index`, `/v1/index`)
|
||||
## The index (`apps/index`, `/v1/index`)
|
||||
|
||||
The in-binary index, speaking the Meilisearch REST dialect so a Meilisearch client
|
||||
repoints by changing one host. It replaced the standalone Meilisearch containers
|
||||
@@ -1133,9 +1133,9 @@ repoints by changing one host. It replaced the standalone Meilisearch containers
|
||||
|
||||
**Four different things, four names — do not merge them.** `hanzoai/search` is the
|
||||
SEARCH PRODUCT (our own Meilisearch build, serving `search.hanzo.ai` and the docs
|
||||
corpus). `clients/websearch` queries the OUTSIDE world. `clients/crawl` fetches it
|
||||
corpus). `apps/websearch` queries the OUTSIDE world. `apps/crawl` fetches it
|
||||
(in-binary — see below; the standalone `hanzoai/crawl` service it used to call is gone).
|
||||
`clients/index` is the storage primitive an application writes documents into and
|
||||
`apps/index` is the storage primitive an application writes documents into and
|
||||
queries back. It is NOT at `/v1/search`: that path belongs to the `hanzoai/ai` RAG
|
||||
plane, whose `/v1/search/{name}` pattern silently swallowed this subsystem's
|
||||
single-segment routes (`/health`, `/version` answered 404 in production while every
|
||||
@@ -1162,11 +1162,11 @@ keeps the wrapped data key beside it as `<path>.dek`, so moving the `.db` alone
|
||||
strands the key and every document becomes undecryptable — data loss that presents
|
||||
as an empty index.
|
||||
|
||||
## The cross-org catalog (`clients/catalog`, `/v1/catalog`)
|
||||
## The cross-org catalog (`apps/catalog`, `/v1/catalog`)
|
||||
|
||||
Everything the fleet has built — hanzo, lux and zoo repos, plus every site this
|
||||
deployment serves — as ONE searchable corpus. It owns no store: the rows live in
|
||||
`clients/index` under the uid `catalog`, so relevance, paging and encryption at rest
|
||||
`apps/index` under the uid `catalog`, so relevance, paging and encryption at rest
|
||||
are the index's. What catalog adds is the one thing a per-org index cannot express,
|
||||
a corpus that spans orgs, and it does that with a SECOND corpus rather than a
|
||||
weaker filter:
|
||||
@@ -1272,7 +1272,7 @@ somebody else's. A patch had pinned the badge back on from an embedded 75-slug
|
||||
manifest, which drifted out of agreement with reality within days of the template
|
||||
rename. A second copy of an unforgeable fact can only ever be the wrong one.
|
||||
|
||||
**Visibility, not authorship, decides who appears** (`clients/projects/visibility.go`).
|
||||
**Visibility, not authorship, decides who appears** (`apps/projects/visibility.go`).
|
||||
One axis owned by the publisher — `public` (default) or `private` — plus
|
||||
`hidden`, the platform's subtractive moderation from admin.hanzo.ai. A row is
|
||||
listed iff `public AND NOT hidden`, enforced in `LiveSites`' own query so a
|
||||
@@ -1293,7 +1293,7 @@ GitHub failing to identify a licence, not a licence, so those rows carry the
|
||||
upstream and state no terms. Live: 437 repos, 40 third-party, every one naming its
|
||||
real parent.
|
||||
|
||||
## Starter kits (`clients/templates`, `/v1/templates`)
|
||||
## Starter kits (`apps/templates`, `/v1/templates`)
|
||||
|
||||
One embedded PUBLIC catalog (read-only; a customer's own templates are the second
|
||||
layer, below), and **one template is one entry**. The shapes a
|
||||
@@ -1370,12 +1370,12 @@ A slug stays single-valued across both layers: publishing over a public slug is
|
||||
Two DIFFERENT orgs may hold the same private slug — the key is `(org, slug)`.
|
||||
|
||||
`templates.Lookup(ctx, org, slug)` is the ONE door other subsystems read through
|
||||
(`clients/projects`' fork resolves the caller org's own templates first, then the
|
||||
(`apps/projects`' fork resolves the caller org's own templates first, then the
|
||||
gallery), so "which templates may this org use" is answered in exactly one place;
|
||||
a fork of a private template records owner-qualified lineage (`acme/acme-portal`),
|
||||
a fork of a gallery template records the bare public slug.
|
||||
|
||||
## Fetching the web (`clients/crawl`, `/v1/crawl`)
|
||||
## Fetching the web (`apps/crawl`, `/v1/crawl`)
|
||||
|
||||
In-binary fetch + extract + markdown. It replaced a call to a standalone crawler
|
||||
at `crawl.hanzo.svc.cluster.local:11235` — a name that had stopped resolving, which
|
||||
@@ -1415,7 +1415,7 @@ cache hit, never the page.
|
||||
## Releases are cut by a merge to main
|
||||
|
||||
`.github/workflows` is intentionally empty of CI. The image and its `v*` tags have ONE
|
||||
owner, `clients/platform/release.go`: compute the next version → build → SMOKE the
|
||||
owner, `apps/platform/release.go`: compute the next version → build → SMOKE the
|
||||
pushed image → tag → roll out. The tag is a RECEIPT for a proven image, so a
|
||||
change that breaks boot never reaches production and leaves no phantom tag.
|
||||
|
||||
@@ -1447,7 +1447,7 @@ than one that fails.
|
||||
|
||||
### Site releases already have a lifecycle — do not build a second one
|
||||
|
||||
`clients/projects` owns the full versioned-release model for static sites, and it is
|
||||
`apps/projects` owns the full versioned-release model for static sites, and it is
|
||||
the ONE way:
|
||||
|
||||
- `<org>/.releases/<slug>/rel_<128-bit manifest digest>/` — immutable, content-
|
||||
@@ -1458,7 +1458,7 @@ the ONE way:
|
||||
row)`, so it cannot point a site at a release that was never created, and two
|
||||
concurrent activations cannot leave the pointer disagreeing with whichever won.
|
||||
`MarkLive` deliberately does NOT touch `current_release`.
|
||||
- `servePrefix` (`clients/projects/sites.go`) — the ONE read rule, re-validating the
|
||||
- `servePrefix` (`apps/projects/sites.go`) — the ONE read rule, re-validating the
|
||||
id against `releaseIDRE` before it can widen a prefix. An unrecognized id falls back
|
||||
to the legacy prefix, so there is no flag day and no migration.
|
||||
- Rollback is activating an older id. Routes are already mounted on both site
|
||||
@@ -1500,13 +1500,13 @@ Two consequences, both load-bearing:
|
||||
|
||||
**Three transports, one trigger.** A push reaches `cloud.OnGitPush` — the
|
||||
single-registrant seam, never a second CI — from the embedded git server
|
||||
(`clients/git/smart_http.go`), the GitHub App (`/v1/connector/github/webhook`), and
|
||||
the canonical forge (`/v1/git/webhook`, `clients/git/webhook.go`). The third exists
|
||||
(`apps/git/smart_http.go`), the GitHub App (`/v1/connector/github/webhook`), and
|
||||
the canonical forge (`/v1/git/webhook`, `apps/git/webhook.go`). The third exists
|
||||
because git.hanzo.ai is a SEPARATE process: its pushes never touch our receive-pack,
|
||||
so without that door the host we call canonical builds nothing and only the mirror
|
||||
releases. Both webhook transports HMAC-verify fail-closed and drop bot-authored
|
||||
pushes through the one `cloud.IsBotActor`, so a release cannot retrigger itself.
|
||||
`clients/platform` is the only place that decides what a push MEANS: an app tracking
|
||||
`apps/platform` is the only place that decides what a push MEANS: an app tracking
|
||||
the repo rebuilds, and cloud's own upstream cuts a release. Cloud is the machine, so it calls the
|
||||
release in-process and the build token is never handed to a caller. The trigger runs
|
||||
BEFORE the token mint and the mirror, because a build reads from GitHub and must not
|
||||
@@ -1533,7 +1533,7 @@ the final bearer fallback — no `--platform-token`). The ONE contract, no TS-Do
|
||||
- `hanzo deploy <app>` → `POST /v1/paas/apps/{app}/deploy` — a zero-downtime ROLLING
|
||||
RESTART (stamps the Deployment pod-template `hanzo.ai/restartedAt` annotation; never
|
||||
changes the declared TAG — that stays a git commit CD reconciles). `--env` picks the ns.
|
||||
- `hanzo clusters list|get` → `GET /v1/clusters` (`clients/visor`, tenant-scoped)
|
||||
- `hanzo clusters list|get` → `GET /v1/clusters` (`apps/visor`, tenant-scoped)
|
||||
- `hanzo build` → `POST /v1/runner` (native buildkit fabric). With `--image` it
|
||||
builds a container image; with NO `--image` it reads the repo's own `hanzo.yml`
|
||||
(`binaries:` + `bucket:`) and builds the ARTIFACT lane instead — see below.
|
||||
@@ -1543,7 +1543,7 @@ the final bearer fallback — no `--platform-token`). The ONE contract, no TS-Do
|
||||
`/v1/runner` has two lanes, and a request is in exactly one of them:
|
||||
|
||||
- **image** (`image:`) → `launchDirectBuild` → rootless BuildKit → a pushed OCI ref.
|
||||
- **artifact** (`binaries:`) → `launchArtifactBuild` (`clients/platform/artifact.go`) → a
|
||||
- **artifact** (`binaries:`) → `launchArtifactBuild` (`apps/platform/artifact.go`) → a
|
||||
Job whose initContainers are ONE PER RECIPE ENTRY, each in that entry's toolchain image
|
||||
(`image:`, default `golang:1.26-bookworm`), sharing `/w`; then a publisher that hashes
|
||||
everything the recipe left in `/w/dist`, PUTs it to hanzoai/s3 and writes `binaries.json`
|
||||
@@ -1563,7 +1563,7 @@ PUBLIC URL, because `s3.hanzo.ai` is this cluster's own LoadBalancer and does no
|
||||
a pod dialling it times out. Its egress hole is `artifact-publish-egress` in universe,
|
||||
selecting the pod label `hanzo.ai/publish=artifact`.
|
||||
|
||||
`/v1/paas/*` auth mirrors `/v1/runner` (`clients/platform/runner.go`): the `guard` admits a
|
||||
`/v1/paas/*` auth mirrors `/v1/runner` (`apps/platform/runner.go`): the `guard` admits a
|
||||
validated principal who is SuperAdmin OR OrgAdmin, then each handler CONFINES a non-super
|
||||
caller to the platform namespaces its own validated org owns (`scopedNamespaces`, keyed on
|
||||
`principal.Org` — a tenant admin can never observe/restart another org's, or a platform,
|
||||
@@ -1578,18 +1578,18 @@ for the apps board, which reads k8s directly with no IAM-store dependency.
|
||||
## GTM: `/v1/campaign` orchestration → channels → connectors → analytics
|
||||
|
||||
The go-to-market stack decomplects a campaign from its execution. A **Campaign is a
|
||||
VALUE** (`clients/campaign`: `{name, audience, content[], schedule, budget, channels[],
|
||||
VALUE** (`apps/campaign`: `{name, audience, content[], schedule, budget, channels[],
|
||||
status}`) that SPANS channels; a **Channel is an EXECUTOR** (`channel.go`, the
|
||||
`Channel` interface) it fans out to. The three channels are orthogonal and each
|
||||
CONSUMES the connector plane via `integrations.TokenFor` — the campaign object never
|
||||
touches a credential:
|
||||
|
||||
- **paid → `/v1/ads`** — `ads.LaunchPaid/PaidSpend/PausePaid` (`clients/ads/provider.go`)
|
||||
- **paid → `/v1/ads`** — `ads.LaunchPaid/PaidSpend/PausePaid` (`apps/ads/provider.go`)
|
||||
resolve the org's ad token (`meta_ads`/`google_ads`/… via `TokenFor(org, <id>,
|
||||
"access_token")`) and run the campaign on the provider. Meta is executed for real;
|
||||
fail-closed when the org has not connected (424). This is the ONLY place `/v1/ads`
|
||||
touches the connector plane.
|
||||
- **organic → `/v1/publish`** (rename of `clients/social`) and **email → `/v1/marketing`**
|
||||
- **organic → `/v1/publish`** (rename of `apps/social`) and **email → `/v1/marketing`**
|
||||
are DESIGNED follow-ons: register their executors the same way in `apps/wire_seams.go`
|
||||
(`campaign.RegisterChannel(campaign.NewChannel(kind, launch, spend, pause))`). Until
|
||||
wired, a fan-out records that channel "unavailable" (honest), never fabricated.
|
||||
@@ -1602,7 +1602,7 @@ campaign can only ever resolve its OWN org's token.
|
||||
|
||||
**Metrics = the ONE analytics plane, not a second store.** `GET /v1/campaign/:id/metrics`
|
||||
reads the funnel from `analytics.CampaignMetrics(org, campaignID, variant, start, end)`
|
||||
(`clients/analytics/campaign.go`) — an org+`utm_campaign`(+`utm_content`)-scoped query
|
||||
(`apps/analytics/campaign.go`) — an org+`utm_campaign`(+`utm_content`)-scoped query
|
||||
over `hanzo.events`, org and campaign bound POSITIONALLY (same tenancy invariant as
|
||||
every analytics query) — joined with each channel connector's reported spend
|
||||
(`Channel.Spend`). Derived KPIs: CTR/CVR/CAC/ROAS. Honest-empty when the warehouse is
|
||||
@@ -1622,7 +1622,7 @@ configuration surface is below, because the shape people expect (a per-repo sync
|
||||
setting) does not exist and should not be added.
|
||||
|
||||
**There is no per-repo and no per-ref configuration.** A repo is not enrolled, and a
|
||||
ref is not filtered. `clients/integrations/github_webhook.go` accepts any ref under
|
||||
ref is not filtered. `apps/integrations/github_webhook.go` accepts any ref under
|
||||
`refs/`, and `Ref` stays a FULL ref (`refs/heads/x`, `refs/tags/v1.2.3`) the length of
|
||||
the chain — webhook → `SyncEvent` → `sync.Event` → `GitInboundReq` → `inboundFastForward`
|
||||
→ `GitPushEvent`. Tags matter here: a version is published by tag, so a filter on
|
||||
@@ -1636,7 +1636,7 @@ propagated`): the mirror does not get to delete canonical history.
|
||||
|
||||
**A consumer that wants a branch cuts the prefix itself.** `strings.CutPrefix(ev.Ref,
|
||||
"refs/heads/")` answers "is this a branch" and "what is its name" in one total step,
|
||||
so a tag can never rebuild an app that tracks a branch (`clients/platform/push.go`),
|
||||
so a tag can never rebuild an app that tracks a branch (`apps/platform/push.go`),
|
||||
and `refs/tags/main` is not `refs/heads/main` for the release check.
|
||||
|
||||
**Tenancy comes only from the installation id** in the HMAC-verified body — never a
|
||||
@@ -1648,7 +1648,7 @@ header, never a client-controlled field. That is the whole tenant resolution:
|
||||
`Store.Get(ctx, org, provider)` keys a connection on `(org, "github")`: **one row per
|
||||
org**, holding one installation id. The App's own identity is a single set of process
|
||||
values, `GITHUB_APP_ID` + `GITHUB_APP_PRIVATE_KEY` + `GITHUB_APP_WEBHOOK_SECRET`
|
||||
(KMS-synced, `clients/integrations/github.go`), used to mint short-lived installation
|
||||
(KMS-synced, `apps/integrations/github.go`), used to mint short-lived installation
|
||||
tokens.
|
||||
|
||||
So pointing a second App at the same webhook with the same secret does not double
|
||||
@@ -1683,7 +1683,7 @@ Process values, all with working defaults — none of them selects *what* syncs:
|
||||
- `GIT_SYNC_ACTOR` — the actor recorded for a sync-initiated write.
|
||||
|
||||
**What genuinely is per-repo is a different plane**, and naming it keeps the two from
|
||||
being confused: `/v1/git/repos/:name/*` (`clients/git/subscriptions.go`) holds a
|
||||
being confused: `/v1/git/repos/:name/*` (`apps/git/subscriptions.go`) holds a
|
||||
repo→Slack-channel subscription and a repo→downstream mirror target. Those are
|
||||
reactor config, org-scoped like every repo route. The code index is also per-repo and
|
||||
indexes the default branch only — a feature-branch push is skipped so it cannot
|
||||
@@ -1710,7 +1710,7 @@ same predicate `cek.EnsureDevKey` uses:
|
||||
Injecting regardless is not a stricter posture — on a pure-Go build it is a hard refusal
|
||||
from `resolveDEK`, `Mount` never reaches `transport.SetApp`, and every S2S billing read
|
||||
then falls through to the network and DNS-resolves the in-process placeholder. That is
|
||||
the failure `clients/commerce/transport` documents: balance reads that answer
|
||||
the failure `apps/commerce/transport` documents: balance reads that answer
|
||||
"Insufficient balance" on funded accounts, with DNS named as the cause.
|
||||
|
||||
**The gate only enforces on a kind that costs something.** `ResourceMeter.Gate`
|
||||
@@ -1742,7 +1742,7 @@ preference, but because they all move the same balance and the suite is otherwis
|
||||
## Encryption at rest: cek is the gate, and per-principal binding is not done yet
|
||||
|
||||
`cek.Open` is the ONE encryption-at-rest gate — ~50 stores, plus IAM's identity store
|
||||
(`clients/iam.openStore`, which previously opened through `iamserver.OpenSQLite` and
|
||||
(`apps/iam.openStore`, which previously opened through `iamserver.OpenSQLite` and
|
||||
left `iam/iam2.db` beginning with the literal `SQLite format 3` magic). If you add a
|
||||
store, open it through cek; if a store is not in the envelope it has no `.dek` sidecar
|
||||
beside it, and that absence is the check worth running on any new data dir:
|
||||
|
||||
@@ -40,7 +40,7 @@ OPENAPI_DIR ?= ../openapi
|
||||
# libsqlcipher, so NEITHER exercises the engine the image ships. Without the codec,
|
||||
# cek falls back to the pure-Go envelope, whose properties differ — a store is
|
||||
# single-writer and durable at close rather than in-place and per-commit. The tests
|
||||
# that pin the shipped storage posture (clients/kms concurrent-open, audit
|
||||
# that pin the shipped storage posture (apps/kms concurrent-open, audit
|
||||
# shareability) therefore skip in both targets. `make test-codec` below is the one
|
||||
# that runs them, and needs a real libsqlcipher to do it.
|
||||
CGO_ENABLED ?= 0
|
||||
@@ -65,23 +65,23 @@ webui: ## Build the real console static bundle into webui/dist (go:embed source)
|
||||
cp -r "$(CONSOLE_DIR)/out/." webui/dist/
|
||||
@echo ">> embedded real console bundle into webui/dist (index.html $$(wc -c < webui/dist/index.html) bytes)"
|
||||
|
||||
deploy-ui: ## Build the monochrome ArgoCD dashboard bundle into clients/deploy/webui/dist (go:embed source). DEPLOY_DIR=<path to hanzoai/deploy>.
|
||||
deploy-ui: ## Build the monochrome ArgoCD dashboard bundle into apps/deploy/webui/dist (go:embed source). DEPLOY_DIR=<path to hanzoai/deploy>.
|
||||
@command -v yarn >/dev/null 2>&1 || { echo "yarn is required to build the deploy dashboard bundle"; exit 1; }
|
||||
@test -f "$(DEPLOY_DIR)/ui/package.json" || { echo "deploy checkout not found at $(DEPLOY_DIR) — set DEPLOY_DIR=<path to hanzoai/deploy on rebrand/hanzo-monochrome>"; exit 1; }
|
||||
@test -d "$(DEPLOY_DIR)/ui/node_modules" || (cd "$(DEPLOY_DIR)/ui" && yarn install --frozen-lockfile)
|
||||
cd "$(DEPLOY_DIR)/ui" && NODE_OPTIONS=--max-old-space-size=8192 yarn build
|
||||
# Overlay the fresh bundle, keeping only the tracked fallback (.gitignore +
|
||||
# index.html shell); the real 43MB bundle is build-time-only (gitignored).
|
||||
find clients/deploy/webui/dist -mindepth 1 -maxdepth 1 ! -name .gitignore -exec rm -rf {} +
|
||||
cp -r "$(DEPLOY_DIR)/ui/dist/app/." clients/deploy/webui/dist/
|
||||
@echo ">> embedded monochrome ArgoCD bundle into clients/deploy/webui/dist (index.html $$(wc -c < clients/deploy/webui/dist/index.html) bytes)"
|
||||
find apps/deploy/webui/dist -mindepth 1 -maxdepth 1 ! -name .gitignore -exec rm -rf {} +
|
||||
cp -r "$(DEPLOY_DIR)/ui/dist/app/." apps/deploy/webui/dist/
|
||||
@echo ">> embedded monochrome ArgoCD bundle into apps/deploy/webui/dist (index.html $$(wc -c < apps/deploy/webui/dist/index.html) bytes)"
|
||||
|
||||
agentskills: ## Regenerate the FULL agent-skills catalog into clients/agentskills/catalog (go:embed source) from the openapi SOT. OPENAPI_DIR=<path to openapi>.
|
||||
agentskills: ## Regenerate the FULL agent-skills catalog into apps/agentskills/catalog (go:embed source) from the openapi SOT. OPENAPI_DIR=<path to openapi>.
|
||||
@test -f "$(OPENAPI_DIR)/skills.py" || { echo "openapi checkout not found at $(OPENAPI_DIR) — set OPENAPI_DIR=<path> or clone hanzoai/openapi"; exit 1; }
|
||||
# skills.py rewrites the whole catalog dir; the .gitignore keeps only the tiny
|
||||
# `ai` fallback tracked, so the full set is embedded at build but never committed.
|
||||
python3 "$(OPENAPI_DIR)/skills.py" --no-services --out clients/agentskills/catalog
|
||||
@echo ">> embedded FULL agent-skills catalog ($$(jq -r .skill_count clients/agentskills/catalog/hanzo/index.json) skills/brand)"
|
||||
python3 "$(OPENAPI_DIR)/skills.py" --no-services --out apps/agentskills/catalog
|
||||
@echo ">> embedded FULL agent-skills catalog ($$(jq -r .skill_count apps/agentskills/catalog/hanzo/index.json) skills/brand)"
|
||||
|
||||
# THE DEFAULT BUILD IS THE HOST, and that is the whole point of the plugin model:
|
||||
# nothing compiles together. The fused binary linked all 112 subsystems into one
|
||||
@@ -183,7 +183,7 @@ TEST_ENV = CLOUD_KMS_MASTER_KEY_REF="$${CLOUD_KMS_MASTER_KEY_REF:-$(DEV_KMS_KEY)
|
||||
# The release image builds with -tags "libsqlite3 sqlite_fts5" (see Dockerfile).
|
||||
# libsqlite3 needs cgo and the C library, but sqlite_fts5 does not — and without it
|
||||
# any store whose migration declares an FTS5 table fails to open, so a subsystem
|
||||
# built on full-text search (clients/code) cannot be tested at all. Carry the tag
|
||||
# built on full-text search (apps/code) cannot be tested at all. Carry the tag
|
||||
# the shipped build carries, so the suite exercises the same schema surface.
|
||||
TEST_TAGS := sqlite_fts5
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ package cloud
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ import (
|
||||
"github.com/hanzoai/account"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
"github.com/hanzoai/account"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/commerce/transport"
|
||||
"github.com/hanzoai/cloud/apps/commerce/transport"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -38,21 +38,21 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/customer"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/finance"
|
||||
"github.com/hanzoai/cloud/clients/admin/health"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/clients/admin/infra"
|
||||
"github.com/hanzoai/cloud/clients/admin/invoices"
|
||||
"github.com/hanzoai/cloud/clients/admin/metrics"
|
||||
"github.com/hanzoai/cloud/clients/admin/revenue"
|
||||
"github.com/hanzoai/cloud/clients/admin/subscriptions"
|
||||
"github.com/hanzoai/cloud/clients/commerce/transport"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/admin/audit"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/customer"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/health"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/infra"
|
||||
"github.com/hanzoai/cloud/apps/admin/invoices"
|
||||
"github.com/hanzoai/cloud/apps/admin/metrics"
|
||||
"github.com/hanzoai/cloud/apps/admin/revenue"
|
||||
"github.com/hanzoai/cloud/apps/admin/subscriptions"
|
||||
"github.com/hanzoai/cloud/apps/commerce/transport"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/health"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/health"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
luxlog "github.com/luxfi/log"
|
||||
fiber "github.com/zap-proto/fiber/v3"
|
||||
"github.com/zap-proto/zip"
|
||||
@@ -61,8 +61,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// Fully-qualified datastore tables. admin only READS these — the ai gateway owns
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
)
|
||||
|
||||
// ── wire shapes (operator contract) ──────────────────────────────────────────
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
// mkTime is a test helper for an RFC3339-ish instant.
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
auditstore "github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
auditstore "github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
luxlog "github.com/luxfi/log"
|
||||
fiber "github.com/zap-proto/fiber/v3"
|
||||
"github.com/zap-proto/zip"
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,9 +36,9 @@ package admin
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// doBlockUsdPerGiB is DO block storage's list price ($0.10/GiB/mo) — the fleet cost
|
||||
@@ -17,7 +17,7 @@ package admin
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
)
|
||||
|
||||
// The DO inventory folds into fleet totals + rows, and per-volume fill stays ABSENT
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
fiber "github.com/zap-proto/fiber/v3"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/customer"
|
||||
"github.com/hanzoai/cloud/clients/admin/revenue"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/customer"
|
||||
"github.com/hanzoai/cloud/apps/admin/revenue"
|
||||
)
|
||||
|
||||
// ── rich stateful fakes for the customer-management surfaces ──────────────────
|
||||
@@ -32,8 +32,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/clients/commerce/transport"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/commerce/transport"
|
||||
)
|
||||
|
||||
// errUnconfigured marks a write (Deposit) attempted against an unwired commerce.
|
||||
@@ -37,8 +37,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// computeTable is the operator-owned compute-usage warehouse table (named to match
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
)
|
||||
|
||||
// SeriesPoint is one bucketed point (count OR cents, per the series). T is the bucket
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/clients/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/finance"
|
||||
)
|
||||
|
||||
// MaxCustomerConcurrency bounds the per-org enrichment fan-out so a large fleet does
|
||||
@@ -1,7 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/clients/finance"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/finance"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/hanzoai/commerce/billing/creditledger"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/account"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/health"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/health"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
)
|
||||
|
||||
// State is admin's own data: the resolved upstream clients + the admin org for this
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/principal"
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// BillingEventsTable is the collector-owned warehouse table the commerce
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
// createCreditGrant mints credit for one org. It is the ONE admin mint surface, and it
|
||||
@@ -29,8 +29,8 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
)
|
||||
|
||||
// ── wire shapes (operator contract) ──────────────────────────────────────────
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
// The GRANTS surface (/v1/admin/grants) — the operator cockpit's credit-grant ledger. A
|
||||
@@ -4,7 +4,7 @@ package customer
|
||||
|
||||
import (
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
)
|
||||
|
||||
// apiBase is DigitalOcean's public API host. Overridable in tests via NewWithBase.
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/commerce"
|
||||
ledger "github.com/hanzoai/cloud/clients/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/commerce"
|
||||
ledger "github.com/hanzoai/cloud/apps/finance"
|
||||
)
|
||||
|
||||
// BackfillIn is the POST /v1/admin/finance/backfill input.
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
|
||||
aiobject "github.com/hanzoai/ai/object"
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
"github.com/hanzoai/types"
|
||||
)
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/finance"
|
||||
)
|
||||
|
||||
// The finance PURE-math derivation tests (ComputeFinance / AvgDailyBurnCents) live with
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/flags"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/flags"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
)
|
||||
|
||||
// volumeGiBCents is DO's block-storage rate: $0.10 per GiB per month. Droplet LOCAL
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
)
|
||||
|
||||
// Two live clusters. The near-miss that motivated this package involved volumes tagged
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/audit"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
)
|
||||
|
||||
// cacheTTL bounds how stale a READ may be. It exists because one board is a fan-out
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
)
|
||||
|
||||
// fakeDO stands in for the DigitalOcean API. kubeAPI is the URL a cluster kubeconfig
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
)
|
||||
|
||||
// TestLiveCollect runs the real fan-out against the real DigitalOcean account and the
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/clients/fleet"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/fleet"
|
||||
)
|
||||
|
||||
// clusterScanTimeout bounds ONE cluster's read. A cluster that exceeds it is recorded
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/digitalocean"
|
||||
"github.com/hanzoai/cloud/apps/admin/digitalocean"
|
||||
)
|
||||
|
||||
const halfGiB = int64(gib) / 2
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// defaultLimit caps the fleet invoice list when the caller sends none.
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
// TestInvoiceRowsFromRows proves the warehouse-row → InvoiceRow mapping (JSON-shape
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// errUnconfigured marks the warehouse not connected on this deployment — core.SrcOf
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
)
|
||||
|
||||
// TestFillHeadline proves the run-rate headline coercion (driver ints) + the
|
||||
@@ -44,13 +44,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/commerce"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/customer"
|
||||
"github.com/hanzoai/cloud/clients/admin/finance"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/clients/admin/revenue"
|
||||
"github.com/hanzoai/cloud/clients/treasury"
|
||||
"github.com/hanzoai/cloud/apps/admin/commerce"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/customer"
|
||||
"github.com/hanzoai/cloud/apps/admin/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/revenue"
|
||||
"github.com/hanzoai/cloud/apps/treasury"
|
||||
)
|
||||
|
||||
// moneyGrantScan bounds the audit scan behind the grant totals. Grants are staff-issued
|
||||
@@ -3,11 +3,11 @@ package admin
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/customer"
|
||||
"github.com/hanzoai/cloud/clients/admin/finance"
|
||||
"github.com/hanzoai/cloud/clients/admin/money"
|
||||
"github.com/hanzoai/cloud/clients/admin/revenue"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/customer"
|
||||
"github.com/hanzoai/cloud/apps/admin/finance"
|
||||
"github.com/hanzoai/cloud/apps/admin/money"
|
||||
"github.com/hanzoai/cloud/apps/admin/revenue"
|
||||
)
|
||||
|
||||
// TestFoldGrants_SplitsBucketsAndTallies proves the two money buckets are kept apart
|
||||
@@ -46,8 +46,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/datastore"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/datastore"
|
||||
)
|
||||
|
||||
// Fully-qualified datastore tables. admin only READS these — the ZAP collector
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/platform"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/platform"
|
||||
)
|
||||
|
||||
// products lists the fleet workload registry: every operator App CR across the platform
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/clients/admin/core"
|
||||
"github.com/hanzoai/cloud/clients/admin/iam"
|
||||
"github.com/hanzoai/cloud/apps/admin/core"
|
||||
"github.com/hanzoai/cloud/apps/admin/iam"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user