# QR Service operator and integration manual

## Boundary

The service stores generic references and controlled metadata only. The owner
app remains responsible for resource existence, private content, and final
authorization. QR tokens contain no IDs or claims; only their SHA-256 hashes
are in QR tables. To support true reprinting, the original token is retained
encrypted in the platform tenant secret vault and is never returned by list or
read operations.

## Policies and lifecycle

V1 supports `permanent_revocable`, `fixed_expiry`, `relative_expiry`,
`single_use`, `limited_use`, `limited_scan`, and `dynamic_session`. Policies can
also bound scans/successful uses, require delegated authentication, delay
validity, and set resolution TTL. A scan is a valid resolution attempt; a
successful use occurs only when the owner app consumes an authorized context.

Transitions are draft→active, active→suspended/expired/consumed/revoked/
replaced, and suspended→active/revoked/replaced. Terminal records cannot be
reactivated. Regeneration creates a new token and record, then marks the old
record replaced with bidirectional lineage.

## Flask and WordPress use

Owner apps call registered capabilities through `platform.interapp.call_capability`.
Administrators first configure that app's operations, resource-type and action
allowlists with `configure_permission_v1`. Tenant and caller are always taken
from trusted context, never the request body.

WordPress uses the signed App Pack wrappers, for example:

```php
$result = ams_qr_create(array(
  'display_name' => 'Generic access QR',
  'resource_type' => 'generic_resource',
  'resource_reference' => 'opaque-reference-001',
  'owner_app' => 'example_owner_app',
  'action_key' => 'view_resource_v1',
  'policy' => array('kind' => 'permanent_revocable'),
  'idempotency_key' => wp_generate_uuid4(),
));
```

Bridge Core performs the signed trusted invocation, live subscription check,
app enablement and quota enforcement. PHP never handles cryptography or QR
counters.

## Resolution and delegated authentication

`/q/<opaque-token>` begins resolution and returns only a short-lived opaque
resolution token and generic status. The owner app calls
`consume_resolution_v1`; only then does it receive its original generic
reference and successful-use accounting occurs. Tokens and unavailable states
never reveal tenant, owner app, resource, status reason, or expiration.

For authenticated policies set `QR_CHALLENGE_APP` to a registered service that
implements `create_verification_challenge_v1` and `verify_challenge_v1`.
`qr_service` stores only `challenge_id`, resolution and correlation IDs. The
challenge service owns OTP generation, hash, five-minute expiry, attempts,
resends and messaging delivery. This repository currently has no such stable
service; `messaging_service` is transport-only, so authenticated QR resolution
returns `challenge_service_unavailable` until one is installed.

## Security and operations

Set `QR_PUBLIC_BASE_URL` to the canonical HTTPS portal URL and configure the
platform secret keyring. Never log public/resolution tokens, OTP, recipients or
private payloads. Public responses use no-store, anti-framing, nosniff and
no-referrer headers. Redirect URLs and callbacks are not accepted; routing is
the registered `owner_app` plus `action_key`. App permissions, subscription,
tenant isolation, idempotency, rate limiting and sanitized audit are enforced
server-side.

Install dependencies, run the control-plane migration, add `qr_service` to the
tenant subscription/app binding, configure owner permissions, and build/sign
the App Pack with the existing platform scripts. A future private-demo plugin
needs only its own content table and registered owner action; it must not add
domain data to this service.
