The app conformance suite
apps/api/src/modules/apps/conformance/sample-app.conformance.spec.ts checks that an app built with
@arc/app-kit works with ARC: it uses the API's own signers and rules, not copies of them, so a change on
either side that breaks apps fails here.
The sample app is what a developer gets: arc app init scaffolds it, and the kit's createArcAppHandler
serves it on 127.0.0.1 with test-mode events only, as arc app dev does.
| Check | Against |
|---|---|
| The scaffolded manifest is valid | The registry's parseAppManifest |
| Webhook HMAC is verified: a signed body is taken, including during a secret rotation; a tampered body, a foreign secret and a stale timestamp are refused; a retried event is handled once | signWebhookBodyWithSecrets and buildOutboxWebhookBody, with the headers webhook-delivery.service.ts sends |
Session tokens are verified: a minted token is taken once; another app's token, a foreign secret and alg: none are refused | signAppJwt |
The test-mode header is honoured: a dev tenant's and a test-mode organization's events say test and are taken; a live organization's are refused | arcEnvironment |
Running it
It is skipped unless armed, like the MCP conformance harness:
ARC_APP_CONFORMANCE=1 pnpm --filter @ims/api test src/modules/apps/conformance
The arming rails themselves always run: they check the suite stays skipped unless ARC_APP_CONFORMANCE is
exactly 1, and refuses a live leg against anything but loopback.
The live leg (your own booted API)
Add these to also run arc app dev and arc app deploy against an API on this machine:
| Variable | Value |
|---|---|
ARC_APP_CONFORMANCE_API_URL | http://127.0.0.1:<port> — loopback only; anything else is refused before the suite starts |
ARC_TOKEN | A staff access token from that API, for a user with settings:apps:manage in an org with the Custom Apps plan switch |
ARC_APP_ID | A private app in that org |
Private apps need the Custom Apps plan switch, which no stock plan includes yet, so today the live leg can
only pass against an API whose plan entitles the org to customApps.
The live leg publishes a new version of that app (numbered 0.1.<unix time>, not made current) on every
run. Run it only against your own throwaway database, never a shared one.