Provision AgenticOrg Authority Access
- Store
COMMERCE_C6Z_AUTHORITY_SERVICE_TOKENthrough the production secret path. - Add the AgenticOrg tenant id to
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS. - Deploy or restart the Grantex auth service with both values available.
PUBLIC_BASE_URLmust be https (status list URIs are built from it). - Create the first OACP issuer key:
POST /v1/commerce/oacp/issuer-keys/rotatewith the platform admin key. Confirm itskid(oacp-...) appears inGET /.well-known/jwks.json. - Set
OACP_STATUS_LIST_PUBLIC_ENABLED=trueand restart when relying parties are ready to check status; until then they refuse artifacts withrevocation_status_unavailable. - Submit a fixture
POST /v1/commerce/oacp/c6z/authority-requestsfor that tenant. Public discovery stays disabled unlessOACP_PUBLIC_DISCOVERY_ISSUANCE_ENABLED=trueand the request opts in (public_discovery_requested: truewithnamespace_attestation_refandserving_host); turn the flag on only after the owner approves publishing UCP profiles. - Confirm
artifact_count = 11, or record the exact refusal/blocker. - To roll back, remove the tenant id from
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTSand rotate the token if there is any suspicion of exposure.
Enable a Synthetic Demo Tenant
A demonstration merchant with an invented catalogue gets real signed artifacts without claiming a merchant system of record. Issuance acceptsconnector_choice: "synthetic_demo" (and
connector evidence with source_system: "synthetic_demo") only when all of these hold:
- the tenant id is on
COMMERCE_C6Z_DEMO_SOURCE_TENANTS(comma-separated; empty by default, so no tenant can use the synthetic source); - the same tenant id is on
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS, whoever calls (the platform admin included); - the request carries
synthetic_data: true, and so does the connector evidence.
422,
status: "rejected", refusal_code: "unsupported_or_enabling_authority_request") and the
refusal is recorded in oacp_artifacts with a refusal_record_id. Evidence that names another
source, or is not marked synthetic, is refused connector_evidence_scope_mismatch, also recorded.
Every artifact issued from the synthetic source says so in its signed payload:
source_system: "synthetic_demo" and synthetic_data: true at the top level, and in
source_lineage source_system: "synthetic_demo", synthetic_data: true and
merchant_system_source_of_record: false. Shopify issuance is unchanged, byte for byte.
Public discovery follows the same opt-in rules as for any tenant (OACP_PUBLIC_DISCOVERY_ISSUANCE_ENABLED,
the allowlist and the namespace / host attestation); the public_discovery_state artifact
then carries synthetic_data: true, and GET /v1/commerce/oacp/ucp-profile adds
synthetic_data: true to its render, so a relying party can see the profile is a demonstration.
To enable one demo tenant:
- Confirm with the owner that the tenant is a demonstration tenant whose catalogue is invented.
- Add its tenant id to
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTSand toCOMMERCE_C6Z_DEMO_SOURCE_TENANTS; redeploy. - Submit its authority request with
connector_choice: "synthetic_demo"andsynthetic_data: true. Confirm201,artifact_count = 11, and that every payload carriessynthetic_data: true. - Only if the demo is to publish a UCP profile: the public discovery opt-in, as above.
COMMERCE_C6Z_DEMO_SOURCE_TENANTS (new synthetic
requests are refused at once) and revoke its live artifacts if they must stop verifying before
they expire.
For a local stack (PUBLIC_BASE_URL=http://localhost:<port>), also set
OACP_ALLOW_LOOPBACK_HTTP=true; without it issuance refuses the http status list URI. It is
ignored when NODE_ENV is production. OACP_AUTO_GENERATE_ISSUER_KEY=true creates the first key
on first use in a local or test stack; it is also ignored in production. Then run the live
conformance suite from the in-tree build (the oacp suite is not yet in a published release of
@grantex/conformance):
GRANTEX_CONFORMANCE_INTERNAL_TOKEN=<service token> node packages/conformance/dist/index.js --base-url http://localhost:<port> --api-key <developer key> --include oacp --suite oacp --oacp-issuer http://localhost:<port> --oacp-tenant <tenant>.
Production Turn-On Order
A merge tomain deploys the auth service, so the OACP code is deployed before anything is
turned on. On 2026-10-20 production had no oacp- key in its JWKS and the status list route
was off. Turn OACP on in this order, and do not start a step before the previous one is
confirmed:
Rollback, in reverse: turning
OACP_STATUS_LIST_PUBLIC_ENABLED off makes every relying party
refuse (fail closed), it does not make anything valid; revoking the issuer key withdraws
everything it signed; removing a tenant from the allowlist stops new issuance for it.
Never put Shopify tokens, provider credentials, payment credentials, raw provider payloads, checkout links, raw POS callbacks, or webhook secrets in Grantex artifacts or logs.
Keys and Revocation
Smoke Tests
Checklist
Rollback
- Remove the AgenticOrg tenant from
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS. - Rotate the service token if compromise is suspected.
- Ask AgenticOrg to stop refreshing artifacts and mark affected cache records stale.
- Leave existing historical artifacts reviewable for audit/export (
oacp_artifacts). - To invalidate artifacts already handed out, revoke them individually, or revoke the issuer key to invalidate everything it signed.
- Publish an operator note with affected tenants, merchant ids, artifact families, and timestamps.
Arbiter Decision Ledger
POST /v1/commerce/oacp/arbiter-decisions records every AgenticOrg arbiter decision in commerce_arbiter_decisions (migration 134_commerce_ledger.sql), append-only: a trigger refuses UPDATE, DELETE and TRUNCATE, and a check constraint refuses a refusal row that carries a grant. It needs no new configuration: the service token and COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS already provisioned for authority requests apply.
To stop countersigning for a tenant, remove it from
COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS; recorded decisions stay readable for audit.
Witness Checkpoints
POST /v1/audit/witness-checkpoints stores AgenticOrg audit chain heads in audit_witness_checkpoints and refused forks in audit_witness_forks (both append-only, migration 134_commerce_ledger.sql). It is authenticated with the AgenticOrg service token alone; no tenant allowlist applies, because tenant_ref names AgenticOrg’s own tenant.
A row in audit_witness_forks means AgenticOrg presented a second, different hash for a chain position Grantex had already witnessed: treat it as an irregularity, open an incident with AgenticOrg, and keep both the witnessed countersignature and the fork record for the review. Read them with GET /v1/audit/witness-checkpoints?source=agenticorg&tenant_ref=....
Wallet Ledger Chain
Migration134_commerce_ledger.sql adds chain_seq, prev_hash and entry_hash to wallet_ledger_entries, chains every new entry in a BEFORE INSERT trigger under a per-wallet advisory lock, and backfills existing entries per wallet in (created_at, id) order. The backfill disables the table’s append-only trigger only inside the migration transaction, which already holds an exclusive lock on the table; it is enabled again before commit. Plan the migration for a maintenance window on a large ledger. No flag: the columns and trigger are additive and no existing response changes.
A failed verification is an irregularity in financial evidence: block the wallet, keep a database snapshot, and reconcile from backups before any repair. Never repair a ledger by editing or deleting entries.