Skip to content

Guide: tenant provisioning

Status: capability-state mix. Tenant create, company bootstrap, and onboarding gate are staging-durable. Key-custody attestation and rotation rehearsal walk end-to-end as fixture / staging records. Production admission precheck and policy admit the records but every gate refuses production admission. Real HSM/KMS provider attestation and real production tenant onboarding are not yet substantive — see 022 gap report items 1, 2, and 6.

This guide walks the path of provisioning a new tenant from “nothing” up to “ready to admit work.” Every gate exists to refuse, not to grant.

A tenant is the legal container under which company truth is admitted, audited, and preserved. Provisioning has three moving parts: identity custody (the keys that sign atoms, and the attestation those keys live in a real HSM/KMS); company bootstrap (the registered legal entity); and production admission (the explicit, multi-evidence, signature-gated decision to let real customer data cross the membrane). Every fixture endpoint refuses production admission — the shape of the gates is fully expressible today; the substance behind them is M22+ work.

  1. client.keyCustodyReadiness — survey what’s already in place.
  2. client.keyCustodyAttest — admit a key-custody attestation.
  3. client.keyCustodyProviderAttest — bind the key to a named HSM/KMS provider.
  4. client.tenantCreate — create the tenant atom.
  5. client.companyBootstrap — bind the legal entity.
  6. client.tenantOnboardingGate — fold the receipts together.
  7. client.productionAdmissionPrecheck — gather the evidence bundle.
  8. client.productionAdmissionPolicy — declare the policy. (Will refuse production_admission_enabled: true against a fixture posture.)

Survey, attest, and bind to a provider. The keyCustodyReadiness GET surveys the tenant’s custody posture; the Koerper should render its body as a checklist banner before any provisioning attempt:

const tenant = "tenant_node:rheinwerk_calibration";
const readiness = await client.keyCustodyReadiness();
// body.productionKmsHsmReady, .tenantKeyCustodyProviderConfigured,
// .privateKeyMaterialExposed — all false on fixture
const attestation = await client.keyCustodyAttest({
tenant,
company: "company_geist:rheinwerk_calibration",
attestation_ref: "key_custody_attestation:rheinwerk_2026_04",
mode: "fixture_staging",
provider: "fixture_provider",
key_ref: "signing_key:rheinwerk_root",
public_key: "ed25519:fixture_public_key_base64",
attestation_hash: "sha256:fixture_attestation_hash",
fixture: true,
production_requested: false,
});
const provider = await client.keyCustodyProviderAttest({
tenant,
company: "company_geist:rheinwerk_calibration",
provider_attestation_ref: "key_custody_provider_attestation:rheinwerk_aws_kms",
mode: "fixture_staging",
provider: "aws_kms",
key_ref: "signing_key:rheinwerk_root",
public_key: "ed25519:fixture_public_key_base64",
public_key_hash: "sha256:fixture_public_key_hash",
attestation_hash: "sha256:fixture_provider_attestation_hash",
fixture: true,
});

Setting production_requested: true against a fixture provider refuses with key_custody_production_admission_blocked. Setting private_key_pem or private_key_material on any call in this family refuses unconditionally. The provider field is enum-checked but the actual KMS attestation document is not validated today; that validation is the M22+ substance work.

Steps 4-6 — tenant, company, onboarding gate

Section titled “Steps 4-6 — tenant, company, onboarding gate”
const created = await client.tenantCreate({
tenant,
requested_tenant: tenant,
host: "rheinwerk.gestalt.local",
company: "company_geist:rheinwerk_calibration",
authority_epoch: "authority_epoch:rheinwerk_root",
home_reality: "reality:record",
signing_key: "signing_key:rheinwerk_root",
encryption_key: "encryption_key:rheinwerk_root",
auth_policy: "auth_policy:default",
key_custody_attestation: attestation.body.keyCustodyAttestation,
fixture: true,
});
const bootstrap = await client.companyBootstrap({
tenant,
company: "company_geist:rheinwerk_calibration",
legal_name_hash: "sha256:rheinwerk_calibration_gmbh",
legal_form: "GmbH",
jurisdiction: "DE-BE",
register_number_hash: "sha256:hrb_fixture",
verification_evidence: ["evidence_bundle:rheinwerk_handelsregister"],
fixture: true,
});
const gate = await client.tenantOnboardingGate({
tenant,
tenant_onboarding: "tenant_onboarding:rheinwerk_2026_04",
company_bootstrap: bootstrap.body.companyBootstrap,
key_custody_attestation: attestation.body.keyCustodyAttestation,
fixture: true,
});

Without the attestation, tenantCreate refuses with tenant_create_key_custody_missing. legal_name_hash and register_number_hash are content commitments — raw legal_name is accepted on fixture but refused against production posture without contains_customer_data. The onboarding gate is the “do these receipts compose?” check; missing any input refuses with the corresponding tenant_onboarding_* reason.

const precheck = await client.productionAdmissionPrecheck({
tenant,
company: "company_geist:rheinwerk_calibration",
precheck_ref: "production_admission_precheck:rheinwerk_2026_04",
key_custody_attestation: attestation.body.keyCustodyAttestation,
edge_access_policy: "edge_access_policy:rheinwerk_default",
authority_package: "authority_package:de_vat_2026",
proof_issuer: "proof_issuer:rheinwerk_default",
backup_restore_receipt: "ops_restore_rehearsal:rheinwerk_2026_04",
hostile_suite_receipt: "staging_maturity_report:rheinwerk_2026_04",
request_production: false,
});

request_production: true against fixture posture refuses with production_admission_precheck_fixture_evidence.

const policy = await client.productionAdmissionPolicy({
tenant,
policy_ref: "production_admission_policy:rheinwerk_2026_04",
jurisdiction: "DE-BE",
vertical: "small_business_invoicing",
tenant_type: "operator",
admitted_connectors: ["bank_observation", "advisor_review"],
admitted_authority_packages: ["authority_package:de_vat_2026"],
admitted_effects: ["send_invoice_email"],
forbidden_data_classes: ["raw_biometric_material"],
admission_signer: "human_person:gestalt_root_signer",
admission_signer_commitment: "sha256:fixture_admission_signature",
production_admission_enabled: false,
});

production_admission_enabled: true refuses against fixture posture. The runtime will not honor production admission until M22+ ships the substantive overlay.

Provisioning is not done when the tenant is created. The four rehearsal endpoints — keyCustodyRotationRehearse, keyCustodySigningRehearse, keyCustodyRevoke, keyCustodyBreakGlass — each admit an audit-citable record of the custody story. The signing rehearsal admits the intent to sign against a fixture payload (production_payload: false); the break-glass call requires both a human_presence_receipt for the approver and an opaque justification_hash.

await client.keyCustodyRotationRehearse({
tenant,
company: "company_geist:rheinwerk_calibration",
provider_attestation: provider.body.providerAttestation,
key_ref: "signing_key:rheinwerk_root",
replacement_key_ref: "signing_key:rheinwerk_2026_04",
replacement_public_key: "ed25519:fixture_replacement_public_key",
rotation_reason: "scheduled_quarterly_rotation",
fixture: true,
});
await client.keyCustodyBreakGlass({
tenant,
company: "company_geist:rheinwerk_calibration",
provider_attestation: provider.body.providerAttestation,
key_ref: "signing_key:rheinwerk_root",
operator: "human_person:gestalt_oncall",
justification_hash: "sha256:fixture_break_glass_justification",
approver_receipt: "human_presence_receipt:fixture_break_glass_presence",
fixture: true,
});

Before requesting production admission, fold the rehearsal record into a maturity report (trial count, pass count, exposure invariants) and a pilot gate (consent, data-boundary, support runbook, presence):

await client.stagingMaturityReport({
tenant,
report_ref: "staging_maturity_report:rheinwerk_2026_04",
run_slug: "rheinwerk-2026-04",
report_commitment: "sha256:fixture_maturity_report",
required_trials: ["invoice_payment_advisor", "human_auth", "key_custody"],
trial_count: 3,
pass_count: 3,
proof_verification_count: 3,
proof_verification_pass_count: 3,
gate_state: "ready",
production_admission_enabled: false,
raw_biometric_material_exposed: false,
private_key_material_exposed: false,
});
await client.pilotAdmissionGate({
tenant,
company: "company_geist:rheinwerk_calibration",
pilot_ref: "pilot:rheinwerk_2026_04",
customer_consent_hash: "sha256:fixture_pilot_consent",
data_boundary_hash: "sha256:fixture_pilot_data_boundary",
support_runbook_hash: "sha256:fixture_pilot_support_runbook",
production_precheck: precheck.body.productionAdmissionPrecheck,
tenant_onboarding_gate: gate.body.tenantOnboardingGate,
human_presence_receipt: "human_presence_receipt:fixture_pilot_presence",
no_public_launch_claim: true,
customer_data_live: false,
});
  • Every gate is structurally a record. Missing any input refuses with a structured reason a Koerper can render.
  • Custody, identity, and production admission are three separate decisions, not one — each has its own receipt and can be audited independently.
  • The maturity report and pilot gate make “we are ready” a signed, citable atom rather than a slack message.
  • Real HSM/KMS provider attestation. keyCustodyProviderAttest walks the shape but does not cryptographically validate a real KMS attestation document. The provider enum is honored; the underlying attestation chain is fixture.
  • Real tenant production onboarding. Every gate refuses production admission. production_admission_enabled: true, production_requested: true, request_production: true all refuse. No tenant in fixture-preview has production substance.
  • Real signing rehearsals. keyCustodySigningRehearse admits the intent to sign; it does not actually exercise the KMS signing path against a real payload.

See 022 items 1, 2, and 6.