Skip to main content
Canonical end-to-end flow: OACP authority overview. Launch closure source of truth: OACP Runtime Launch Closure PRD.

Provision AgenticOrg Authority Access

  1. Store COMMERCE_C6Z_AUTHORITY_SERVICE_TOKEN through the production secret path.
  2. Optionally add bootstrap tenant ids to COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS. Merchant tenants no longer need an entry here: AgenticOrg enrols each one itself (see Service-Tenant Enrolment).
  3. Deploy or restart the Grantex auth service with both values available. PUBLIC_BASE_URL must be https (status list URIs are built from it).
  4. Create the first OACP issuer key: POST /v1/commerce/oacp/issuer-keys/rotate with the platform admin key. Confirm its kid (oacp-...) appears in GET /.well-known/jwks.json.
  5. Set OACP_STATUS_LIST_PUBLIC_ENABLED=true and restart when relying parties are ready to check status; until then they refuse artifacts with revocation_status_unavailable.
  6. Submit a fixture POST /v1/commerce/oacp/c6z/authority-requests for that tenant. Public discovery stays disabled unless OACP_PUBLIC_DISCOVERY_ISSUANCE_ENABLED=true and the request opts in (public_discovery_requested: true with namespace_attestation_ref and serving_host); turn the flag on only after the owner approves publishing UCP profiles.
  7. Confirm artifact_count = 11, or record the exact refusal/blocker.
  8. To roll back, revoke the tenant’s enrolment (or remove a bootstrap tenant id from COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS) and rotate the token if there is any suspicion of exposure.

Service-Tenant Enrolment

A tenant may use the AgenticOrg service token when it is on COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS or has an active enrolment in oacp_service_tenant_enrolments (migration 137_oacp_service_tenant_enrolments.sql). The env list is now only a bootstrap list for operator and fixture tenants: no per-merchant env edit or redeploy is needed. Env-listed tenants behave exactly as before (any merchant, any route). Validation refusals are 422 validation_failed with details.fields: tenant_id must be a UUID, merchant_id must match ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$ (the OACP id rule), source_system must be shopify or catalog_upload, evidence_ref an opaque reference of at most 256 characters. Every enrolment, refusal, revocation and admin read is written to the append-only oacp_service_tenant_enrolment_events table (UPDATE and DELETE are blocked by trigger, like commerce_audit_events, which cannot hold these rows because it references commerce_tenants). For an enrolled tenant the service token must also name one of the enrolled merchants:
  • C6Z issuance (request.merchant_id), arbiter decisions and settlement evidence (merchant_id), the UCP profile (merchant_id) and artifact replay (merchant_id, now required for an enrolled tenant) answer 403 service_tenant_merchant_not_enrolled otherwise;
  • mandate issuance and the ACP delegate-payment allowance use the passport’s merchant_id (or the body’s, when given) and refuse the same way;
  • reads, revocations and suspensions of a stored artifact, mandate or order of another merchant answer 404, exactly like another tenant’s, and mandate verification answers mandate_scope_mismatch.
A revoked tenant gets 403 service_tenant_revoked (or 404 on the by-id routes); an unknown one the route’s existing service_tenant_not_allowed / tenant_not_allowed. Operators and the platform admin are unaffected. Audit witness checkpoints (/v1/audit/witness-checkpoints) apply the same tenant check when tenant_ref is a tenant UUID (what AgenticOrg sends): 403 SERVICE_TENANT_NOT_ALLOWED or SERVICE_TENANT_REVOKED unless the tenant is env-listed or actively enrolled. A tenant_ref that is not a UUID is an opaque reference with no tenant behind it and is accepted as before. If AgenticOrg witnesses the audit chains of tenants that never enrolled (its AGENTICORG_AUDIT_CHAIN_ENABLED covers every tenant), those posts are now refused and logged as audit_chain_witness_refused; enrol or env-list such tenants first.

Catalogue upload source

connector_choice: "catalog_upload" (with connector evidence source_system: "catalog_upload") is a catalogue the merchant uploaded itself. It is accepted for an env-listed tenant, or for an enrolled tenant whose enrolment lists catalog_upload; otherwise it is refused unsupported_or_enabling_authority_request, recorded like any refusal. It is never synthetic data (synthetic_data: true is refused). Its artifacts say so honestly: source_system: "catalog_upload" at the top level, and in source_lineage source_system: "catalog_upload", merchant_system_source_of_record: true, merchant_supplied_upload: true and raw_catalog_upload_stored_by_grantex: false. The synthetic demo source keeps its rules: an enrolment never grants it. Public discovery issuance treats an actively enrolled tenant like an env-listed one; it still needs OACP_PUBLIC_DISCOVERY_ISSUANCE_ENABLED=true and the request’s opt-in and attestation. To offboard a merchant tenant: POST .../service-tenants/{tenant_id}/revoke with a reason code, then revoke its live artifacts if they must stop verifying before they expire.

Enable a Synthetic Demo Tenant

A demonstration merchant with an invented catalogue gets real signed artifacts without claiming a merchant system of record. Issuance accepts connector_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.
Otherwise the request is refused exactly as an unsupported source always was (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:
  1. Confirm with the owner that the tenant is a demonstration tenant whose catalogue is invented.
  2. Add its tenant id to COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS and to COMMERCE_C6Z_DEMO_SOURCE_TENANTS; redeploy.
  3. Submit its authority request with connector_choice: "synthetic_demo" and synthetic_data: true. Confirm 201, artifact_count = 11, and that every payload carries synthetic_data: true.
  4. Only if the demo is to publish a UCP profile: the public discovery opt-in, as above.
To withdraw it, remove the tenant id from 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 to main 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; revoking a tenant’s enrolment, or removing a bootstrap 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

  1. Remove the AgenticOrg tenant from COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS.
  2. Rotate the service token if compromise is suspected.
  3. Ask AgenticOrg to stop refreshing artifacts and mark affected cache records stale.
  4. Leave existing historical artifacts reviewable for audit/export (oacp_artifacts).
  5. To invalidate artifacts already handed out, revoke them individually, or revoke the issuer key to invalidate everything it signed.
  6. 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

Migration 134_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.

Protocol Surfaces, Receipts And Network Identity

These routes ship enabled behind authentication (the service token with its route scope and the tenant allowlist, an operator of the tenant, or the platform admin) and need no new configuration. All of them depend on step 2 (issuer key); relying parties depend on step 3.

Refusal Copy

Use exact blockers: missing source evidence, stale artifact, unsafe executable request, provider capability missing, tenant not allowlisted, or authority temporarily unavailable.

Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: sanjeev@orchestrum.in or mishra.sanjeev@gmail.com.
Last modified on October 29, 2026