A ctlplne studio product

Plan and license

trstctl is a source-available Machine Identity Security Control Plane: the core is BUSL-1.1, converting to MPL-2.0 four years after each release, and the commercial editions are proprietary. The product line keeps core credential issuance, enrollment, rotation primitives, protocol interoperability, audit/export, PostgreSQL RLS tenant isolation, and the offline license verifier in Free. Enterprise adds the commercial ee/ feature set. Provider / MSP includes every Enterprise feature and adds provider-plane operations plus managed-service and resale rights.

Commercial posture

Free has no license bill: the BUSL-1.1 core runs in production without a signed license. Enterprise and Provider / MSP are commercial editions activated by an offline Ed25519-signed license; their commercial terms are not published yet and are agreed per customer. A Provider license is scoped by a managed-customer band, and the MSP controls its own downstream hosting, support, and terms. Certificates, SVIDs, secrets, API keys, tokens, rotations, nodes, discovery findings, and audit events are never automatic billing units: there is no per-connector, no per-protocol, and no per-certificate or per-ephemeral-identity meter. Every Enterprise or Provider license names 1 production deployment and bundles 3 non-production deployments with the full licensed feature set and no production SLA; see Signed deployment environment entitlement.

Grace and expiry

At expiry, the offline verifier provides a 30-day grace period. During grace, licensed modes remain enabled. After grace, the license state and commercial feature modes become read_only; core issuance, renewal, revocation, protocol, audit/export, tenancy, and license-verification capabilities remain Community software. The core keeps running: a lapsed commercial order never bricks a customer's PKI. Commercial mutation surfaces honor the served read_only mode, and the editions page makes that state visible before an operator acts. GET /api/v1/editions and Platform → Editions expose the effective tier, the expiry/read-only horizon, the signed deployment environment, production-unit consumption, remaining non-production slots, and packaging rules; internal/license owns the one feature-to-tier table and the one bundled allowance constant, and the API and docs are guarded against drift from them.

Console journey

Open Plan and license at /admin/editions to answer two questions first: which signed features this deployment may use, and when that permission ends. The opening card shows the current plan, enabled-feature count, expiry, and a plain-language state such as Community, Active, Grace, or Read-only. The page makes only the Editions API read needed for that answer.

The deeper proof remains available without putting it in the operator's way:

  1. Signature verification explains whether the offline Ed25519 signature verified and when the deployment becomes read-only.
  2. Feature table maps each exact feature ID to its required plan and current mode.
  3. Entitlement evidence shows the customer, deployment binding, environment, use rights, FIPS posture, and optional packaging evidence. Deployment distribution and active-active issuance evidence are fetched only after the nested architecture disclosure is opened.

Add license is an operator guide, not a browser upload. A private license is installed as an operator-controlled 0600 file, bound to the signed deployment ID and environment, then supplied to both the control plane and isolated signer at startup. Restart those two processes together. This avoids browser custody of the license and prevents the control plane and signer from temporarily applying different rights. Read or verification failures are shown with sanitized, fail-closed language and a safe retry; raw server errors are not displayed.

Where the trusted vendor key comes from

A license verifies only against the vendor's license signing public keys baked into the binaries at build time. Native builds bake them with make LICENSE_KEYS_B64=$(base64 -w0 license-signing.pub); container images bake them with the LICENSE_KEYS_B64 build argument of deploy/docker/Dockerfile (the release pipeline passes the repository variable TRSTCTL_LICENSE_KEYS_B64). Only public keys are ever baked. A build without a baked key bakes no trust: no license file can verify and the deployment stays Community, which is the fail-closed default for local builds. The local click-through demo is the one exception: its image target mints a throw-away key and license inside the build and deletes the signing material, so a demo license never verifies on any other build.

Containers and the partner lab

In containers the license is a read-only mounted file owned by the operator, never an image layer. The partner lab ships this path as an opt-in profile:

TRSTCTL_LAB_LICENSE_FILE=/secure/acme-license.json \
TRSTCTL_LAB_LICENSE_KEYS_B64="$(base64 -w0 vendor-ed25519.pub)" \
TRSTCTL_LAB_LICENSE_DEPLOYMENT_ID=acme-lab \
TRSTCTL_LAB_LICENSE_ENVIRONMENT=non_production \
deploy/demo/lab/run.sh

run.sh refuses a license file that is group- or world-readable, builds the clean release image target with the vendor key baked in, copies the file once into the run-owned runtime volume for the service user, and starts the control plane and the isolated signer with the same bound deployment ID and environment. The deployment ID must be one the license signed for that environment; a copied file with another binding fails startup. The same profile pins the lab's local identity provider for provider-operator sign-in (see Provider operator delegation and the provider journey).

For a license renewal in an existing partner lab, point TRSTCTL_LAB_LICENSE_FILE at the new 0600 file and run the licensed run.sh command again. The lab initializer copies that file into the shared runtime volume before the signer and control plane restart. Restarting only those two services leaves the earlier copied license in place; check Plan and license after the restart for active and the expected deployment ID.

Buyer Matrix

Packaging line Free Enterprise Provider / MSP
Buyer Organization operating trstctl for itself Organization needing the commercial feature set MSP operating or reselling trstctl-backed services to customers
Primary billing unit None Per control-plane deployment Negotiated managed-customer band
Core protocols ACME, EST, SCEP, CMP, SPIFFE, SSH CA, TSA Included Included
Tenant isolation PostgreSQL RLS and event spine Included Included; shared multi-tenant control plane is the normal shape
Patent-pending families and PQC Included: PCAS, agent delegation, reconciliation, VDEC, and post-quantum cryptography attach in every build Included Included
Remediation Incident response and guided remediation, subject to authorization and configured targets Included Included
Enterprise features Not included FIPS artifact posture, HA support, BYOK, governance, tenant SAML/LDAP/SCIM (Enterprise SSO), and audit anchoring/retention/compliance packaging All Enterprise features
Provider operations Not included Not included Provider plane, metering, white label, and siloed isolation
Product motion and commercial ee/ rights Self-hosted core under BUSL-1.1 Self-hosted commercial feature set Self-host, managed service, and resale of the commercial feature set
Deployment flexibility Customer operated Customer operated Shared control plane or dedicated customer deployments
Commercial terms No license fee Not yet published Not yet published
Environment entitlement Community deployments are unmetered 1 production + 3 signed non-production deployment slots Same bundle per licensed Provider control plane

The same binary lineage serves all three tiers. The offline signed tier drives both feature inheritance and use rights. Core multi-tenancy, audit/export, crypto, and the license verifier remain in core.

Signed deployment environment entitlement

New licenses use claim version 2. Their signed environment_entitlement object contains production_deployment_id, up to three non_production_deployment_ids, and non_production_allowance: 3. The vendor helper refuses duplicate IDs, a production ID repeated as non-production, an unsafe ID, or more registered non-production IDs than the allowance.

The offline vendor helper creates that bundle explicitly:

trstctl-license sign \
  --private-key vendor-ed25519.key \
  --out acme-license.json \
  --id lic-acme-2027 --customer "Acme Corp" --tier enterprise \
  --production-deployment-id acme-prod \
  --non-production-deployment-ids acme-stage,acme-dev,acme-test \
  --expires-at 2027-08-13T00:00:00Z

The running binary binds those claims to operator-owned configuration:

TRSTCTL_LICENSE_FILE=/etc/trstctl/license.json \
TRSTCTL_LICENSE_DEPLOYMENT_ID=acme-stage \
TRSTCTL_LICENSE_ENVIRONMENT=non_production \
trstctl

Both runtime values are required together for v2. production must match the one signed production ID; non_production must match one signed non-production ID. A copied file with a changed or misclassified ID fails startup. The Editions API exposes deployment_entitlement.environment, deployment_id, production_units_consumed, registered and remaining non-production slots, and the legacy marker. A non-production deployment therefore provides a machine-readable zero production-unit result rather than relying on sales prose.

Version 1 files remain readable for upgrade continuity, but they are explicitly production-only and legacy_unbound: true; a v1 file cannot activate the bundled non-production right. This compatibility rule avoids inventing an entitlement that was never signed.

The supervised signer child receives the same deployment ID and environment as the control plane and independently applies the same bound loader. An operator starting trstctl-signer as a separate process must pass --license-deployment-id and --license-environment beside --license; a v2 file without that pair fails closed before the signing service listens.

Core Protocols

These protocol surfaces are Free/Community capabilities. They are not Enterprise-only features in internal/license.

Capability Edition Notes
ACME Community ACME server, account/order flow, ARI, and DNS-validation framework.
EST Community RFC 7030 enrollment endpoint.
SCEP Community RFC 8894 enrollment endpoint.
CMP Community RFC 4210 / CMPv3 enrollment endpoint.
SPIFFE Workload API Community X.509 and JWT SVID workload identity surface.
SSH CA Community SSH certificate authority endpoints and KRL publication.
TSA Community Timestamping authority surface.

License-Gated Features

This table mirrors internal/license exactly. A feature absent from this table is Free/Community by default unless a signed license explicitly grants it as an extra. Provider inherits every Enterprise row below, then adds the Provider rows. PCAS, agent delegation, reconciliation, verifiable decommissioning, PQC and remediation are core capabilities and require no commercial feature grant. The fips row is a commercial assurance/distribution classification, not a runtime switch: a FIPS-capable artifact is built with make fips-build, and startup checks its module and self-test. The running module status appears separately in GET /api/v1/editions under fips; the license feature row does not decide whether the module runs. The console excludes fips from its count of runtime licensed features.

Feature ID Edition Product line
fips Enterprise Assurance: FIPS-capable distribution posture and evidence.
ha_support Enterprise Scale: served enterprise support posture, SLA target catalog, 24x7 production tier, and professional-services packages.
byok Enterprise Assurance: bring-your-own-key / external custody operations.
governance Enterprise Governance: advanced approvals, policy, and audit controls.
enterprise_sso Enterprise Tenant SAML and LDAP sign-in, plus tenant SCIM provisioning. OIDC remains core.
audit_compliance Enterprise Audit timestamp anchoring, retention, and compliance packaging. Plain signed history export stays core.
provider_plane Provider Managed-provider control plane features, gated by per-customer delegation (see below).
metering Provider Provider usage metering, durable per-customer, pullable as invoice evidence (see below).
white_label Provider Provider branding controls.
siloed_isolation Provider Provider tenant-silo operating mode.

Provider operator delegation

The provider plane has two independent gates, and both fail closed.

Authentication answers who an operator is. With no Authenticator configured, /provider/ refuses every request; there is deliberately no placeholder verifier.

Delegation answers which customers that operator may touch, and through which operations. Grants live in provider_operator_delegations and are read on every customer-scoped action:

  • An operator acts only on customers explicitly granted to them. Naming any other customer is refused, and the refusal happens before the store is written — a suspended customer and an audited refusal must never disagree.
  • Operations are granted separately (read, provision, suspend, resume, offboard, break-glass) because they carry different blast radii. Suspending interrupts a live service and is reversible; offboarding destroys. Being trusted with one is not being trusted with the other.
  • There is no wildcard customer. A wildcard grant is indistinguishable from the unscoped access this replaces.
  • The tenant list returns only delegated customers. The full roster is the provider's commercial information, and it is the map an operator would need to attempt a cross-customer action.
  • Break-glass is re-checked when the grant is USED, not only when it was requested, so revoking a delegation stops access an operator already holds.
  • No delegation source, or a delegation source that cannot be read, refuses everything. Failing open on a read error would make the plane widest exactly when it is least healthy.

Grants are minted with a local subcommand against PostgreSQL and the event log. Name the customer by the slug you will provision (its tenant id is derived from the slug, so a customer can be delegated before it exists; the command prints the derived id) or by an existing tenant id. Every invocation needs a stable idempotency key; an identical retry returns the canonical authority event, while reusing the key for a changed grant is refused. The same rule holds for every provider mutation over the API: a key binds its first answer, a refusal included, and a replayed answer carries the Idempotent-Replayed: true header. After you fix the cause of a refusal, retry with a new key:

trstctl provider-grant -operator op-1 -customer acme \
  -operations read,provision,suspend -granted-by platform-admin \
  -idempotency-key acme-op-1-read-provision-suspend-v1
# time-boxed: the grant stops authorizing after -expires-at (RFC3339)
trstctl provider-grant -operator op-1 -customer acme \
  -operations read -granted-by platform-admin \
  -expires-at 2027-01-01T00:00:00Z \
  -idempotency-key acme-op-1-read-until-2027-v1
trstctl provider-grant -operator op-1 -customer acme \
  -operations offboard -revoke -granted-by platform-admin \
  -idempotency-key acme-op-1-offboard-revoke-v1

The Provider console displays the current operator ID next to its provision form. A provision grant for one existing customer can make that form visible, but it does not authorize a different new slug. A host administrator must grant that exact future slug to the displayed operator with provision (and read to see it afterward) before the operator submits the form. A 403 keeps the form values and shows this recovery step; it does not create the customer.

Offboarding a customer clears every grant over it. Tenant ids are derived from the customer slug, so a grant that outlived the tenancy would hand a reused slug to whoever held access on the old customer. This applies to database-backed grants; a deployment still keeping grants in a static configuration file must remove those lines itself, since rewriting an operator's config file from the running process would put the file out of step with the system it describes. Before a customer workspace is initialized, offboarding removes Provider metadata without inventing a core tenant deletion. Completed HTTP retry receipts remain until their normal expiry so an old key keeps its first result; an unfinished receipt or any workload row stops this metadata-only path. Once the workspace exists, offboarding uses the full tenant erasure flow.

This is a local command rather than a served route because of the bootstrap problem: a route that hands out provider authority must itself be authorized by somebody holding provider authority, and at install time no such operator exists. The command opens the configured PostgreSQL and JetStream stores, bootstraps any pre-event delegation rows exactly once, appends one immutable provider.delegation.granted or provider.delegation.revoked event, and lets the provider authority projection update the delegation view. It never writes the table directly. When embedded NATS is configured, run the command while the control plane is offline so two processes do not open the same file-backed store.

Provider OIDC bearer verification uses pinned JWKS, issuer, audience, expiry, not-before, role, and MFA claims. Provider SAML is a separate SP at /provider/v1/auth/saml/{login,acs,metadata}: the crypto boundary validates the signed assertion, issuer, audience/recipient, time window, request correlation, role, and MFA attributes before minting a Provider-only HttpOnly session. That cookie never becomes a customer-tenant session; state-changing requests also need its double-submit X-Provider-CSRF-Token value.

Provider SCIM is served at /provider/scim/v2. Token bytes are read from custody-checked files, hashed, wiped, and never retained as strings. User join/update/active:false/delete emits immutable operator lifecycle events. When SCIM is enabled, OIDC and SAML re-read that projected operator row on every request. A still-valid token or browser session therefore stops at the first request after a leaver event; the leaver projection also revokes every standing customer grant for that operator.

Provider administrators with current MFA use GET /provider/v1/operators, GET /provider/v1/access/customers, and the per-operator /delegations, /revocations, and /role mutation routes. The console shows the operator, SCIM source, role, exact customer/operation, grant source, expiry, last use, and retained revocation evidence. These routes return each grant episode with its immutable grant_event_id; regranting an exact customer/operation creates a new episode while the previous revoked episode remains readable. The current authorization tuple is a separate projection and never derives authority from historical rows. are not the install-time bootstrap: the local provider-grant command remains available for creating the first authority before an administrator exists. POST /provider/v1/auth/logout revokes the Provider session, clears its cookie, and is protected by the same CSRF and idempotency contract as other mutations.

Provider authority event source and retries

The Provider max_secrets_stored quota counts current native/Vault application secrets and stored connector credentials for the exact customer tenant. New names require a free slot; an exact replay or update of an existing name does not. A pending first-create command reserves its slot through restart until projection finishes. The Provider usage evidence gauge reports materialized objects only. A cap refusal returns 429, and a concurrent first-create returns a retryable 503. A missing quota leaves this resource uncapped; a quota-store or count outage refuses a new capped write with a retryable 503. Quota changes are Provider authority events and require the customer's provision delegation. An already admitted first write can finish while a lower cap is being saved; the next admission reads the new projected limit.

Customer quota writes support max_agents, max_certificates_stored, and max_secrets_stored; a blank cap means no customer resource limit and zero refuses new objects. max_tenants is not a customer quota: each customer is one tenant, and the signed Provider license's managed-customer band limits new customer provisioning across the deployment. The API rejects max_tenants in new quota writes. Older authority events and quota rows may contain that field for replay compatibility, but reads omit it and it never enforces a limit. The tenants usage gauge still records one for each active customer and is separate from customer resource caps.

Customer lifecycle, delegation, quota, white-label branding, and break-glass grant/use state share one tenant-scoped immutable authority history. The provider_tenants, provider_operators, provider_operator_delegations, provider_operator_grant_episodes, provider_tenant_quotas, tenant_branding, and provider_breakglass_grants tables are read models owned only by that projection; their PostgreSQL stores expose no direct mutators. An upgraded installation captures each uncovered pre-event row once before its first rebuild, including brand token overrides and both break-glass consents. Custom domains are unique across customers. A new claim for an already owned domain returns 409 brand_domain_conflict before an authority event is appended, and the existing owner's public brand remains served. If an older release retained a claim that its database index refused, startup replays that attempt as a rejected outcome rather than failing the entire Provider plane; activity names the rejection. Retry the rejected idempotency key to inspect its original conflict, then use a new key after choosing an unclaimed domain. Provider brand editors first read the exact delegated customer's saved brand at GET /provider/v1/tenants/{id}/brand. The response includes a strong ETag; PUT requires that value in If-Match and an Idempotency-Key. An absent precondition returns 428, and a stale revision returns 409 brand_revision_conflict without overwriting the newer state. The immutable event ID becomes the next revision during projection, including cold replay. Internal projection failures return a generic retry instruction; database constraint names and SQL diagnostics stay in server logs. Deployment-wide Provider audit events use the reserved UUID Provider partition. Older events recorded under the historical provider-control-plane text partition remain readable during catch-up, full rebuild and outbox recovery; core skips only that historical Provider namespace while its licensed projection preserves the audit activity. Malformed tenant IDs on other event namespaces still stop replay or recovery.

Every state-changing /provider/v1 request requires Idempotency-Key. The key is immutably bound to the authenticated operator, method, path, and body; exact retries return the original status, headers, and bytes, including concurrent retries, while a changed command returns 409. Break-glass result access increments use count and is therefore POST /provider/v1/breakglass/{grant}/results, not a read-looking GET. The provider console generates a distinct key for every mutation it submits.

The Provider console's exact-customer emergency queue uses GET /provider/v1/tenants/{id}/breakglass with a bounded limit and customer-bound before cursor. The read requires the authenticated operator's current MFA and break-glass delegation for that customer. Its state is derived at read time so expiry is visible even without a new event. Listing a request never grants access to its result; that separate POST rechecks requester identity, current delegation, both distinct consents, and expiry. After the license grace period, the same queue remains readable for operators with current delegation; the console hides request, consent, withdrawal, and result-use controls because those actions mutate commercial state.

GET /provider/v1/activity?limit=100 derives a newest-first authority evidence view directly from that same immutable history. It returns event identity, sequence, type, time, customer, actor, grant, subject, and reason only: request bindings, authority state payloads, and break-glass tenant snapshots are never exposed through the history route. Current delegation is applied before any customer event is returned, so an operator cannot discover another customer's authority history; deployment-wide isolation-drill evidence is Provider-admin only. The Provider console renders this view beside the controls, making the evidence for a completed mutation visible without trusting a second audit store.

Provider tenant templates

A Provider holds its customers to versioned tenant templates. A template names the objects a customer tenant should hold, each in the shape the core API accepts for it:

Template section Customer object
profiles certificate profiles, by name
policy the active lifecycle policy module
issuers CA bindings: the X.509 chain (or SSH CA key) its identities are issued under
deployment_targets connector targets it deploys to by default
notification_channels its channel of each type
members built-in role grants to named members
report_schedules scheduled compliance reports

POST /provider/v1/templates records a version (templates:write): the template is checked with the same validation the customer's own requests get, and an unchanged definition returns the latest version instead of minting another. GET /provider/v1/templates and GET /provider/v1/templates/{id} read them, with the customers bound to each that the operator may see.

POST /provider/v1/tenants/{id}/template onboards a delegated customer (templates:write and the customer's provision delegation): it registers the customer's core tenant when core has not, then writes every object the customer does not already hold as the version says, through the customer's own commands (a profile edit that dual control holds is reported pending_approval). Re-applying writes nothing that is already in place. GET /provider/v1/tenants/{id}/template-drift reads the customer's objects back against its version: each object is in_sync, drifted or missing, compared by digest.

A new version rolls out with a preview: POST /provider/v1/templates/{id}/rollout/preview lists, for each customer bound to the template (or the customers named), what applying the version would change, writes nothing, and returns a fingerprint. POST /provider/v1/templates/{id}/rollout with that fingerprint applies the version exactly as previewed; if any customer changed since the preview it answers 409 template_rollout_stale and applies nothing. Each customer's application is a provider.tenant_template.applied authority event naming the operator and each object's outcome, shown in the Provider activity view; template versions are provider.template.recorded events.

A report schedule in a template may carry dispatch, as the customer's own schedule does: {"channels":["webhook"]} names the customer's channel of that type, which the same template usually creates, and each completed run is announced there.

Provider customer assurance reports

A Provider schedules a customer's customer assurance report through a template's report_schedules ("report_type":"customer_assurance"), and brands the customer with PUT /provider/v1/tenants/{id}/brand. Every run renders in that brand and is signed by the governance report key.

GET /provider/v1/tenants/{id}/assurance-reports lists the customer's completed runs, newest first (at most 50), with the public key that signs them and its SHA-256. GET /provider/v1/tenants/{id}/assurance-reports/{run_id} returns one run's exact signed bytes, read back from the archive and verified before they are handed out. Both need the operator's read delegation on that customer. The Provider shares the file and the key's SHA-256; the customer verifies the file offline with trstctl-cli compliance report-runs verify.

Usage metering and invoice evidence

Metering is durable: usage is recorded in PostgreSQL under the same row-level security fence as every other tenant table, so it survives a restart and cannot be read across tenancies.

GET /api/v1/provider/usage-evidence (CLI: trstctl usage evidence, console: Platform → Usage & invoice evidence) returns the evidence document for a period. The tenant console accepts an exact UTC start and end to the minute, including a single closed meter hour, and clears an older verdict when the selected period changes. It disables a reversed or incomplete interval before requesting evidence. The System health panel first checks the running API's operation registry. Core builds without Provider metering show that invoice evidence is unavailable and do not offer a pull action that would return 404. An attached route still checks the caller's audit-read permission at execution. Two properties of that document matter more than the totals:

  • The tenant route is scoped to the caller's own tenancy. A customer_id naming any other tenant is refused with 403 before the store is touched. Provider billing staff do not impersonate that tenant: their separate workforce credential calls GET /provider/v1/tenants/{id}/usage-evidence with an exact customer read delegation (the read operation). An undelegated customer or a grant for another operation is refused before the customer's forced-RLS metering transaction opens.
  • It says whether it may be billed. signable is false, with a reason, for any period the metering store cannot vouch for end to end — an open period, metering that was not durable for the whole window, or coverage that starts after the period does. The totals are still returned, because "your usage is incomplete and here is how" is actionable and an error is not, but they are a partial view rather than an invoice. There is deliberately no way to get the numbers without the verdict attached.

For a fully covered closed period, observed_from and observed_to describe the selected period exactly. Later observations outside that period cannot change its signed digest. An incomplete period retains the metering store's actual diagnostic bounds and remains unsigned.

An absent metering store returns 503 rather than a zero-usage document: no metering and no usage are different facts.

The Provider route returns the same canonical signed JSON/JWS document as the tenant route, or ?format=csv for a strict finance CSV whose rows retain the customer, period, signable verdict, reconciliation result, and document digest. The /provider console selects a customer and exact UTC period to the minute (including a single closed meter hour), rejects a reversed interval, downloads signed JSON or finance CSV, and fetches public trust separately from GET /provider/v1/evidence/verification-keys. It reconstructs the displayed document's canonical bytes and shows Signature verified only when RS256, the protected billing-invoice artifact domain, key id, digest, and signature all match. A green label therefore does not trust the evidence response's own signable boolean.

The same customer selection also reads GET /provider/v1/tenants/{id}/health. This is a small operational view, not break-glass access: after the exact read delegation succeeds, DirectTenantSnapshot enters that customer's PostgreSQL RLS identity and counts only active certificate rows for that tenant. The response distinguishes healthy, suspended, offboarded, and no-active-certificate states. A customer whose workspace is not initialized reports setup_required and workspace_initialized: false; an initialized workspace reports true even when its certificate inventory is empty. This field confirms workspace registration, not token availability, successful sign-in or agent enrollment. The Provider console's View setup explains the administrator's first-token command and rechecks this state without treating missing evidence as success. A customer that does not exist returns 404; a storage failure returns 503; and the console renders both as unknown/unavailable rather than showing zero as if it were a measured fact. Customer health and Invoice evidence therefore name the same selected customer while keeping lifecycle/count truth separate from the signed billing period.

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.