Skip to content

PointSav Documentation

The engineering library for the PointSav platform — operating systems and services for regulated businesses that own their data, their AI, and their record-keeping outright. Where the monorepo holds the code, this wiki holds the reasoning: architecture, services, security, and the governance commitments that bind future development.

Configure a tenant namespace

← All revisions

eb708994 · PointSav Digital Systems ·

fix(how-to): Multi-entity scale + Integration & data groups rewritten against real source (Groups 5-6, Phase 1 complete)

View the full record as of this revision →

@@ -1,118 +1,94 @@
---
schema: foundry-doc-v1
title: "How to configure a tenant namespace"
title: "Configure a tenant namespace"
slug: configure-tenant-namespace
short_description: "Configures an isolated tenant namespace: registers the tenant, sets quota limits, issues the root capability token, and verifies isolation and quota enforcement."
short_description: "Configures a tenant namespace on service-vm-tenant via environment variables and a restart — the real config-driven mechanism, since no runtime tenant-registration API exists."
category: how-to
content_type: how-to
type: how-to
quality: complete
status: active
last_edited: 2026-07-18
audience: "Engineers (hands on keyboard); customer operators"
last_edited: 2026-08-06
editor: pointsav-engineering
paired_with: configure-tenant-namespace.es.md
research_trail:
  sources: [pointsav-monorepo service-vm-tenant/src/main.rs, service-vm-tenant/src/tenant.rs, service-vm-tenant/src/quota.rs, infrastructure/virt/local-vm-tenant.service]
  verification_method: "independently source-verified against pointsav-monorepo on 2026-08-06 by reading the Rust source directly, with file:line citations recorded per claim; this guide's own prior Major Correction note (2026-07-18) had already confirmed no /v1/tenants API exists, but flagged the actual provisioning mechanism as needing further investigation before rewriting — this pass completes that investigation"
---

A tenant namespace is the isolated partition within the platform that holds a single customer's resources — fleet nodes, sessions, audit records, and capability tokens. Configuring a tenant namespace means registering the tenant with the service layer, defining quota limits, and verifying that isolation is enforced. This guide covers initial namespace configuration for a single tenant.

For the service that enforces tenant isolation, see [[service-vm-tenant]]. For the fleet architecture that namespaces span, see [[ppn-small-business-compute]].

**Major correction (2026-07-18):** the API surface this guide describes does not exist in
the live `service-vm-tenant` source. Direct verification of the route table
(`service-vm-tenant/src/main.rs`) finds exactly four routes: `GET /healthz`, `POST /v1/vms`
(create), `GET /v1/vms` (list), `DELETE /v1/vms/:vm_id`, and `GET /v1/status` — **no
`/v1/tenants` endpoint of any kind.** Steps 1 and 2 below (`POST /v1/tenants` to register a
tenant, `POST /v1/tenants/acme-corp/tokens` to issue a root capability token) describe
routes that are not in the service. Tenant registration is not an API call at all in the
live source — it is **config-driven at service startup** via a `TENANT_IDS` environment
variable (the service logs "`TENANT_IDS not set — no tenants registered; all requests will
401`" when it is absent), not a runtime "register a tenant" operation an administrator
performs after the service is already up. Step 3's `GET /v1/vms` isolation check and Step
4's quota-exceeded check are plausible against the real `/v1/vms` route, but have not been
individually re-verified here. **Flagged, not silently rewritten** — this reads as either
an early design that was replaced by simpler env-var-driven tenant provisioning, or a
guide written ahead of the service it describes; needs project-totebox confirmation of the
actual tenant-provisioning mechanism before Steps 1–2 are corrected or removed.

## Prerequisites

- Administrator access to `service-vm-tenant` (the tenant proxy service, port 9221)
- A tenant ID: a stable, lowercase ASCII string that identifies the customer (e.g., `acme-corp`)
- Quota values agreed with the tenant: maximum concurrent VMs, storage quota, API request rate
- Administrator access to the machine running `service-vm-tenant` (default port 9221)
- A tenant ID: a stable, lowercase ASCII string identifying the customer (e.g., `acme-corp`)
- Quota values agreed with the tenant: maximum concurrent VMs, maximum RAM

## Step 1: Register the tenant
## Purpose

Create the tenant record by posting to the tenant service:
Add a tenant namespace to `service-vm-tenant` the way the service actually supports today — editing its environment configuration and restarting it. There is no runtime registration API; provisioning is config-driven.

```
curl -X POST http://127.0.0.1:9221/v1/tenants \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "display_name": "Acme Corporation",
    "max_vms": 10,
    "storage_quota_gb": 100,
    "api_rate_limit_rpm": 600
  }'
```
## Procedure

A successful response returns the tenant record with a `created_at` timestamp. The tenant ID is immutable after creation — choose it carefully. The WORM ledger records the creation event automatically.
1. Add the tenant to the allowlist. `TENANT_IDS` is a bare comma-separated list of tenant IDs — it carries no quota data itself:

## Step 2: Issue the tenant's root capability token
   ```
   TENANT_IDS=acme-corp,existing-tenant
   ```

After the namespace exists, issue a root capability token for the tenant's first administrator session. This token uses scope `service:tenant-admin` and is scoped to the new `tenant_id`:
2. Set the new tenant's quotas as separate, per-tenant environment variables, named by uppercasing the tenant ID:

```
curl -X POST http://127.0.0.1:9221/v1/tenants/acme-corp/tokens \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_pubkey": "<tenant-admin-pubkey>",
    "scope": "service:tenant-admin",
    "expires_in_seconds": 86400
  }'
```
   ```
   TENANT_ACME_CORP_MAX_VMS=10
   TENANT_ACME_CORP_MAX_RAM_MB=16384
   ```

Deliver this token to the tenant's designated administrator. They use it to issue sub-tokens and enroll their own devices.
   Both are optional — if omitted, they default to 5 VMs and 8192 MB.

## Step 3: Verify namespace isolation
3. Set an authentication token for the tenant. `service-vm-tenant` uses a plain Bearer token, not a signed capability token:

Confirm the tenant namespace is isolated from other tenants by querying the fleet under the new tenant's credentials:
   ```
   TOKEN_MAP=<a-generated-token>:acme-corp
   ```

```
curl -s "http://127.0.0.1:9221/v1/vms" \
  -H "Authorization: Bearer <tenant-admin-token>"
```
   > **Warning:** if `TOKEN_MAP` is left unset entirely, the service falls back to an explicitly-logged **insecure mode** where the bearer token literally *is* the tenant ID (`Authorization: Bearer acme-corp` authenticates as that tenant, no secret required). Set `TOKEN_MAP` for anything beyond local testing.

The response must contain only VMs belonging to `acme-corp`. If VMs from other tenants appear, namespace enforcement has a defect — do not proceed; review the `service-vm-tenant` configuration.
4. Restart `service-vm-tenant` to load the new configuration. There is no hot-reload, no admin endpoint, and no signal-based config refresh — `TENANT_IDS` and the per-tenant variables are read exactly once, at process startup.

## Step 4: Verify quota enforcement
## Expected outcome

Submit a request that would exceed the configured quota to confirm enforcement is active. For example, if `max_vms` is 10, attempt to register an 11th VM. The service must return a `429 Too Many Requests` response with a quota-exceeded error body.
`service-vm-tenant` recognizes requests bearing the new tenant's token, scopes every response to that tenant's own VMs automatically, and enforces the quotas you set.

Quota enforcement failure is a billing and fairness defect — resolve it before the tenant begins production use.
## Verification

## Step 5: Confirm the WORM ledger entry

The tenant creation event is permanently recorded. Verify the entry exists:
Confirm the tenant is recognized and see its current usage in one call:

```bash
curl -s http://127.0.0.1:9221/v1/status \
  -H "Authorization: Bearer <acme-corp-token>"
```
service-fs read --filter tenant-id=acme-corp --event-type TENANT_CREATED --limit 1

This returns `tenant_id`, `vms_running`, `ram_used_mb`, `max_vms`, and `max_ram_mb` — a real, working quota-usage endpoint.

Confirm isolation by listing VMs — there is no client-supplied tenant filter; the server scopes results to whichever tenant the Bearer token authenticates as:

```bash
curl -s http://127.0.0.1:9221/v1/vms \
  -H "Authorization: Bearer <acme-corp-token>"
```

The entry confirms when the namespace was created and under which administrator token. This record cannot be altered.
Confirm quota enforcement by attempting to exceed `max_vms` or `max_ram_mb` via `POST /v1/vms`. Both limits are enforced synchronously, before the request reaches the fleet controller, and return `429 Too Many Requests` with a plain-text body describing the limit.

## Rollback

Remove the tenant's ID from `TENANT_IDS` (and its `TOKEN_MAP` entry, if set) and restart the service. Existing VMs the tenant owns are not automatically destroyed — deallocate them explicitly first via `DELETE /v1/vms/:vm_id` if that's the intent, since a removed tenant simply loses the ability to authenticate, not its running resources.

## Key takeaways
## Next steps

- Tenant IDs are immutable after creation — choose descriptive, stable identifiers
- Every tenant namespace has independent quota limits; configure them before the tenant starts enrolling nodes
- Namespace isolation must be verified before handing off credentials to the tenant
- A WORM ledger entry is created for every namespace lifecycle event (creation, quota change, deletion)
- [[issue-capability-token]] — a related but distinct credential system, for service-to-service authentication rather than tenant-scoped VM access
- [[add-a-fleet-node]] — add compute capacity for tenants to place VMs on

## See also

- [[service-vm-tenant]] — the tenant proxy service that enforces namespace boundaries
- [[ppn-small-business-compute]] — the compute fleet architecture that tenant namespaces partition
- [[issue-capability-token]] — how to issue capability tokens once the namespace exists
- [[scale-user-tiers]] — adjusting per-user access tiers within an established namespace
- [[add-a-fleet-node]] — enrolling the tenant's first compute node after namespace setup
- [[scale-user-tiers]] — a separate, unrelated access-tier system for individual users within an archive
Important Information

Corporate structure. PointSav Digital Systems ("PointSav") is currently a trade name of Woodfine Capital Projects Inc. ("Woodfine"), planned to become a wholly-owned Woodfine subsidiary upon incorporation. PointSav does not itself offer, sell, or solicit any security. Any securities offering associated with Woodfine's real-property direct-hold solutions is made exclusively by Woodfine, and only by means of the applicable Private Placement Memorandum.

No investment advice. This wiki's content is provided for engineering, operational, research, and development purposes. Nothing on this wiki constitutes investment advice or a solicitation to invest in any Woodfine partnership or direct-hold solution.

Intellectual property. The PointSav name, trade name, wordmark, and marks, together with all current and future PointSav- and Totebox-branded products, services, and offerings — and the software, source code, documentation, design system, and all related materials — are proprietary to Woodfine and its affiliates, except for components identified as open source. No rights are granted except as expressly set out in a written license or agreement. The full trademark notice appears in the footer of every page on this site.

Open source components. Portions of the platform are made available under permissive open-source licenses identified in the accompanying repository. Use of those components is governed by their respective license terms.

No warranty; informational use. Content on this wiki is provided for general informational purposes only and does not constitute a representation, warranty, or commitment with respect to product functionality, availability, pricing, or roadmap. Some articles describe planned or intended features, capabilities, and milestones — language such as "planned," "intended," "targeted," "may," and "expected" marks this forward-looking content, which is subject to change and does not constitute a commitment regarding future performance.

Confidentiality. Where an article describes an operational or deployment detail that is not intended for public disclosure, that article is not published on this wiki. Content here is general-purpose engineering documentation, not customer-specific configuration.

Jurisdiction. Woodfine Capital Projects Inc. is organized in British Columbia, Canada. References to the Sovereign Data Foundation on this wiki describe a planned or intended initiative only, not a current equity holder or active governance body.

Changes to this notice. PointSav may update this notice from time to time; the version posted on this page governs.

Not a filing system. This wiki is not a securities filing system, an electronic disclosure repository, or a substitute for SEDAR+ or any other regulatory filing system. Formal securities filings are made through the applicable regulatory filing system, not through this wiki.

Full disclaimer. This notice supplements, and does not replace, the full Disclaimers article. In the event of any conflict, the full Disclaimers article governs.

Read the full disclaimer →