Monthly Character Allowance
Shared monthly limits, online authorization, retries, counting and migration.
ILS One mode now requires online authorization before dispatching each logical automatic translation batch. Stardetha One atomically debits a licence-wide UTC calendar-month allowance. No text is sent to the licensing service: requests contain a count, opaque batch fingerprint and retry ID. The translation provider still receives text through the existing provider adapter.
Counting contract
| Item | Rule |
|---|---|
| Unit | Unicode code points in the exact strings passed to the outer provider wrapper, including whitespace, markup and protected placeholders. Combining marks count separately; this is not grapheme or byte counting. |
| Target | Each logical batch to one target language counts separately. The Auto provider's internal DeepL-to-Google fallback is one logical batch, even if both providers receive requests. |
| Limit | The licence snapshot's monthly_characters non-negative integer. Missing, boolean or zero grants authorize no characters. Plan-record changes do not rewrite issued licence snapshots. |
| Scope | One allowance shared across every activation of a licence. Sandbox, live and manual licences remain distinct. |
| Period | Calendar month in UTC, reset at 00:00 UTC on the first day; unused allowance does not roll over. Renewal within the same month does not reset usage. |
| Charge point | The service's successful authorization, before provider dispatch. It remains counted after a provider failure, local save failure, crash, interrupted request or expired authorization. There is no automatic refund. |
| Retry | An uncertain authorization for the same batch reuses a saved UUID and cannot debit twice. A new provider attempt after confirmed authorization uses a new UUID and counts again. |
| Offline | Cached signed licence grants do not authorize new character usage. Provider dispatch is blocked when online authorization cannot be confirmed. Published translations and manual editing continue; signed memory access retains its existing bounded lifetime. |
This is an allowance for characters authorized, not provider success, delivered translations, a provider billing limit or an invoice reconciliation system. A provider may bill differently. Customer-controlled PHP code and older ILS releases can bypass client enforcement; this is not tamper-proof metering of externally billed provider traffic. All sites must run ILS 0.23.0+ for this rollout, and legacy mode remains unmetered.
Data and concurrency
Migration 2026_10_08_055827_create_license_usage_tables adds licence/month totals in license_usage_buckets and accepted request identities in license_usage_events. Consume operations authenticate the installation, lock the parent licence first, revalidate active status/environment, then compare and update the balance within one transaction. Unique licence/month and licence/request constraints protect identities. The same request ID with a different installation, count or fingerprint returns HTTP 409. Exhausted requests create no debit or event.
Accepted request IDs are retained; do not prune event rows independently because replay protection depends on them. There is no retention job or usage correction/refund operation in this release. The ledger stores no raw translation text. Fingerprints are hashes of batches and should still be treated as internal metadata.
Each receipt is bound to licence, activation, request ID, fingerprint and count. It can be acknowledged for at most five minutes, capped by licence expiry and the UTC month boundary. An expired receipt is never renewed by replaying its UUID, and its debit remains. The SDK validates the online response over its configured HTTPS transport and saves acknowledgement before allowing dispatch; these receipts are not offline signed tokens. The existing signed licence-token protocol remains v2.
API and SDK
POST /api/v1/licenses/usage/consume uses the existing installation Bearer credential, never a customer login token or raw licence key.
{"request_id":"b4fdfce4-a5a8-4b9d-bb54-1d45d9ef8c33","characters":120,"fingerprint":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}
Requests require a UUID, 1–1,000,000 characters and a lowercase SHA-256 fingerprint. A successful HTTP 200 may contain approved=false with quota_exhausted or authorization_expired; callers must check the strict boolean approval. Positive responses include the complete binding, authorized_until and usage snapshot. Invalid credentials return 401, inactive/expired or wrong-environment licences 403, identity conflicts 409 and invalid input 422. Usage routes allow 60 requests/minute per credential/IP, also subject to the existing 120/minute API limit. Responses are no-store.
Activation, refresh and customer licence resources now include usage: period, limit, authorized, remaining, resets_at and as_of. The customer licence screen displays the server totals. The WordPress License screen displays a labelled cached snapshot; Check now refreshes it. Other sites may have spent allowance since that snapshot.
The additive SDK methods are Metering::batch(texts, source, target) and Client::authorizeUsage(characters, fingerprint). Existing V050 namespaces and encryption context remain compatible with saved installation state. The ILS copy remains isolated under IST\Vendor\StardethaOne\V050. A persisted usage_pending identity survives an uncertain request for the same batch; a different batch replaces it and any earlier debit remains. Only the pending batch ID, hash and count are stored locally, never its text. A positive HTTP status alone, truthy non-boolean approval, wrong binding or stale receipt cannot unlock dispatch.
Queue and recovery
The existing queue surfaces quota/network errors through its normal error/retry behavior. Quota failure can mark attempted queue items failed; after resolving the limit or connection, retry the affected job using its existing controls. This release does not add automatic month-start replay. Whole-site discovery can still create queued work while allowance is exhausted; the provider boundary enforces the debit immediately before text is sent. Manual editing and public translations do not consume allowance.
On an uncertain response, retry the same unchanged batch promptly. Once the earlier receipt expires, the SDK reports that expiry and a subsequent attempt can request a new authorization. Prior authorized characters are not refunded. Do not clear local data simply to recover quota: resets do not reset the server balance.
Upgrade and acceptance
- Back up the application database, .env, APP_KEY, signing keys and WordPress data/salts. Preserve existing configuration and encrypted installation options.
- Deploy Stardetha One 0.7.0 source and locked dependencies; run php artisan migrate --force, optimize:clear and the normal production cache steps. Production Composer installs may use --no-dev. Laravel Boost and four supporting packages were added as development dependencies to satisfy repository setup instructions; no production dependency changed.
- Check the licence's monthly_characters grant and the intended policy with the customer. Existing licences receive an empty current-month ledger; older usage is not reconstructed or backfilled.
- Upgrade all One-mode sites to ILS 0.23.0 after the service migration. The plugin fails closed for automatic generation against an older server that lacks the usage endpoint. The SDK example plugin is still optional and does not generate translations.
- Install the matching ILS documentation plugin 1.4.0/site 0.23.0 and Stardetha One docs viewer 0.7.0. Validate on staging before customer rollout.
Rollback dropping the usage tables destroys balances and replay protection. Prefer a forward fix; never run migrate:fresh on customer data. Downgrading ILS can remove client enforcement while preserving server totals, so do not present a mixed-version deployment as universally metered.
Tests exercise actual Laravel routes, database debits and the SDK/provider boundary with WordPress doubles. PostgreSQL concurrent-spend validation is included but requires the dedicated PostgreSQL test configuration. Native WordPress/MySQL/options cache/cron/admin and live provider/gateway/browser acceptance remain outstanding. Licensed WordPress update delivery is available from ILS 0.24.0 / One 0.8.0; see plugin-updates.html. No deployment or provider/gateway transaction was performed.