# Marketplace Overview

The Marketplace package connects a Capell installation to the Capell extension marketplace.

## Responsibilities

- Browse extension listings in the admin panel.
- Connect a Capell account to the local installation.
- Verify public production domains when Marketplace needs external trust.
- Request extension install and upgrade authorizations.
- Present explicit marketplace states for purchase, activation, authorized install, installed, and incompatible extensions.
- Install theme packages independently, using package metadata such as `kind`, `themeKey`, and `extends`.
- Publish Composer changes through Deployments when that package is available; otherwise show the Composer command.
- Send installed package snapshots for heartbeat and advisory checks.
- Store update advisory snapshots and per-user dismissals.

## Runtime Integration

Marketplace is a normal Capell package, but it contributes several admin surfaces at boot:

| Surface                            | Registration                                                                                                                                               |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Marketplace pages                  | `CapellAdmin::contributeToAdminSurface(AdminSurfaceContributionData::page(...))` for `MarketplacePage` and `MarketplaceExtensionDetailPage`.               |
| Installed Extensions alert/actions | `MarketplaceExtensionsPageExtender` tagged with `ExtensionsPageExtender::TAG`.                                                                             |
| Theme resource header action       | `ThemeMarketplaceHeaderActionExtender` tagged with `ResourceHeaderActionExtender::TAG`.                                                                    |
| Extensions header actions          | `ExtensionsPageActionRegistry` keys `capell-marketplace.open-marketplace`, `capell-marketplace.connect-account`, and `capell-marketplace.validate-domain`. |
| Marketplace browser                | Livewire aliases `capell-marketplace.marketplace-extensions-browser` and `capell-marketplace::marketplace-extensions-browser`.                             |
| Signed activation checks           | Container binding `capell.marketplace.activation-verifier` points to `VerifyMarketplaceSignedActivationAction::run(...)`.                                  |

Third-party packages normally do not extend Marketplace directly. They should publish accurate `capell.json` metadata, expose package settings or admin pages through Admin extension contracts, and let Marketplace read catalogue/licence state from Capell App.

## Admin Flow

The installed Extensions page is the single admin surface for installed extension management. Marketplace dynamically contributes a connection alert to that page so admins can see when the site still needs Marketplace setup and connect a Capell account from one primary action.

Marketplace must not add browse controls, install actions, or catalogue content to the installed Extensions page. Marketplace-owned surfaces render Marketplace connection status, account connection, optional public domain verification, diagnostics, heartbeat controls, and catalogue browsing only when that integration is available.

Local installed extensions remain managed from the main Extensions table. Marketplace can request install authorization and provide Composer or deployment guidance, but removal, enable, disable, and bulk uninstall actions stay on the installed Extensions table.

The Marketplace catalogue action is state-aware:

- `Purchase` opens the public purchase/detail URL when one is supplied by Capell App.
- `Activate licence` opens a confirmation form for the admin's email, verified domain, licence key, and install options.
- `Install` requests a signed install authorization and then either publishes a Composer change through Deployments or shows the Composer command.
- `Incompatible` is disabled and explains the platform mismatch.

Capell App owns the catalogue `install_state` and `primary_action` contract. The admin UI may override that state only for local facts it can prove, such as an extension already being installed or the local platform being incompatible. Purchase URLs are opened with return context (`source`, `return_url`, `composer_name`, and `instance_id`) so checkout can send the admin back to the correct installation.

## Data Model

| Table                                     | Purpose                                                                                                                                |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `marketplace_instances`                   | Connected Marketplace instance ID, encrypted signing secret, connection mode, account data, verified domains, and heartbeat timestamp. |
| `marketplace_account_connection_sessions` | Short-lived account-link sessions with hashed state/verifier values and callback status.                                               |
| `marketplace_registration_sessions`       | Public domain verification challenges, status, expiry, and last error.                                                                 |
| `marketplace_connection_domains`          | Per-domain account-link/public-verification status and diagnostics.                                                                    |
| `marketplace_install_intents`             | Requested Marketplace installs that may be resolved once Composer/deployment completes.                                                |
| `marketplace_update_advisory_snapshots`   | Last heartbeat/update response stored locally for admin notices.                                                                       |
| `marketplace_update_notice_dismissals`    | Per-user dismissal state for update/security notices.                                                                                  |

Instance IDs and signing secrets are secrets. Do not render them in public output or unauthenticated admin copy.

## Catalogue, Cache, And Install Authorization

`MarketplaceClient` is the API boundary. It sends JSON requests to the configured Marketplace API with a 10-second timeout, parses response data into package Data objects, and throws friendly runtime exceptions when the remote API is unavailable or returns the wrong shape.

Catalogue requests are cached under `capell-marketplace.marketplace.*` keys. The cache payload includes query filters and connection context from `BuildMarketplaceConnectionContextAction`: instance ID, account ID, claimed domains, publicly verified domains, and preferred domain. This matters because the same extension can show a different action when the site is unconnected, account-linked, publicly verified, already installed, or incompatible.

Install authorization is separate from catalogue browsing. `CreateExtensionAcquisitionAction` sends the selected listing, licence/email/domain input, and allowed install options to Marketplace. If an instance is known, it records an install intent with a signed payload and the current installed package snapshot. The returned Composer command is guidance only; package installation still happens through Deployments or a human running Composer.

Heartbeat/update checks use `PhoneHomeAction`. A heartbeat requires:

- a Marketplace API base URL;
- a public callback URL resolved from `CAPELL_MARKETPLACE_WEBHOOK_URL` or `APP_URL`;
- a known Marketplace instance ID from `marketplace_instances` or `CAPELL_INSTANCE_ID`;
- network access to `/instances/heartbeat`.

When heartbeat succeeds, `RecordUpdateAdvisorySnapshotAction` stores updates/advisories locally so the admin UI can render notices without asking the browser to run Composer.

## FAQ

### How does Marketplace domain verification work?

Account linking is the default connection path. The admin clicks `Connect Capell account`, signs in to Capell App, approves the requested site, and returns to the CMS. Capell App requires the account email to be verified before it issues Marketplace instance credentials.

This works for `localhost`, `127.0.0.1`, `.test`, `.localhost`, private staging hosts, public staging hosts, and production domains. Capell App stores the claimed domain on the verified account even when the domain is not publicly reachable.

Public domain validation is still available as a stronger production trust signal. It proves that Capell App can reach a public host and that the host serves the expected Marketplace challenge.

When an admin starts verification, Capell sends the requested domain, `APP_URL`, webhook URL, and any existing instance ID to Capell App. Capell App returns a registration session with a challenge ID, token, public challenge path, optional approval URL, and expiry time.

The validation modal first asks Capell App for a challenge, writes the challenge token to `public/.well-known/capell/marketplace/{challengeId}`, confirms that local file exists, and records the pending registration in `marketplace_registration_sessions`.

The next modal state shows the domain and `.well-known` challenge path. When the admin clicks `Approve and verify`, Capell checks that the local challenge file still exists before asking Capell App to verify the registration session. Capell App fetches the challenge URL from the public domain and approves the session only when the response contains the expected token.

After approval, the package stores or updates a `marketplace_instances` row with the Marketplace instance ID, signing secret, verified domain list, and heartbeat timestamp, then removes the local challenge file so stale challenges are not left publicly reachable.

### Is domain verification still needed if the admin logs in to Capell App?

Not for local development or basic account-linked Marketplace access. Login plus a verified email proves who owns the Marketplace account, and the approval flow links the claimed site to that account.

Public verification is still useful for production purchase-domain binding, webhook delivery, public reachability checks, and any Marketplace policy that requires Capell App to prove it can independently fetch the site.

The admin should not normally need to think about challenge files. Capell creates and serves the challenge during public verification. The file/path details are mainly useful when debugging server, DNS, or CDN issues.

### Why does verification fail even though the challenge file exists?

The most common cause is a host mismatch. The challenge controller only serves a pending challenge when the request host exactly matches the domain stored for the registration, after lowercasing and trimming a trailing dot.

For example, a session for `example.com` will not be served from `www.example.com`, `admin.example.com`, `localhost`, an IP address, or an internal tunnel host.

The second common cause is public web server routing. The path must be reachable from the internet at `https://your-domain/.well-known/capell/marketplace/{challengeId}` and must not be blocked by redirects, auth middleware, maintenance mode, CDN rules, static file handling, or page cache rewrites.

Expired sessions also fail. Starting a new verification expires older pending or failed sessions for the same domain and writes a fresh challenge file.

### How should I debug a domain verification issue?

Start with the exact domain shown in the Marketplace page. Check it matches the public host you expect Marketplace to verify.

Confirm the app URL and webhook URL:

```bash
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.webhook_url
php artisan config:show capell-marketplace.marketplace.base_url
```

`APP_URL` must include a valid host. Local hosts such as `capell-ruby.test` can be account-linked, but public verification must use the exact public host that Capell App can fetch.

Check the current pending session:

```sql
select domain, challenge_id, challenge_path, status, expires_at, last_error
from marketplace_registration_sessions
order by id desc
limit 5;
```

Fetch the public challenge URL from outside the server:

```bash
curl -i https://your-domain/.well-known/capell/marketplace/chal_EXAMPLE
```

The response should be `200 OK`, `Content-Type: text/plain`, and the body should be the stored challenge token. A `404` usually means the host does not match the pending session, the session expired, or the request is not reaching Laravel or the public file.

Check Laravel logs for package messages:

```bash
tail -n 100 storage/logs/laravel.log | grep capell-marketplace
```

The admin notification body is deliberately user-friendly. The log usually keeps the sharper failure reason from the registration or verification request.

### How should I debug account connection?

Check the configured app URL first:

```bash
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.base_url
```

`APP_URL` must include a host because `StartMarketplaceAccountConnectionAction` builds the callback URL from it. The callback route sits under the configured admin path and is protected by Filament admin auth.

Check the latest account connection session:

```sql
select connection_session_id, claimed_domain, app_url, callback_url, status, expires_at, completed_at, last_error
from marketplace_account_connection_sessions
order by id desc
limit 5;
```

`pending` means the admin has not returned from Capell App yet. `completing` means the callback reserved the session while exchanging the code. `failed` usually keeps the useful remote or validation error in `last_error`. `expired` means the 10-minute session window passed; start again from the Marketplace page.

If the callback says the state is invalid, do not reuse an old approval URL. Start a new connection in the same browser session and complete that new approval URL.

### How should I debug catalogue or install authorization?

Catalogue browsing and installation use different Marketplace endpoints. First confirm that the API URL is the versioned base URL:

```bash
php artisan config:show capell-marketplace.marketplace.base_url
```

Then confirm whether the site has a connected instance:

```sql
select instance_id, connection_mode, account_email, verified_domains, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;
```

If the catalogue loads but the action is `purchase`, `activate licence`, or `incompatible`, use the Marketplace response reason and local compatibility facts. Capell App owns entitlement and install-state decisions. The local UI should only override the response when it can prove a package is already installed or the platform is incompatible.

If the catalogue seems stale during local debugging, clear the local cache:

```bash
php artisan cache:clear
```

Production should prefer the Marketplace browser refresh action or the normal short TTL over broad cache clears.

### How should I debug heartbeat and update notices?

Run the checks in the same order as `PhoneHomeAction`:

```bash
php artisan config:show capell-marketplace.marketplace.base_url
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.webhook_url
```

Then check for a connected instance and recent advisory snapshots:

```sql
select instance_id, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;

select source, checked_at, capell_version, metadata
from marketplace_update_advisory_snapshots
order by checked_at desc
limit 5;
```

If there is no instance, connect the account first. If the webhook URL is null and `APP_URL` is local or malformed, set `CAPELL_MARKETPLACE_WEBHOOK_URL` to the public callback URL Capell App should use.

### What Marketplace URL should be configured?

The default Marketplace API URL is `https://capell.app/api/v1`. Only set `CAPELL_MARKETPLACE_URL` when pointing the site at a staging or self-hosted Marketplace API.

If the site reports `The route api/registration-sessions could not be found`, the URL is probably using the old unversioned API path. Clear the override or set:

```ini
CAPELL_MARKETPLACE_URL=https://capell.app/api/v1
```

Then clear cached config:

```bash
php artisan config:clear
```

### Why does Marketplace say a webhook URL is required?

Marketplace needs a public callback URL before it can send install, update, and instance events back to the site.

By default, the package derives the webhook URL from `APP_URL`. If the detected URL is not public or is not the address Capell App should call, set `CAPELL_MARKETPLACE_WEBHOOK_URL` explicitly.

### Can I verify localhost, an IP address, or a staging tunnel?

You can account-link `localhost`, IP addresses, `.test`, and `.localhost` domains. Capell App stores them against the verified account as local development aliases.

You cannot publicly verify those local-only hosts because Capell App cannot fetch them from the internet. A tunnel can be publicly verified only when it has a public hostname and that exact hostname is entered as the verification domain. For production Marketplace use, verify the final production domain.

### How do diagnostics and tracers work?

`Run diagnostics` asks Capell App to check the account-linked domains for public reachability. For local hosts such as `capell-ruby.test`, diagnostics should report that the domain is account-linked but not publicly verifiable.

For public hosts, diagnostics can store DNS, HTTP, redirect, TLS, challenge, and webhook reachability results against the local `marketplace_connection_domains` row. The Marketplace status keeps the admin copy short, while the stored diagnostics remain available for troubleshooting and future support tooling.

### What happens when I re-run verification?

Starting a new registration for a domain expires older pending or failed registrations for that same domain. Existing verified domains stay on the Marketplace instance.

Clicking `Verify` on an existing pending or failed registration retries the verification call. If it fails, the session is marked failed and stores the latest user-facing error in `last_error`.

### Why can I browse Marketplace but not install an extension?

Browsing only needs the catalogue endpoint. Installing needs a connected Marketplace instance so Capell can sign authorization requests and associate purchases, licence activation, and install telemetry with the correct account and site.

Connect a Capell account first, then run diagnostics or heartbeat. If the Marketplace response still blocks an action, use the returned reason: Capell App decides when public verification is required for a specific production action.