Private release previewNo live purchasesRelease details ↗

EN · ORIGINAL REPOSITORY DOCUMENT

Customer and license operations

Original English text. Commands and evidence apply to the revision and environment stated in the document.

product/licensing/go/CUSTOMER-OPERATIONS.md

On this page

Local customer/license operator workflow

This is a bounded operator workflow for the original Go entitlement backend. Customer IDs and manual order references are opaque ASCII identifiers, not user accounts or a PII directory. There is no public customer API, checkout, payment provider, email delivery or automated billing authorization. Operators must verify fulfillment and recovery evidence independently before supplying confirmation. A confirmation flag records operator intent; it does not prove payment. Original entitlements never replace upstream software distribution permissions.

Fulfillment and status

IssueForCustomer(customerID, orderID, expires, siteLimit, manualAuthorized) requires explicit manual authorization and valid future expiry/site limits. References contain 1..128 ASCII letters/digits with internal ., _, :, -; the first character is alphanumeric. Use opaque identifiers, never email addresses. Order references are globally unique. The first transaction creates a random bearer key and atomically upgrades state to schema 2, preserving anonymous licenses. Concurrent/repeated fulfillment for the same customer/order returns the existing digest and created:false, without a bearer key. Another customer cannot reuse that order. No duplicate license or plaintext bearer is stored.

Store the first returned key privately. A lost bearer cannot be restored by replaying fulfillment, and a digest is not an equivalent replacement credential. Confirmed lost-license replacement is available as a separate operator action; it never recovers or reissues the old bearer. A repeated order is not a renewal or an opportunity to change its plan. Anonymous existing licenses retain their previous operator workflow and are not silently attached to customers.

CustomerLicenseStatus(customerID,key) returns only opaque customer/order IDs, expiry, site limit, revocation and active/approved counts. It reveals no bearer, installation secret, domain list or signing material. Customer binding is checked inside the same locked state transaction as all other operator mutations.

Renewal, revocation and installation recovery

RenewForCustomer requires a future expiry, does not shorten the current expiry, and refuses a site limit below the number of active sites. It never reverses revocation. RevokeForCustomer is customer-bound and idempotently revokes the license, invalidating server-protected downloads through the existing verifier.

RecoverInstallationForCustomer(customerID,key,site,expectedOldDigest,newKey,confirm) requires confirmed, independently verified operator recovery evidence, an approved origin, a non-revoked/non-expired customer-bound license and an exact old-owner SHA-256 digest. This compare-and-swap rejects stale recovery attempts. It changes ownership to the replacement installation digest, marks it inactive, preserves origin approval/site limits and invalidates old download tokens even after the replacement is activated. There is no automatic migration or remote domain fetch. The new WordPress installation must explicitly activate; the old owner is refused.

Prepare the replacement installation secret through the intended installation’s private/admin setup and deliver it privately to the operator. Installing it into WordPress, authenticating the human requester and proving domain ownership remain operator responsibilities; this is not a self-service account recovery portal. Already issued offline signed entitlements cannot learn server recovery instantly; their fixed validity/grace remains bounded, while downloads always recheck the current owner. Renewal may need a later explicit refresh after a signed denial; client anti-replay timestamps are not overridden.

Confirmed lost-license replacement

ReplaceLostLicenseForCustomer(customerID,oldOrderID,newOrderID,confirmReplacement) looks up the original license using customer/order references without requiring a lost secret. Confirmation must include independently verified customer identity and authorization for every retained origin. The old license must be live and not revoked, and the replacement order reference must be distinct and unused. One locked transaction permanently revokes the old license and issues a brand-new bearer under the new reference, preserving exactly its expiry, site limit and approved origins. All replacement activations start empty. No new origins or extra limits are granted; WordPress must explicitly save/activate its new license. Old server download tokens remain invalid even after replacement activation.

A used replacement reference is rejected, including retries by the same customer; it never mints another key. Preserve the initial private output and investigate an uncertain operator result before attempting further recovery. The old revoked license cannot be renewed, recovered or replaced again. This is replacement, not plaintext-key restoration or reversal of revocation. Offline entitlement claims still have their fixed signed deadlines; package delivery checks the live revocation and ownership. There is no account reset, email, payment or public API.

Local CLI examples

Root integration wires these methods into the existing local binary. Use a private state directory, server-only HMAC material through LICENSING_SIGNING_KEY, and a restrictive umask. Do not pass bearer or installation secrets in process arguments. /private/operator/key.json contains exactly {"license_key":"<private-key>"}; /private/operator/recovery.json contains exactly {"license_key":"<private-key>","new_installation_key":"<private-replacement>"}. These are examples of privately supplied credentials, not committed fixtures.

umask 077
original-licensing --database /private/operator/licenses.json fulfill \
  --customer-id customer-001 --order-id verified-order-001 \
  --expires <future-unix-time> --sites 2 --confirm-manual-fulfillment \
  > /private/operator/issued-once.json
original-licensing --database /private/operator/licenses.json customer-status \
  --customer-id customer-001 < /private/operator/key.json
original-licensing --database /private/operator/licenses.json customer-renew \
  --customer-id customer-001 --expires <later-future-unix-time> --sites 2 \
  < /private/operator/key.json
original-licensing --database /private/operator/licenses.json customer-revoke \
  --customer-id customer-001 < /private/operator/key.json
original-licensing --database /private/operator/licenses.json customer-recover-installation \
  --customer-id customer-001 --site https://approved.example \
  --expected-old-digest <old-installation-sha256> --confirm-recovery \
  < /private/operator/recovery.json
# Alternative lost-bearer workflow: no old secret is needed.
original-licensing --database /private/operator/licenses.json customer-replace-license \
  --customer-id customer-001 --old-order-id verified-order-001 \
  --new-order-id confirmed-replacement-001 --confirm-replacement \
  > /private/operator/replacement-key-once.json

Recovery and revocation above are alternative lifecycle operations: a revoked license cannot subsequently recover or renew. Capture a valid old installation digest from independently verified private operator records, not an untrusted claim. Service deployment, customer identity verification, billing and original artifact distribution authorization remain separate release gates.

Evidence

Focused tests cover manual-authorization refusal without a key/state write, concurrent eight-way fulfillment with exactly one bearer issuance, idempotent replays, cross-customer rejection, safe counts, renewal limits, revocation, unchanged snapshots after failures, legacy retention under schema 2, recovery CAS, expiry/revocation recovery refusal, inactive replacement, confirmed lost-bearer replacement without the old secret, used-order rejection without partial revocation, and old tokens rejected before and after replacement activation. Tests use temporary synthetic licenses, keys and an original fixture ZIP; no customer data or production secrets.

go test ./... -run '^TestCustomer' -count=1
go test -race ./... -run '^TestCustomerConcurrentFulfillmentOneKey$' -count=1

State schema 2 deliberately rejects older schema-1-only server versions so they cannot silently strip customer/order references. Backups must be restored only with a compatible backend and the existing ambiguity/revocation restore controls.