# WordPress App Pack Standard v1

## Distribution model

### WP Flask Bridge Core

One trusted plugin is installed on every customer WordPress site. It owns:

- site identity and short-lived RS256 JWTs;
- the generic `wp_flask_bridge_invoke()` PHP client;
- subscription and capability enforcement;
- normalized errors and safe transport;
- the signed App Pack catalog, download verification, and installation.

The v1 update, revocation propagation, and rollback workflow remains a
production-hardening task.

Custom developers need only Core and the app manual. They do not need an App
Pack to invoke platform capabilities.

### WordPress App Pack

An optional add-on for one subscribed app. It may contain:

- app-specific PHP wrappers around `wp_flask_bridge_invoke()`;
- shortcodes, dynamic blocks, admin screens, templates, JavaScript, and CSS;
- client-side validation and display components;
- WordPress hooks required by that app.

It must not contain credentials, master keys, site private keys, tenant data,
provider tokens, copied Flask business logic, or direct calls to another app.

## Source and release

Each app may declare one `WordPressPackageContract` in `APP_MANIFEST`. Source
is stored at:

```text
apps/<app_id>/wordpress-pack/<package_id>/
```

Source is never downloadable. A release consists of:

1. deterministic ZIP artifact;
2. SHA-256 digest;
3. detached Ed25519 signature over the artifact;
4. immutable app/package/version catalog record;
5. compatibility and capability metadata.

The artifact-signing private key is offline or in a managed signing service.
Bridge Core pins only the public verification key. Site JWT signing keys are
never reused for artifact signing.

## Catalog contract

The authenticated catalog returns only packs for apps included in the tenant
subscription. A record contains:

- app, package, and immutable version;
- required Core, WordPress, and PHP versions;
- capabilities used by the pack;
- artifact size, SHA-256, Ed25519 signature, and short-lived download URL;
- release time, changelog, security status, and optional revoked reason.
- a signed `plugin_file` identity such as `package-slug/entrypoint.php`.

Catalog access does not authorize app invocation by itself; the gateway still
checks every capability.

## Secure installation lifecycle

1. Administrator selects **Download** for an entitled app.
2. Core requests a fresh catalog record with site JWT.
3. Core downloads to a non-executable temporary path.
4. Core verifies size, SHA-256, and Ed25519 signature before extraction.
5. Core rejects absolute paths, traversal, links, unexpected root folders,
   excessive file counts/sizes, and an invalid manifest/entrypoint.
6. Core checks compatibility and declared capabilities.
7. WordPress installs atomically through its plugin upgrader.
8. Activation is an explicit administrator action.
9. Core records installed version and verified digest, never secret material.

The WordPress web process must be able to replace the installed App Pack
directory. Production deployments should use one deployment identity or a
shared web/deploy group with least-privilege directory permissions. Do not mix
CLI-created plugin ownership with web-based updates, and do not use globally
writable plugin directories in production.

Bridge Core uses native `WP_Filesystem` negotiation. Direct access proceeds
without another prompt. FTP, FTPS, or SSH installations render WordPress's
standard one-request credential form; the bridge never stores or returns
filesystem passwords or private keys.

Updates repeat the same verification and use WordPress's native update hooks.
Activation state is preserved, the old directory moves to WordPress's temporary
backup area, and a failed installation schedules an automatic restore on
shutdown. The Apps screen may install the nearest earlier non-revoked signed
Standard release as a manual rollback.

Revoked releases remain visible to entitled sites only as security metadata,
have no download path, cannot be installed, and produce an administrator
warning when their exact version is active. Operators revoke with:

```text
venv/bin/python scripts/revoke_wordpress_pack_release.py \
  <package_id> <version> --reason "<safe customer-facing reason>"
```

Bridge Core checks the entitlement-aware catalog twice daily and on a stale
administrator session. When a higher SemVer exists, it shows a dashboard notice
and an Apps-screen action with installed version, available version, and safe
release notes. Updates are never installed automatically.

## Runtime rules

- Packs call only the public Core API.
- Packs never read Core options or create JWTs.
- PHP wrappers return Standard v1 envelopes or `WP_Error`.
- Browser code calls WordPress REST/AJAX handlers protected by nonce; it never
  receives JWTs or provider credentials.
- Uninstalling a pack removes WordPress UI code only. Tenant data and encrypted
  credentials require separate, explicit deletion.

## Initial implementation phases

1. Contract and ABN reference source (complete).
2. Deterministic builder, external Ed25519 key, and independent verifier
   (complete).
3. Control-plane release/catalog records and authenticated download endpoint
   (complete).
4. Core verifier, safe installer, native filesystem negotiation, compatibility
   checks, periodic detection, signed update, rollback, and revocation UI
   (complete).
5. Publish ABN pack in a test channel and run installation tests (complete).

The test-channel ABN package is now downloadable only to an authenticated site
whose effective subscription includes `abn_lookup_app`.

The first signed development artifact is stored outside the repositories under
`/var/www/html/.platform-artifacts/wordpress-packs`. It is served only through
the entitlement-aware authenticated download endpoint. The private signing key is mode `0600` under
`/var/www/html/.platform-signing`; only its public key may later be pinned in
Bridge Core.

## Verified test result

The `abn_lookup_wp` release `1.0.0-dev` was synchronized by the AbsoluteMS
WordPress site, downloaded through the protected endpoint, verified by Bridge
Core, and installed successfully in the inactive state. The customer-facing
Apps screen distinguishes subscription, availability, installed, and active
states. Packages are never activated automatically.
