This guide describes the developer beta toolkit workflow for building, testing, signing, cataloging, and dry-run publishing a standalone Crypta app bundle.
Use this page with a current crypta-app launcher built from :platform-devtools. Older launchers
may expose only the base commands documented in app-dev-cli.md. Run
crypta-app --help and the subcommand help before relying on beta flags in automation.
The beta toolkit is for developer-owned app bundles outside the first-party apps/* Gradle
projects. It can scaffold a queue dashboard template, run a mock development server, execute
offline strict checks, generate local signing keys, sign and pack bundles, create and sign catalogs,
and prepare a dry-run USK publication plan. It does not make unsigned bundles trusted, bypass
catalog verification, or turn a mock development server into a live daemon install path.
Build the installed CLI launcher with Gradle:
./gradlew :platform-devtools:installDistUse the generated launcher directly from the install distribution:
platform-devtools/build/install/crypta-app/bin/crypta-app --helpOn Windows, use the matching crypta-app.bat under the generated bin directory.
The examples below assume the launcher directory is on PATH:
export PATH="$PWD/platform-devtools/build/install/crypta-app/bin:$PATH"Start external developer beta work with the stable-only template:
crypta-app init \
--dir build/dev-apps/hello-stable \
--template hello-stable \
--id org.example.hello \
--name "Hello Stable" \
--version 0.1.0The generated manifest declares api.targetStability=stable,
api.experimentalCapabilitiesAccepted=false, and app.permissions=platform.contract.read. The UI
uses the browser SDK to read GET /api/v1/platform/contract, which keeps the first local sample
inside the Platform API 1.0 stable baseline. Use
third-party-developer-beta-program.md for the complete
external developer flow and
third-party-app-submission-checklist.md before creating a
submission package.
Scaffold a standalone staged bundle with the queue dashboard template:
crypta-app init \
--dir build/dev-apps/queue-dashboard \
--template queue-dashboard \
--app-id queue-dashboard \
--name "Queue Dashboard" \
--version 0.1.0 \
--permission queue.read \
--permission queue.writeThe output is a staged app bundle directory, not a Gradle subproject. The template should create the
app manifest, launch files, browser assets, SDK copy, and local design-system assets under the
bundle directory. Keep generated work under build/ or dist/ unless you intentionally want to
check in source files.
The signed manifest remains authoritative for app id, version, launch behavior, UI mode, requested permissions, and Platform API compatibility metadata. Catalog metadata can add display and review context, but it does not replace the bundle manifest.
Start the beta development server against the staged bundle:
crypta-app dev --bundle-dir build/dev-apps/queue-dashboardThe development server is a mock app-authoring surface. It is useful for iterating on static UI, template assets, local SDK wiring, and fixture-backed queue views. It is not the real Cryptad daemon, does not install the app into AppHost, does not fetch signed catalogs, and does not prove that a live node will grant the same permissions.
For Trust Graph Local RC-style apps, the mock server exposes deterministic local fixtures for
GET /api/v1/trust-graph/status, GET /api/v1/trust-graph/anchors,
POST /api/v1/trust-graph/anchors, DELETE /api/v1/trust-graph/anchors/{fingerprint},
POST /api/v1/trust-graph/import, GET /api/v1/trust-graph/score, bounded
POST /api/v1/content/fetch, and
POST /api/v1/app-vault/identities/{identityId}/trust-statement. Those responses are fixtures for
offline UI work; they do not verify live network trust, global reputation, old WebOfTrust plugin
compatibility, or moderation behavior.
Use a real daemon when testing install, update, AppHost lifecycle, catalog refresh, browser session issuance, audit behavior, review policy, or Crypta network fetch/insert behavior. Live daemon install and update still go through signed bundle and signed catalog verification as described in app-catalogs.md and app-update-lifecycle.md.
Run the beta test wrapper in strict mode:
crypta-app test --bundle-dir build/dev-apps/queue-dashboard --strictStrict mode should be treated as the pre-signing gate. It is expected to include bundle validation, Platform API compatibility verification, and static UI lint checks. For static UI bundles, the UI lint checks cover local-resource rules, SDK/bootstrap ordering, obvious CSP mistakes, accessibility basics, permission disclosure, and design-system adoption. The base checks are documented in app-ui-design-system.md.
You can also run the base commands directly when you need narrower feedback:
crypta-app ui lint --bundle-dir build/dev-apps/queue-dashboard --strict
crypta-app validate --bundle-dir build/dev-apps/queue-dashboard --strict
crypta-app compat verify --bundle-dir build/dev-apps/queue-dashboard --strictDo not sign or catalog a bundle that fails strict checks. A warning promoted to an error usually means the bundle is relying on an unknown permission, stale compatibility metadata, missing permission disclosure, remote UI resource, or unsupported design-system pattern.
Generate local development key pairs outside the repository:
mkdir -p "$HOME/.crypta-dev/keys"
crypta-app keys generate \
--key-id dev-local-bundle \
--private-key-file "$HOME/.crypta-dev/keys/dev-local-bundle-private.der" \
--public-key-file "$HOME/.crypta-dev/keys/dev-local-bundle-public.der" \
--trusted-keys-file "$HOME/.crypta-dev/keys/trusted-app-keys.properties" \
--overwrite
crypta-app keys generate \
--key-id dev-local-catalog \
--private-key-file "$HOME/.crypta-dev/keys/dev-local-catalog-private.der" \
--public-key-file "$HOME/.crypta-dev/keys/dev-local-catalog-public.der" \
--overwriteUse different key ids for different trust roles when possible:
| Role | Example key id | What it signs |
|---|---|---|
| Bundle publisher | dev-local-bundle |
cryptad-app.digests through cryptad-app.signature. |
| Catalog publisher | dev-local-catalog |
cryptad-app-catalog.properties through cryptad-app-catalog.signature. |
| Reviewer | dev-review |
Independent review receipt payload bytes provisioned through the review-key registry. |
Local development keys are not production keys. Do not commit private keys, reviewer private keys, trusted-key registries containing private material, private insert URIs, or generated secrets.
Sign the staged bundle:
crypta-app sign \
--bundle-dir build/dev-apps/queue-dashboard \
--key-id dev-local-bundle \
--private-key-file "$HOME/.crypta-dev/keys/dev-local-bundle-private.der"The bundle signature authenticates the bundle payload. It writes the root sidecars
cryptad-app.digests and cryptad-app.signature. The digest sidecar includes the manifest and
regular bundle files and excludes reserved distribution sidecars.
Pack the signed bundle as a catalog artifact:
mkdir -p dist/apps
crypta-app pack \
--bundle-dir build/dev-apps/queue-dashboard \
--output dist/apps/queue-dashboard-0.1.0.zip \
--overwriteVerify the signed staged bundle with the trusted public key:
crypta-app verify \
--bundle-dir build/dev-apps/queue-dashboard \
--trusted-keys-file "$HOME/.crypta-dev/keys/trusted-app-keys.properties"Packing does not make an unsigned or invalid bundle trusted. The runtime catalog install path still verifies artifact size, artifact SHA-256, extracted bundle signature, and manifest id/version before AppHost receives the bundle.
Create an entry descriptor for the packed artifact:
mkdir -p dist/catalog
crypta-app catalog entry \
--bundle-dir build/dev-apps/queue-dashboard \
--output dist/catalog/queue-dashboard-entry.properties \
--artifact dist/apps/queue-dashboard-0.1.0.zip \
--bundle-uri "crypta:CHK@..." \
--summary "Inspect and manage local transfer queue state." \
--homepage "https://example.invalid/apps/queue-dashboard" \
--source "https://example.invalid/src/queue-dashboard" \
--license "MIT" \
--category productivity \
--minimum-crypta-version 1481 \
--permission-rationale "queue.read=Reads local queue state." \
--permission-rationale "queue.write=Cancels or reprioritizes selected queue entries." \
--overwriteThe descriptor is authoring input for catalog create; it is not the app manifest and is not the
signed catalog. It should name the exact ZIP artifact so the catalog writer can compute the public
artifact size and lowercase SHA-256 digest from the bytes that will be distributed.
If the beta catalog entry helper is not available in your launcher, write the descriptor manually
using the shape in app-dev-cli.md:
artifact.path=/abs/path/to/dist/apps/queue-dashboard-0.1.0.zip
bundle.uri=crypta:CHK@...
summary=Inspect and manage local transfer queue state.
name=Queue Dashboard
version=0.1.0
permissions=queue.read,queue.write
app.id=queue-dashboard
homepage=https://example.invalid/apps/queue-dashboard
source=https://example.invalid/src/queue-dashboard
license=MIT
categories=productivity
permissions.rationale.queue.read=Reads local queue state.
permissions.rationale.queue.write=Cancels or reprioritizes selected queue entries.
api.minimumVersion=1
api.maximumTestedVersion=1
api.targetStability=stable
api.experimentalCapabilitiesAccepted=falseCatalog entry metadata is for operator display, compatibility hints, and review context. Permission rationales do not grant capabilities. URI fields do not fetch automatically in the Web Shell unless the operator takes an explicit action.
Feed-style apps that target the current Platform API contract should declare content.subscribe
for /api/v1/content/subscriptions, content.fetch for POST /api/v1/content/fetch, and
app.data.read/app.data.write when they persist bounded reader state through the app-data store.
Use SDK feed, subscription, and app-data helpers such as CryptaPlatform.content.subscriptions.*,
CryptaPlatform.feed.fetchSnapshot, CryptaPlatform.feed.publishSnapshot, and
CryptaPlatform.data.records.putJson. If the app also publishes generated feed documents, add
content.insert.app-document, queue.write, and queue.read with permission rationales. Do not
write raw feed bodies, raw request bodies, private insert URIs, app/browser tokens, form passwords,
or local paths into catalog descriptors, mock fixtures, UI lint JSON, or release evidence.
A review receipt is independent reviewer evidence. It is not the bundle signature and not the catalog signature. Create one before catalog creation when your policy requires review evidence:
crypta-app review sign \
--catalog-entry dist/catalog/queue-dashboard-entry.properties \
--receipt-file dist/catalog/queue-dashboard-review.properties \
--reviewer-key-id dev-review \
--reviewer-private-key-file /abs/path/outside/repo/dev-review-private.der \
--policy-id crypta-app-review-v1 \
--policy-version 1 \
--status reviewed \
--note "Reviewed for local beta testing." \
--overwriteVerify it with a trusted reviewer-key registry:
crypta-app review verify \
--catalog-entry dist/catalog/queue-dashboard-entry.properties \
--receipt-file dist/catalog/queue-dashboard-review.properties \
--trusted-reviewer-keys-file /abs/path/outside/repo/trusted-reviewers.propertiesThe receipt binds a reviewer decision to the app id, app version, artifact digest, artifact size,
reviewer policy, reviewer key id, timestamps, and optional evidence. A trusted rejected receipt
is still not a positive review. Publisher-advisory review.status=reviewed is not the same thing
as a trusted review receipt.
Create the catalog properties file:
crypta-app catalog create \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--catalog-id dev-queue-dashboard \
--name "Development Queue Dashboard" \
--entry dist/catalog/queue-dashboard-entry.properties \
--review-receipt dist/catalog/queue-dashboard-review.properties \
--overwriteSign the exact catalog bytes:
crypta-app catalog sign \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--key-id dev-local-catalog \
--private-key-file "$HOME/.crypta-dev/keys/dev-local-catalog-private.der"Verify the catalog and sibling signature:
crypta-app catalog verify \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--trusted-key-id dev-local-catalog \
--trusted-public-key-file "$HOME/.crypta-dev/keys/dev-local-catalog-public.der"The catalog signature authenticates the exact bytes of
dist/catalog/cryptad-app-catalog.properties. Do not sort, rewrite, or reformat that file after
signing. A valid catalog signature authenticates catalog bytes and publisher metadata only; it does
not authenticate the extracted app payload, does not grant Platform API permissions, and does not
make reviewer claims trusted.
Prepare a dry-run catalog publication plan:
crypta-app publish-usk \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--catalog-signature-file dist/catalog/cryptad-app-catalog.signature \
--catalog-source "crypta:USK@.../apps/dev-queue-dashboard/cryptad-app-catalog.properties" \
--output dist/catalog/publish-plan.md \
--dry-runDry-run mode must not insert catalog bytes, signature bytes, or app artifacts into Crypta. Treat it as a plan and validation step: it should confirm which files would be published, which USK path would be used, which public source URI operators would add, and whether the catalog and signature sidecar files are present and parse.
Live insertion is different. A live publish uses an insert-capable key, contacts a real localhost daemon, consumes network resources, and may make the catalog source discoverable to anyone who knows the corresponding public URI. The release/operator form is explicit:
crypta-app publish-usk --live \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--catalog-signature-file dist/catalog/cryptad-app-catalog.signature \
--catalog-source "crypta:USK@.../apps/dev-queue-dashboard/cryptad-app-catalog.properties" \
--private-insert-uri-env CRYPTAD_FIRST_PARTY_CATALOG_INSERT_URI \
--node-base-url http://127.0.0.1:8888/api/v1 \
--form-password-env CRYPTAD_CERT_FORM_PASSWORD \
--trusted-keys-file "$HOME/.crypta-dev/keys/trusted-app-keys.properties" \
--output dist/catalog/live-publication-summary.jsonThe private insert URI and form password must come from an environment variable or protected file,
not from a positional argument. Live mode publishes cryptad-app-catalog.properties and the
cryptad-app-catalog.signature sibling at the same USK path and edition, then writes a sanitized
summary. The summary may include the public crypta:USK@.../cryptad-app-catalog.properties
source, digest values, catalog id, signing key id, and queue status, but it must not include private
insert URIs, private keys, form passwords, tokens, raw request bodies, or absolute staging paths.
The private insert URI must be the matching private USK directory insert URI for the configured
public source parent. For example,
crypta:USK@PUBLIC.../catalog/42/cryptad-app-catalog.properties is inserted from the secure
private USK root for the same catalog/42 parent path, supplied only through the env/file option.
Because the queue insert path reads staged disk files after the request is queued, the CLI retains
the two public sidecar files until the live insert has finished consuming them. --verify-live-fetch
proves the currently fetched public catalog and signature match the local signed files, but it is not
treated as proof that the queued insert has consumed the staged directory.
Keep the beta toolkit boundaries explicit:
| Boundary | What it means | What it does not mean |
|---|---|---|
| Mock dev server | Local fixture-backed UI and SDK iteration. | Real daemon install, real AppHost launch, live catalog refresh, or network insertion. |
| Real daemon | Runtime Platform API, AppHost lifecycle, browser-session issuance, audit, catalog refresh, and Crypta transport. | Automatic trust in unsigned bundles or untrusted catalogs. |
| Dry-run publish | Local validation and publication plan. | Live insertion or public availability. |
| Live insertion | Bytes are submitted to Crypta through a real insert path. | Catalog or bundle trust; signatures still verify locally. |
| Catalog signature | Authenticates catalog properties bytes and publisher metadata. | Bundle payload trust or reviewer trust. |
| Bundle signature | Authenticates the staged app payload through digest and signature sidecars. | Catalog publisher trust or reviewer approval. |
| Review receipt | Authenticates independent reviewer evidence with a trusted reviewer key. | App signing authority, catalog signing authority, or permission grant. |
crypta: URI |
Transport location for catalog sidecars or immutable CHK artifacts. | Trust boundary. Signatures, sizes, and digests remain mandatory. |
Catalog sources may use crypta: USK/SSK paths for catalog sidecars or CHK companion forms for
immutable catalog bytes. App ZIP artifacts published through catalogs must use supported artifact
URI forms documented in app-catalogs.md. A
crypta:CHK@... artifact URI is only a byte transport. The runtime still enforces the catalog
declared size and SHA-256 before extracting and verifying the signed bundle.
AppHost launch tokens and app browser sessions are separate credentials.
An AppHost process receives a fresh CRYPTAD_APP_TOKEN at launch. Process-originated Platform API
requests authenticate with that token and then pass the central capability matrix. The token must
not appear in browser bootstrap JSON, logs, diagnostics, catalog responses, audit entries, or
operator-facing errors.
An app-owned static UI receives a short-lived browser session through the app bootstrap route and
uses it as X-Crypta-App-Session. The browser session is origin-bound to the installed app UI and
is not an AppHost process token. The browser SDK keeps it in memory and must not persist it to
localStorage, sessionStorage, cookies, or catalog metadata.
A valid process token or browser session is still not enough by itself. The requested route must allow the app principal and the signed manifest must declare the required capability. Missing capability returns an authorization failure even when authentication succeeds.
Static app UI should use local bundle assets only. Load the browser SDK from
./crypta-platform.js and the design-system assets from ./crypta-ui/. Do not add CDN CSS,
remote JavaScript, third-party font loads, or silent screenshot/image fetches from catalog
metadata.
crypta-app test --strict and crypta-app ui lint --strict should fail static UI bundles that
violate the local-resource model, omit permission disclosure, drift from SDK/bootstrap ordering, or
depend on unsupported design-system patterns. The lint is an offline developer gate. Runtime
authorization, token handling, and catalog verification still happen server-side.
Bundle manifests should declare Platform API compatibility metadata:
api.minimumVersion=1
api.maximumTestedVersion=1
api.targetStability=stable
api.experimentalCapabilitiesAccepted=falseStable-only permission sets keep experimental capability acceptance disabled and set
api.targetStability=stable. The scaffolder derives the target from the final permissions after
template defaults and repeatable --permission values are merged. If an added permission is
app-facing experimental or outside the Platform API 1.0 stable baseline, the manifest uses
api.targetStability=experimental and enables api.experimentalCapabilitiesAccepted=true.
Operator-only and internal permissions are rejected during scaffolding. The vault-profile
template uses the experimental target because its default vault.* permissions exercise
experimental app-vault capabilities.
Run compatibility checks before signing:
crypta-app api snapshot --output build/platform-api-contract.json
crypta-app compat verify \
--bundle-dir build/dev-apps/queue-dashboard \
--contract build/platform-api-contract.json \
--strict
crypta-app compat verify \
--catalog-entry dist/catalog/queue-dashboard-entry.properties \
--contract build/platform-api-contract.json \
--strictThe verifier checks declared API versions, optional and experimental capability use, deprecated or
scheduled capabilities, and catalog descriptor mismatches against the referenced bundle. Warnings
become failures with --strict.
- app-platform-developer-portal.md is the app ecosystem beta entry point.
- app-platform-beta-tutorials.md gives copyable offline beta tutorials.
- app-platform-beta-known-limitations.md records beta limits and safety boundaries.
- app-platform-beta-program.md covers app submission, feedback, and release closeout.
- third-party-developer-beta-program.md covers the external
developer beta workflow from
hello-stablethrough catalog candidate. - third-party-app-submission-checklist.md is the public pre-submission checklist for manifest, UI, compatibility, rationale, redaction, and resubmission evidence.
- platform-api-compatibility-support-window.md defines stable baseline, experimental opt-in, deprecation, and scheduled-removal expectations.
- app-store-submission-and-review-workflow.md documents submission packages, pre-review, decisions, receipts, and catalog candidates.
- app-dev-cli.md documents the base standalone CLI workflow.
- app-distribution.md documents signed bundle sidecars and runtime trusted key inputs.
- app-catalogs.md documents catalog format, source transport, artifact verification, and review trust.
- first-party-beta-catalog.md covers first-party catalog publication and Web Shell onboarding.
- app-ui-design-system.md documents static UI assets and offline lint.
- platform-sdk-js.md documents the browser SDK and browser session handling.
- examples/third-party-hello-stable.md shows the
stable-only SDK call used by the
hello-stabletemplate. - app-permissions-and-audit.md documents process principals, browser principals, permission checks, and audit redaction.
- platform-api-contract.md documents API compatibility snapshots and verifier behavior.