Licensing & entitlement¶
Audience: DevOps configuring PrivaCI commercial on AWS Marketplace.
When you are done: You know how entitlement flows into each batch job, which env vars to set, which tier grants which capabilities, and why a run exits 5.
PrivaCI commercial is sold via AWS Marketplace. Customers subscribe, run the official container image in their VPC, and entitlement is validated at job start — no git clone, no separate license server in your network, and no usage counting. A tier is a fixed set of capabilities, not a meter.
The commercial plugin MarketplaceLicenseValidator (entry point
license_validator) is called by the public engine before every privaci run.
See public Extending PrivaCI — Community mode
for the plugin boundary and
License capabilities
for how the engine reads capability tokens.
How licensing works¶
Marketplace subscription
→ customer pulls commercial container image
→ privaci run (batch job in customer VPC)
→ load_plugins().license_validator.validate()
→ tier resolved once (dev bypass → offline JWT → License Manager checkout)
→ LicenseStatus carries the tier's capability tokens
→ (if not valid) run exits 5
validate() resolves a tier from the first source that applies:
| Priority | Source | Tier comes from |
|---|---|---|
| 2 | PRIVACI_LICENSE_KEY (offline signed JWT) |
the tier claim |
| 3 | AWS License Manager CheckoutLicense |
the entitled dimension in the Marketplace-issued license |
| — | none of the above | invalid → exit 5 |
Capabilities are always derived locally from the tier — the JWT never stores capabilities, so an issuer and a runtime can never disagree.
| Check | When | Exit code |
|---|---|---|
| A valid tier resolves | Run start (preflight) | 5 |
| Requested feature is in the tier | Config parse / feature use | 5 |
Model rationale: ADR-0012 (commercial).
Marketplace subscription flow¶
- Subscribe on AWS Marketplace (Standard or Compliance).
- Run in VPC — attach an IAM task role to your ECS task / EKS pod / Batch
job that can call
license-manager:CheckoutLicenseandlicense-manager:CheckInLicense. - Entitlement at job start — the commercial layer calls
CheckoutLicense(typePROVISIONAL) against the Marketplace-issued license, reads the entitled tier, then checks the provisional lease back in. Alternatively use an offlinePRIVACI_LICENSE_KEY(JWT) for air-gapped environments.
There is no BoundaryLogic-hosted license server and no metering call. Entitlement uses AWS License Manager APIs from inside your account/VPC.
Configuration¶
Environment variables¶
| Variable | Required | Description |
|---|---|---|
PRIVACI_MARKETPLACE_PRODUCT_SKU |
Optional | Overrides the ProductSKU baked into the published image; set only to point a staging build at a different listing |
AWS_REGION |
Marketplace runs | Region for License Manager APIs (default us-east-1) |
| AWS credentials | Marketplace runs | Via task/instance role — never static keys in the image |
PRIVACI_LICENSE_KEY |
Offline / air-gap | Signed JWT license (EdDSA) when License Manager is unavailable |
PRIVACI_LICENSE_PUBLIC_KEY |
With JWT | PEM (or 64-char hex) Ed25519 public key that verifies the JWT |
Configure variables via your orchestrator's secret/env mechanism — see your platform's docs for injecting secrets at runtime.
Production (Marketplace)¶
This is the recommended production path for Marketplace subscribers.
# Kubernetes Secret / ECS task definition — illustrative
env:
- name: AWS_REGION
value: "us-east-1"
# IAM task role grants license-manager:CheckoutLicense + CheckInLicense.
# The image already knows its ProductSKU — no license ARN or product code needed.
The task role needs exactly two License Manager permissions — the engine calls
CheckoutLicense (type PROVISIONAL) at job start and best-effort
CheckInLicense at exit. Attach this policy (also shipped as
docs/marketplace/iam-task-role.json):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LicenseManagerCheckout",
"Effect": "Allow",
"Action": [
"license-manager:CheckoutLicense",
"license-manager:CheckInLicense"
],
"Resource": "*"
}
]
}
No aws-marketplace:* actions are required — entitlement is resolved entirely
through License Manager.
Offline JWT (air-gapped contract)¶
- Obtain a signed JWT from BoundaryLogic (tier claim inside token).
- Inject at runtime:
export PRIVACI_LICENSE_PUBLIC_KEY="$(cat license-public.pem)"
export PRIVACI_LICENSE_KEY='eyJ...'
JWT claims:
| Claim | Values |
|---|---|
tier |
standard · compliance (legacy starter → standard; growth/team/business/enterprise/unlimited → compliance) |
exp |
Standard JWT expiry |
Legacy slugs from JWTs issued before the capability-tier model are normalized forward so already-issued tokens keep their full feature set.
Tiers & capabilities¶
A tier is a fixed set of capability tokens. The public engine and the commercial layer share these strings verbatim; the engine checks membership only and never interprets tier names.
| Capability token | What it unlocks |
|---|---|
keyed_actions |
hmac_hash / pseudonym deterministic keyed masking |
conditional_masking |
Optional CEL when: guards on column actions |
json_mask |
JSONB path masking |
fk_subsetting |
Foreign-key-aware subset runs |
drift_detection |
Schema-drift detection CI gate |
strict_gate |
Strict-gate preflight + SARIF |
signed_report |
Ed25519-signed compliance reports |
| Tier | Price (USD/mo) | Capabilities |
|---|---|---|
| Standard | $149 | keyed_actions, conditional_masking, json_mask |
| Compliance | $749 | Everything in Standard plus fk_subsetting, drift_detection, strict_gate, signed_report |
Compliance is a strict superset of Standard. There is no source-database limit and no monthly data ceiling — tiers gate features, not volume.
Planned redraw (ADR-0013):
conditional_masking → Free (delete token); declared-FK subsetting → Free;
fk_subsetting retired; Standard gains implied_fk, subset_budgets,
drift_findings (and ai_refine when implemented); Compliance keeps
strict_gate, drift_gate, signed_report. Listing copy and this table
update atomically with the public subsetting move — values above are
current shipped tokens until then.
ai_refine is not implemented; connector stubs fail closed. Do not
configure it in production.
How capability gating behaves¶
Gated features follow a consistent contract (see ADR-0012 and the placement principle in ADR-0013):
| Feature class | Behaviour when the capability is missing |
|---|---|
Masking semantics (keyed actions, conditional when:, subsetting) |
Fail loudly at config parse — exit 5 with an upgrade message. A run never silently produces differently-masked output. |
| Additive outputs (signed report) | Degrade gracefully with a visible notice (e.g. an unsigned report is still written). |
| CI gates (drift detection, strict gate) | Refuse loudly with a distinct non-zero exit and an upgrade message. |
keyed_actions and conditional_masking gating are enforced by the public
engine; the remaining commercial capabilities are enforced by the commercial
layer.
License Manager / JWT failures fail closed¶
When a tier cannot be resolved — CheckoutLicense denies or errors, the JWT is
expired or unsigned, and no dev license is set — validate() returns
is_valid=False and the run exits 5. Entitlement API errors never grant a
free tier. A best-effort CheckInLicense releases the provisional lease; a
leaked provisional lease is harmless because entitlement was already confirmed at
run start.
CI/CD integration (customer pipeline)¶
Run the Marketplace image as a one-shot job in your CI cluster or runner with the same env vars and IAM task role as production:
- name: Mask staging from prod snapshot
env:
AWS_REGION: us-east-1
SOURCE_DB_URL: ${{ secrets.STAGING_SOURCE_DB_URL }}
TARGET_DB_URL: ${{ secrets.STAGING_TARGET_DB_URL }}
ANONYMIZATION_SALT: ${{ secrets.ANONYMIZATION_SALT }}
run: |
docker run --rm … <marketplace-image> run --config /config/mask-rules.yaml
test $? -ne 5 # fail pipeline on entitlement error
Schema drift gate: Drift detection.
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| Exit 5 — no license | No active subscription; License Manager checkout returned no tier | Confirm active Marketplace subscription; verify the task role can call CheckoutLicense |
| Exit 5 — not authorized | Task role lacks license-manager:CheckoutLicense |
Attach the IAM task role policy shown in the Production section above (CheckoutLicense + CheckInLicense) |
| Exit 5 — invalid JWT | Bad signature or expired exp |
Re-issue token; check PRIVACI_LICENSE_PUBLIC_KEY |
| Exit 5 — feature not in tier | Config uses a capability your tier lacks | Upgrade to Compliance, or remove the gated feature |
Full exit code reference: Troubleshooting · public Error codes.
FAQ¶
Does community mode (public GHCR image, no commercial layer) exit 5?
No. Community LicenseValidator always returns valid with no capabilities.
Keyed actions and other gated features are unavailable; unsigned reports only.
Do you count source databases or data volume?
No. The previous usage-metering model was removed — tiers gate features, not
volume. There is no _privaci.usage_ledger and no RegisterUsage/MeterUsage.
What does the provisional checkout do to my subscription?
Nothing billable. CheckoutLicense (type PROVISIONAL) confirms entitlement and
reads the tier; the commercial layer checks the lease back in immediately.
Do I need this private GitHub repo?
No. Everything ships in the Marketplace container image.