Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
306 lines
20 KiB
Markdown
306 lines
20 KiB
Markdown
# Scratchpad — Appliance registration → download → install flow
|
||
|
||
- Plan slug: `appliance-registration-install-flow`
|
||
- Created: `2026-06-05`
|
||
- Source spec: `docs/superpowers/specs/2026-06-05-appliance-registration-install-flow-design.md`
|
||
|
||
## What This Is
|
||
|
||
Working memory for the register → download → install (+ re-issue) flow. The spine
|
||
is a **direction inversion**: today the appliance generates its own tenant UUID and
|
||
sends it up at `/register`; the new flow mints `tenant_id` upstream at registration,
|
||
carries it down via a one-time install code, and the appliance **adopts** it via a
|
||
new `INITIAL_TENANT_ID`.
|
||
|
||
## Decisions
|
||
|
||
- (2026-06-05) **Reuse `/register`** for the friendly install code (decision 1) —
|
||
one redeem path, reuse the claim-code machinery.
|
||
- (2026-06-05) **Extend `claim_codes`** (decision 2): `entitlement_id` → nullable,
|
||
add `tenant_id` FK → `tenant_registry`. One code type for essentials (no
|
||
entitlement) and paid. Chosen over a separate `install_tokens` table.
|
||
- (2026-06-05) **Presigned for all** downloads (decision 3) — registration-gated,
|
||
not public; ISO isn't a secret, the code is the gate.
|
||
- (2026-06-05) **Re-issue in scope now** (decision 4) — portal action resolves an
|
||
existing registry tenant and mints a fresh code for the same `tenant_id`.
|
||
- (2026-06-05) Test weighting is **smoke-first** (user directive): small automated
|
||
set for the riskiest seams, everything else validated live on the VM. This
|
||
deliberately inverts the software-planner default (tests longer than features) —
|
||
`tests.json` is shorter and smoke-weighted on purpose.
|
||
- (2026-06-05) **The short code is the identity carrier for BOTH tiers** (not just
|
||
paid). Essentials installs get a short code too and never type a raw tenant_id;
|
||
the code's `tenant_id` is set for everyone, `entitlement_id` is null for
|
||
essentials. One-liner framing: "a short code always resolves a tenant; for paid
|
||
it additionally mints a bound license."
|
||
- (2026-06-05) **§16 open questions all resolved:** (1) alga-license presigns with
|
||
object-store creds via **env vars**; the presigned URL is **emailed** to the user
|
||
after registration/purchase (alongside the code) — not a public link. (2) Paid
|
||
ordering: registry row at form submit, entitlement + code on
|
||
`checkout.session.completed`. (3) Essentials edition from
|
||
`tenant_registry.edition` (no edition on the code). (4) Reuse
|
||
`generateClaimCode()` for install codes. Remaining impl detail (not a fork):
|
||
which object-store key holds the current generic ISO + how the release process
|
||
publishes it (F041).
|
||
|
||
## Discoveries / Constraints
|
||
|
||
- (2026-06-05) **`claim_codes` baseline is `entitlement_id NOT NULL`**
|
||
(`alga-license/migrations/03_claim_codes.cjs`). Needs a new migration to relax it
|
||
and add `tenant_id`.
|
||
- (2026-06-05) **alga-license accessors that already exist** (`src/db/db.ts`):
|
||
`createRegistryTenant`, `getRegistryTenant`, `getRegistryTenantByEmail`,
|
||
`insertClaimCode`, `getClaimCode`, `consumeClaimCode`,
|
||
`revokeClaimCodesForEntitlement`, `upsertAppliance` (already takes `tenantId`),
|
||
`setEntitlementLicenseSub`, `getEntitlementById`. Signing helpers exist:
|
||
`signLicense` (takes `aud`), `generateClaimCode`, `generateApplianceCredential`,
|
||
`generateLicenseId`.
|
||
- (2026-06-05) **New alga-license work:** the migration, `insertClaimCode` +
|
||
`getClaimCode`/`ClaimCodeRow` for `tenant_id`/nullable entitlement, a new
|
||
`revokeClaimCodesForTenant` (essentials reissue has no entitlement to key on),
|
||
a `setRegistryTenantInstalled`, the `/register` essentials+response changes, the
|
||
`/register-tenant` and `/install-codes/reissue` routes, and a MinIO presign
|
||
helper.
|
||
- (2026-06-05) **`/register` today** (`src/routes/register.ts`): requires an
|
||
entitlement (`getEntitlementById` → 500 if none), binds `aud` from the
|
||
**request body** `tenant_id`, returns `{ appliance_credential, first_jwt,
|
||
check_in_url }`. The new flow sources `tenant_id` from the **code row** and
|
||
returns it (+ edition) to the appliance.
|
||
- (2026-06-05) **`/check-in`** (`src/routes/checkIn.ts`) already preserves
|
||
`appliance.tenant_id` as `aud` across re-signs — no change needed.
|
||
- (2026-06-05) **The appliance tenant UUID is born in**
|
||
`ee/server/src/lib/testing/tenant-creation.ts` `createTenant` (~line 80): it
|
||
inserts into `tenants` without a `tenant` value, so the DB default
|
||
(`gen_random_uuid()`) generates it and it's returned via `.returning('tenant')`.
|
||
That insert is the exact `INITIAL_TENANT_ID` seam.
|
||
- (2026-06-05) **`connectAppliance`** (`server/src/lib/actions/licenseManagementActions.ts`,
|
||
~line 125) already POSTs `{ claim_code, appliance_id }` to `/register` and seeds
|
||
`license_state` with `first_jwt`/`appliance_credential`/`check_in_url` — but
|
||
AFTER the tenant exists. The install-time consumer generalizes this to run before
|
||
create-tenant and adopt the returned `tenant_id`.
|
||
- (2026-06-05) **Licval WIP already in the working tree** (uncommitted, NOT part of
|
||
this plan's deltas — treat as precondition F001): `create-tenant.ts` +
|
||
`tenant-creation.ts` thread `INITIAL_ADMIN_PASSWORD`/`args.password` through to
|
||
`createAdminUser` (uses `input.password ?? generateSecurePassword()`),
|
||
initialize `tenant_settings` (onboarding pending), and honor
|
||
`DB_HOST`/`DB_PORT`/`DB_USER_ADMIN`. `INITIAL_TENANT_ID` lands in the same two
|
||
files. Do not revert this WIP.
|
||
|
||
## Build-step 1 — alga-license schema + /register (DONE on branch `feat/registration-install-flow`)
|
||
|
||
- (2026-06-05) **Two refinements discovered while implementing** (both fed back into
|
||
the PRD/features):
|
||
- `claim_codes.tenant_id` is **NULLABLE**, not NOT NULL. Migration alters
|
||
`entitlement_id` NULLABLE via `knex.raw('ALTER TABLE … DROP NOT NULL')` (knex
|
||
`.alter()` would try to rebuild the FK) and adds `tenant_id uuid` nullable FK +
|
||
index (`migrations/05_claim_codes_registry.cjs`). `/register` resolves
|
||
`tenant = row.tenant_id ?? body.tenant_id` — the body path is the clean legacy
|
||
fallback, which only works because tenant_id is nullable.
|
||
- **Essentials `/register` creates NO appliance row.** `appliances.entitlement_id`
|
||
is `NOT NULL` (FK), and essentials has nothing to refresh (no license → no
|
||
check-in), so the essentials branch returns `{ tenant_id, edition }` and does
|
||
not call `upsertAppliance` or issue a credential. (Original F012 said "still
|
||
upsertAppliance" — corrected.)
|
||
- (2026-06-05) `createRegistryTenant` already accepted a pinned `tenantId` and
|
||
returns the row — `/register-tenant` (build step 3) is mostly wiring.
|
||
- (2026-06-05) `RegisterResponse` now returns `tenant_id` + `edition` +
|
||
`company_name`/`contact_email`; `appliance_credential`/`first_jwt`/`check_in_url`
|
||
are **optional** (paid only). The existing alga-psa `connectAppliance` destructures
|
||
the three paid fields and ignores the rest, so paid back-compat holds.
|
||
- (2026-06-05) **Validated:** `tsc --noEmit` clean; migration applies + rolls back +
|
||
re-applies on a throwaway `postgres:16` (5433); jest **28/28** incl. 3 new seam
|
||
tests (claim_codes tenant_id round-trip, `revokeClaimCodesForTenant`,
|
||
`setRegistryTenantInstalled`). The repo's `signing.test.ts` IS the gated-on-DB_HOST
|
||
pg integration suite — extend that block, don't add a parallel harness.
|
||
- (2026-06-05) Test weighting note: `/register` HTTP behavior (T003/T004) is
|
||
**smoke**, not Fastify route tests — validated in the build-step-4 live loop, per
|
||
the light-automated directive.
|
||
|
||
## Build-step 2 — INITIAL_TENANT_ID seam (code in working tree, alga-psa; UNCOMMITTED w/ licval WIP)
|
||
|
||
- (2026-06-05) Implemented F050/F051/F052/F054/F055: `createTenant`
|
||
(`ee/server/src/lib/testing/tenant-creation.ts`) takes optional `tenantId` →
|
||
sets `tenantInsert.tenant` when present (else DB `gen_random_uuid()` default,
|
||
unchanged); idempotency guard returns the existing tenant (+ its client) if the
|
||
id already exists so a re-run doesn't error/duplicate. Threaded through
|
||
`TenantCreationInput` → `createTenantComplete`. `create-tenant.ts` reads
|
||
`INITIAL_TENANT_ID` (env or `--tenantId`) and passes it down.
|
||
- (2026-06-05) **Three `tenant-creation.ts` copies** — only
|
||
`ee/server/src/lib/testing/tenant-creation.ts` is the live path (what
|
||
`server/scripts/create-tenant.ts` imports). `packages/ee/src/lib/testing/…` is
|
||
the CE stub (throws). No parallel edit needed.
|
||
- (2026-06-05) No caller breakage: the change is additive-optional;
|
||
`tenant-test-factory.ts` (the only other `createTenantComplete` consumer) is
|
||
unaffected. Other `createTenant(` matches are unrelated functions (temporal
|
||
activities, server test-utils).
|
||
- (2026-06-05) **Not committed** — these two files also carry the user's licval
|
||
WIP (password seam). Left uncommitted in the working tree to avoid committing
|
||
in-flight work; appliance changes (steps 2+4) commit together when the user is
|
||
ready. F053 (bootstrap passes the redeemed id) + full typecheck land in step 4.
|
||
- (2026-06-05) T007/T008 reclassified AUTO→SMOKE: exercising `createTenant`'s id
|
||
adoption needs the full alga-psa `tenants`/`clients` schema (heavy), so it's
|
||
validated in the live install loop, not a unit DB.
|
||
|
||
## Build-step 3 — /register-tenant + /install-codes/reissue + presign (DONE, alga-license branch)
|
||
|
||
- (2026-06-05) **Dependency-free SigV4 presigner** (`src/storage/presign.ts`) — the
|
||
service had no AWS SDK and is intentionally lean (fastify/knex/pg only), so the
|
||
S3/MinIO presigned-GET URL is hand-rolled with `node:crypto` (path-style,
|
||
UNSIGNED-PAYLOAD, query-auth). `now` is injectable for deterministic tests.
|
||
- (2026-06-05) Object-store config is **env-driven and OPTIONAL**:
|
||
`OBJECT_STORE_ENDPOINT/REGION/BUCKET/ACCESS_KEY/SECRET_KEY` + `APPLIANCE_ISO_KEY`
|
||
(default `appliance/current/alga-appliance.iso`). `getPresignConfigFromEnv()`
|
||
returns null when unset so existing deploys (and /sign /register /check-in) boot
|
||
without it; only register-tenant/reissue need it, and they emit `download_url:''`
|
||
if it's missing. TTL = `claimCodeTtlSeconds` (link expiry aligned to the code).
|
||
- (2026-06-05) New routes: `/register-tenant` (service-authed) creates the registry
|
||
row, (paid) upserts the entitlement bound to the new tenant, mints the install
|
||
code carrying tenant_id (+ entitlement), presigns. `/install-codes/reissue`
|
||
resolves by tenant_id|email, `revokeClaimCodesForTenant`, re-attaches the active
|
||
entitlement via new `getActiveEntitlementByTenant`, mints a fresh code + link.
|
||
- (2026-06-05) **Validated:** tsc clean; jest **31/31** (added presign structural
|
||
test ×2 + getActiveEntitlementByTenant). Route HTTP behavior (T010/T018) is smoke.
|
||
- (2026-06-05) **DEPLOY FOLLOW-UP (relates to F041):** the alga-license Deployment
|
||
(service `k8s/deployment.yaml` + nm-kube-config) must get the `OBJECT_STORE_*`
|
||
env + `APPLIANCE_ISO_KEY`, and the appliance release process must publish the
|
||
current ISO to that key. Not wired here (ops); register-tenant returns an empty
|
||
download_url until it is.
|
||
|
||
## Build-step 4 — appliance setup integration (MAPPED; needs coordination, NOT yet built)
|
||
|
||
- (2026-06-05) The redeem belongs in the **host-service** `setup-engine.mjs` (has
|
||
the form inputs + network egress; mints the in-cluster secrets), not the in-app
|
||
`connectAppliance` (which runs post-tenant). Seam: after `normalizeInitialTenant`,
|
||
if an install code is present, POST it to `ALGA_LICENSE_SERVICE_URL/register` →
|
||
capture `tenant_id` + `edition` (+ paid `first_jwt`/`appliance_credential`/
|
||
`check_in_url`), then:
|
||
- add `INITIAL_TENANT_ID` to the `appliance-initial-tenant` Secret
|
||
(`initialTenantSecretYaml`, ~line 429) → create-tenant adopts it (build-step 2);
|
||
- feed the `appliance-license-seed` Secret (`licenseSeedCmd`, ~line 1014:
|
||
`EDITION_CHOICE` + optional `LICENSE_TOKEN`).
|
||
- (2026-06-05) **THREE reconciliation decisions block a clean edit** (all inside the
|
||
user's ACTIVE licval license-setup rework):
|
||
1. **UX:** the setup UI (`status-ui/app/setup/page.tsx`) already has
|
||
`editionChoice` (ee/ce) + a pasted `licenseKey`. Does the install code become
|
||
the primary path (overriding those), an added third option, or replace them?
|
||
2. **Taxonomy:** registry `edition = essentials|pro|premium` vs appliance
|
||
`editionChoice = ee|ce` + license tier `pro|premium`. Need a mapping
|
||
(essentials→ce? pro/premium→ee+token?).
|
||
3. **Connected seed:** `appliance_credential`/`check_in_url` are written today only
|
||
by in-app `connectAppliance`, not the seed Secret — install-time connected
|
||
licensing needs new seed plumbing in `packages/licensing/src/lib/license-state.ts`.
|
||
- (2026-06-05) `setup-engine.mjs` + `status-ui/` are NOT in the uncommitted WIP
|
||
(safe to edit git-wise) but are conceptually the licval subsystem. F053/F060–F068
|
||
pend on the three decisions above.
|
||
|
||
## Build-step 4 — appliance setup integration (BUILT in working tree, alga-psa)
|
||
|
||
- (2026-06-05) Redeem lives in the host-service. New `install-code.mjs`
|
||
(`redeemInstallCode` / `deriveApplianceId` / `licenseSeedFromRedeem`) is pure +
|
||
unit-tested (7 tests, mock fetch). `setup-engine.mjs` `validateSetupInputs` takes
|
||
`installCode`; `applyRuntimeValuesAndReleaseSelection` redeems it (injectable via
|
||
`options.redeemInstallCode` for tests) BEFORE building the two Secrets, threading
|
||
`tenant_id` → `initialTenantSecretYaml(initialTenant, tenantId)` (INITIAL_TENANT_ID
|
||
line) and the license fields → the `appliance-license-seed` literals
|
||
(`licenseSeedFromRedeem`). Redeem failure → `preflightFailure` blocks the install
|
||
(F067/F068). 2 new workflow tests; 8/8 pass.
|
||
- (2026-06-05) `appliance-bootstrap-configmap.yaml`: create-tenant gets
|
||
`INITIAL_TENANT_ID="${INITIAL_TENANT_ID:-}"` (create-tenant.ts reads it; empty =>
|
||
DB-generated). License-seed SQL extended: a `sql_value()` helper + new columns
|
||
`appliance_id`/`check_in_url`/`appliance_credential` (connected refresh), and the
|
||
trial is suppressed when `INSTALL_EDITION=essentials` (essentials resolves to the
|
||
essentials tier, not an auto premium trial).
|
||
- (2026-06-05) status-ui `setup/page.tsx`: an **Install code** field (primary,
|
||
recommended) above the manual Edition/license fallback; included in the POST.
|
||
Typechecks clean.
|
||
- (2026-06-05) **DEFAULTS I PICKED — confirm during smoke** (you authorized
|
||
sensible defaults + flag):
|
||
1. **Taxonomy:** the appliance always runs EE (`EDITION_CHOICE=ee`); essentials =
|
||
EE-unlicensed (no token, no auto-trial), pro/premium = EE + minted token. The
|
||
`ce` choice stays only for a manual community install (no install code).
|
||
2. **Connected seed:** `appliance_id`/`check_in_url`/`appliance_credential` are
|
||
seeded straight into `license_state` by the bootstrap SQL (vs. the in-app
|
||
`connectAppliance` plaintext write) so daily check-in works from first boot.
|
||
- (2026-06-05) **Two caveats:** (a) the install code is single-use and **consumed
|
||
at apply time** — a failed install past that point needs a re-issued code (the
|
||
reissue endpoint exists; consider persisting the redeem to skip re-redeem on
|
||
retry). (b) `ee/appliance/ubuntu-iso/overlay/.../host-service/` is a build-synced
|
||
COPY — the ISO build must pick up the new `install-code.mjs` + setup-engine edits
|
||
(overlay sync, not a manual edit).
|
||
|
||
## Build-step 5 — nm-store (separate repo /home/robert/nm-store; outward-facing)
|
||
|
||
- (2026-05-31/06-06) nm-store is local. Its existing order flow is **hosted-only**
|
||
(checkout → Temporal `OrderInstallWorkflow` → hosted tenant; no appliance/
|
||
deploymentType concept). Appliance registration is a **new outward-facing product
|
||
surface** there.
|
||
- (2026-06-06) **BUILT (branch `feat/appliance-registration-client`, commit
|
||
6ab0a427):** `src/lib/algaLicenseClient.ts` — typed `registerTenant()` +
|
||
`reissueInstallCode()` calling `/register-tenant` + `/install-codes/reissue`
|
||
(Bearer `ALGA_LICENSE_SERVICE_SECRET`, `ALGA_LICENSE_SERVICE_URL`). This is the
|
||
transport F071/F075 build on. Logic validated via a tsx harness (the repo's
|
||
vitest can't run here — missing `strip-literal` in node_modules under Node 24; the
|
||
`.test.ts` is correct and runs once that dep gap is fixed).
|
||
- (2026-06-06) **BUILT — commerce surface (decisions: SEPARATE route + essentials+paid),
|
||
branch `feat/appliance-registration-client` commit `ee53d0b0`, tsc clean:**
|
||
- `/order/appliance` registration form (client): essentials → direct register →
|
||
show code; paid (pro/premium) → embedded Stripe checkout. `/order/appliance/reissue`.
|
||
- `lib/appliance/applianceRegistration.ts`: `registerApplianceAndEmail`,
|
||
`reissueApplianceAndEmail`, `sendInstallCodeEmail` (Resend, reuses the
|
||
newsletter pattern), and **idempotent** `provisionApplianceFromCheckoutSession`
|
||
— nm-store has no DB, so it writes the minted `tenant_id`/`install_code` back
|
||
onto the **Stripe subscription metadata** and reuses them on thank-you refresh
|
||
(so a refresh never re-mints).
|
||
- `lib/appliance/applianceCheckout.ts`: embedded subscription checkout stamping
|
||
`deploymentType=appliance` + edition/contact into metadata, dedicated
|
||
`return_url=/order/appliance/thank-you`. **Hosted OrderForm/checkout/thank-you
|
||
untouched** (the separate-route choice).
|
||
- Checkout-complete is handled by the thank-you page (Stripe `return_url`), not a
|
||
webhook — so the appliance thank-you provisions on render.
|
||
- **Email is best-effort** (Resend): logs + returns `emailSent:false` if
|
||
`RESEND_API_KEY` unset; the code is always shown on-page too.
|
||
- (2026-06-06) **nm-store env to set for PAID:**
|
||
`STRIPE_PRICE_ID_APPLIANCE_{PRO,PREMIUM}_{MONTHLY,YEARLY}` (+ optional
|
||
`..._USER_MONTHLY/YEARLY` per-user), `ALGA_LICENSE_SERVICE_URL`,
|
||
`ALGA_LICENSE_SERVICE_SECRET`, `RESEND_API_KEY`/`APPLIANCE_FROM_EMAIL`. Essentials
|
||
needs no Stripe price. Tests can't run here (repo vitest `strip-literal` gap +
|
||
`server-only` blocks tsx) — validated by tsc + the tsx client harness + smoke.
|
||
- (2026-06-06) F076 operator doc written: `ee/appliance/docs/registration-install-flow.md`.
|
||
- (2026-06-06) **F041 remains ops:** publish the current generic ISO to the
|
||
object-store key the presigner targets (`APPLIANCE_ISO_KEY`), and wire the
|
||
`OBJECT_STORE_*` env onto the alga-license Deployment (service `k8s/deployment.yaml`
|
||
+ nm-kube-config).
|
||
|
||
## Commands / Runbooks
|
||
|
||
- (2026-06-05) **alga-license migration/db tests:** run against a throwaway
|
||
`docker run postgres:16` (snap docker can't reach /tmp; `docker run` is fine —
|
||
prior runs used port 5433). The least-priv app role can't `TRUNCATE` (by design)
|
||
— tests `DELETE`. DB-layer tests are gated on `DB_HOST` (skip without a DB).
|
||
- (2026-06-05) **Build gotcha:** the shell has `NODE_ENV=production`, so
|
||
`npm install` omits devDeps (breaks tsc/@types) — use `npm install --include=dev`.
|
||
- (2026-06-05) **alga-license validation one-liner** (throwaway pg):
|
||
`docker run -d --name algalic-pg -e POSTGRES_PASSWORD=test -e POSTGRES_DB=alga_license -p 5433:5432 postgres:16`
|
||
then `DB_HOST=localhost DB_PORT=5433 DB_NAME=alga_license DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=test npm run migrate`
|
||
and `DB_HOST=localhost DB_PORT=5433 DB_NAME=alga_license DB_USER_APP=postgres DB_PASSWORD_APP=test npm test`.
|
||
The signLicense tests need the alga-psa fixture key at
|
||
`packages/licensing/src/lib/__test-fixtures__/v1-test.private.pem` (present).
|
||
- (2026-06-05) **Appliance VM smoke:** the full register→download→install loop is
|
||
validated live on the libvirt appliance VM (see the appliance teardown/reinstall
|
||
+ VM ISO-test memories for driving setup via the browser / virsh).
|
||
|
||
## Links / References
|
||
|
||
- Design spec: `docs/superpowers/specs/2026-06-05-appliance-registration-install-flow-design.md`
|
||
- Registry foundation (already shipped): alga-license PR #3/#4, nm-kube-config PR #50/#51.
|
||
- alga-license routes: `src/routes/{register,claimCodes,checkIn,sign,revoke}.ts`;
|
||
db: `src/db/db.ts`; migrations: `migrations/0{1..4}_*.cjs`; types: `src/api-types.ts`.
|
||
- Appliance: `ee/server/src/lib/testing/tenant-creation.ts`,
|
||
`server/scripts/create-tenant.ts`,
|
||
`server/src/lib/actions/licenseManagementActions.ts`.
|
||
|
||
## Open Questions
|
||
|
||
- (2026-06-05) **All four §16 design questions resolved** — see the Decisions
|
||
section. No open design forks remain. Only impl detail left: the object-store key
|
||
for the current generic ISO + the release-process publish step (F041).
|