> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tappify.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Review and what gets checked

> The checks a release has to pass, and what a Tappify reviewer reads on top of them.

[`tappify extension doctor`](/extensions/test/doctor-checks) runs these checks on your
machine, and Tappify runs them again on the bundle you upload, so nothing here should be a
surprise.

```bash theme={null}
tappify extension doctor
```

## What the manifest has to pass

| Check                     | Looks at                                                                                                                    | Fix                                                                                |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `manifest.schema`         | The manifest against the published JSON Schema, with every rule the schema adds on top of its field types                   | Fix the field the message names                                                    |
| `manifest.id.reserved`    | An id that would shadow a Tappify route                                                                                     | Choose another id; an id is immutable after the first publish                      |
| `scopes.security_review`  | Your vendor's security-review flag, for `ai:actions`, `store.metadata:write`, `autopilot:trigger` and `messaging:send`      | Contact Tappify; the flag is set after a review of your company, or drop the scope |
| `exposes`                 | Every UI contribution has a matching expose in the build                                                                    | Rebuild; the entry is missing from `dist/`                                         |
| `assistant_text.override` | Tool, action, mention, skill, prompt and context-provider text, for instructions that try to override the assistant's rules | Rewrite the text so it describes your product                                      |

`manifest.schema` is one id carrying the whole schema, so these all arrive under it:

* A widget's slot that is not on its page, and a series naming a metric your connector does
  not declare.
* Contributions that imply a scope you did not declare — rendered UI needs `ui:render`,
  hosted storage needs `storage:write`, markers need `ui:render` and `alerts:write`, an
  action needs `ai:actions` and its own declared scope.
* A scope whose definition asks for a justification, without one of 20 to 500 characters.
* Limits: 20 UI contributions, 15 assistant tools, 10 actions, 10 storage collections,
  5 skills, 20 prompts, 3 context providers, 30 procedures, 20 telemetry names.
* A banner or marker naming an event your webhooks do not declare.
* A storage collection called `settings`, which the settings contribution owns.
* A prompt whose `after` names a tool id that is not yours.
* A telemetry name that is not `snake_case`.
* `server.baseUrl` missing when a connector, webhook, assistant contribution, work
  destination or procedure exists, and any declared host that is not `https`.
* An `sdk` range naming no major Tappify serves.
* A `public` extension without all five listing fields.
* An `organization`-scoped extension declaring pages, tabs, widgets or series.
* A `pricing` or `score` block, which the schema reserves and does not accept yet.

## What the bundle has to pass

| Check                       | Looks at                                                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `bundle.size`               | The zip is at most 5 MB. The message also names the limit on what it unpacks to                                                       |
| `bundle.manifest`           | `tappify.extension.json` is at the root of the zip and parses. `publish` packs it for you, so this one is for a zip built another way |
| `bundle.remote_entry`       | `remoteEntry.js` is present                                                                                                           |
| `bundle.icon`               | Present, png or svg, at most 512 KB, square                                                                                           |
| `bundle.storage_schemas`    | Every `$ref` resolves to a file in the bundle, parses, and does not cycle                                                             |
| `bundle.listing_assets`     | For a public listing: every screenshot is in the bundle at 1600 by 1000                                                               |
| `bundle.third_party_script` | No `<script src>` to another origin                                                                                                   |
| `bundle.external_font`      | No `@font-face` or `@import` pointing off-origin                                                                                      |
| `bundle.css_asset`          | The build emitted no stylesheet as a separate file, which would never reach your shadow root                                          |
| `bundle.assistant_text`     | Bundled markdown the assistant reads, scanned for the same override patterns                                                          |

## What your server has to answer

There is no check id for this one. After the bundle passes, Tappify asks
`<base url>/tappify/health` for a 200 within three seconds, on `server.baseUrl` and on
`server.sandboxBaseUrl` if you declared one. A redirect is a failure, and the publish stops
there. See [Deploy your server](/extensions/build/deploy-your-server).

## What does not stop a publish

Three rules are warnings. They print under `doctor` and a release goes out with them:
`storage.pii`, a storage schema field named `email`, `name`, `phone`, `address` or `ip`,
which a reviewer reads before it goes live; `procedures.write_kind`, a write-shaped procedure
name without `kind: "write"`; and `bundle.style_injection`, built code that writes styles
into the document. A publish reports only what stopped it, so `doctor` is where you read
them.

Your typecheck, lint, tests and accessibility pass are not run at publish — `publish` runs
`doctor` with your own scripts left out, because it has just run your build. `doctor` runs
them, and they are the reason to run it before you publish rather than after.

`doctor` also checks the files on your disk before they ever reach a zip — a missing entry,
an icon that is not square, a screenshot at the wrong size, a `$ref` that points nowhere, a
stale `src/tappify.d.ts`. Those ids are on
[Doctor checks](/extensions/test/doctor-checks).

## What a reviewer reads

Automated checks decide whether a release can be reviewed at all. A person then reads:

* Every scope you asked for against the justification you wrote, and whether the
  contributions need them.
* The diff against your live release: which scopes are new, which contribution kinds are
  new, and whether the release widens who can install.
* Every word your extension puts in front of the assistant — tool descriptions, skills,
  prompt templates, knowledge files.
* Your listing, screenshots and privacy URL, for a public extension.
* Your release notes.

A decision reaches your vendor admins in their inbox, and the Releases page in the developer
portal shows it. A rejection carries a reason. Fix what the reason names and publish again;
the next publish re-enters review.

Unlisted extensions get the automated checks only; no person reads them. Private extensions
are never reviewed.
