This document describes the crypta-app command for scaffolding, validating, signing, packing,
and cataloging standalone AppHost bundles.
Use crypta-app for developer-owned bundles that live outside the first-party apps/* Gradle
projects. The command works on a staged bundle directory containing cryptad-app.properties,
launch files, and optional static/ assets. The scaffold is a standalone staged bundle directory,
not a new Gradle subproject.
The signing, packing, validation, and catalog commands are offline filesystem tooling. The beta toolkit adds a local mock development server for static app UI iteration, but it is not hot reload and it is not a daemon-side install command. Install and update flows still go through the Platform API or signed catalog source handling described in app-catalogs.md.
First-party repo apps can keep using their existing Gradle tasks. See First-party Gradle workflow.
For the PR-225 beta sidecar workflow, including init --template queue-dashboard, the mock
development server, strict beta test wrapper, local key generation, and publish-usk --dry-run,
see developer-beta-toolkit.md.
crypta-app is delivered by the :platform-devtools application plugin. Build an installed
launcher with:
./gradlew :platform-devtools:installDistThe generated script is under the installDist output, typically:
platform-devtools/build/install/crypta-app/bin/crypta-app --helpOn Windows, use the matching crypta-app.bat launcher from the generated bin directory.
You can also run the application plugin task directly while iterating:
./gradlew :platform-devtools:run --args='--help'
./gradlew :platform-devtools:run --args='validate --bundle-dir build/dev-apps/hello-queue'Command shape:
crypta-app init \
--dir <staged-dir> \
--app-id <app-id> \
--name <display-name> \
--version <version> \
[--template static-basic|queue-dashboard|publisher|vault-profile] \
[--ui-mode static|shell-panel|none] \
[--permission <capability>] \
[--overwrite]Example:
crypta-app init \
--dir build/dev-apps/hello-queue \
--app-id hello-queue \
--name "Hello Queue" \
--version 0.1.0 \
--ui-mode static \
--permission queue.read \
--permission queue.writeRepeat --permission for each Platform API capability the app needs. Current capability names are
listed in app-permissions-and-audit.md. The developer tooling also
recognizes the app-vault capability names vault.secrets.read, vault.secrets.write,
vault.identities.read, vault.identities.create, vault.identities.use, and
vault.identities.manage; see app-secret-and-identity-vault.md
before requesting them. Use --overwrite only when you deliberately want to replace an existing
scaffolded directory.
The static scaffold is expected to produce a bundle shaped like:
build/dev-apps/hello-queue/
cryptad-app.properties
bin/start.sh
static/index.html
static/app.js
static/app.css
static/crypta-platform.js
static/crypta-ui/crypta-ui-tokens.css
static/crypta-ui/crypta-ui.css
static/crypta-ui/crypta-ui-components.js
When the browser SDK resource is available, the static template copies or vendors it as
static/crypta-platform.js. Static pages should load it with ./crypta-platform.js; see
platform-sdk-js.md. The static template also copies the canonical Crypta UI
design-system assets under static/crypta-ui/ and links the token stylesheet, base stylesheet, and
app stylesheet in that order. See app-ui-design-system.md for the
supported cr-* vocabulary, local-resource rules, and permission-disclosure guidance. If the
scaffold is created with --ui-mode none, no browser UI is declared. If it uses
--ui-mode shell-panel, the manifest points at a shell-panel entry instead of an app-owned static
route.
--template defaults to static-basic, which preserves the minimal static scaffold. The beta
templates queue-dashboard, publisher, and vault-profile are static-only examples that vendor
the same SDK and design-system assets, declare the permissions they demonstrate, and include visible
permission disclosure. They are intended as staged bundles for app authors, not new Gradle
subprojects. The publisher template uses the same sourcePath, insertUri, and identifier
form fields accepted by /api/v1/queue/inserts/file; it does not read local files through browser
file inputs. The vault-profile template sets api.experimentalCapabilitiesAccepted=true because
the current vault capabilities are experimental. See
developer-beta-toolkit.md for the complete mock-dev, offline-test,
signing, catalog, and dry-run publication flow.
A static scaffold should produce a manifest with fields like:
manifest.version=1
app.id=hello-queue
app.name=Hello Queue
app.version=0.1.0
app.exec=bin/start.sh
app.ui.mode=static
app.ui.entry=static/index.html
sandbox.mode=none
sandbox.required=false
app.permissions=queue.read,queue.write
api.minimumVersion=1
api.maximumTestedVersion=1
api.experimentalCapabilitiesAccepted=false
quota.data.bytes=0
quota.cache.bytes=0
app.restart.policy=never
app.restart.maxAttempts=0
app.restart.backoff.ms=0The manifest is part of the signed payload. Change app id, version, executable path, UI fields, or permissions before signing, then validate again.
Validate the staged bundle before signing:
crypta-app validate --bundle-dir build/dev-apps/hello-queueAdd --strict when unknown manifest permissions, compatibility warnings, or
newer-than-tested contract targets should fail the command instead of producing a warning. Strict
validation also runs the static UI linter for app.ui.mode=static bundles.
Run the UI linter directly while iterating on app-owned static UI:
crypta-app ui lint --bundle-dir build/dev-apps/hello-queue
crypta-app ui lint --bundle-dir build/dev-apps/hello-queue --strict
crypta-app ui lint \
--bundle-dir build/dev-apps/hello-queue \
--strict \
--json build/dev-apps/hello-queue-ui-lint.jsoncrypta-app ui lint is offline. It checks the staged static UI for local-resource compatibility,
obvious CSP violations, SDK/bootstrap ordering, accessibility basics, permission disclosure, and
design-system adoption. JSON output uses bundle-relative paths so reports can be archived without
developer-specific local paths.
When a static app declares vault capabilities, crypta-app init writes the same names into the
visible permission disclosure and crypta-app ui lint --strict checks that the disclosure still
matches app.permissions. Keep the disclosure path-free and value-free: show capability names and
operator-facing rationale, not secret values, identity private keys, seed phrases, or recovery
phrases.
Sign it with a local development key:
crypta-app sign \
--bundle-dir build/dev-apps/hello-queue \
--key-id dev-local \
--private-key-file /abs/path/to/dev-app-signing-private.pemThis writes cryptad-app.digests and cryptad-app.signature beside
cryptad-app.properties. Keep private keys outside the repository. Prefer --private-key-file or
the CLI's environment-variable key input over putting private key bytes on the shell command line.
Pack the signed staged bundle for catalog distribution:
mkdir -p dist/apps
crypta-app pack \
--bundle-dir build/dev-apps/hello-queue \
--output dist/apps/hello-queue-0.1.0.zip \
--overwritecrypta-app pack writes catalog-compatible ZIP artifacts. It rejects bundles with more than 4096
regular-file entries, because the catalog install/update path uses the same entry cap before
extracting artifacts.
Verify the signed staged bundle with the matching trusted public key:
crypta-app verify \
--bundle-dir build/dev-apps/hello-queue \
--trusted-key-id dev-local \
--trusted-public-key-file /abs/path/to/dev-app-signing-public.pemThe catalog install path verifies the catalog signature, the ZIP artifact size and SHA-256, and the extracted bundle signature before AppHost installs or updates the app. Packing an unsigned bundle does not make it trusted.
Generate the current offline Platform API compatibility snapshot:
crypta-app api snapshot --output build/platform-api-contract.jsonVerify a staged bundle against the built-in current contract:
crypta-app compat verify --bundle-dir build/dev-apps/hello-queueVerify against an explicit target contract, such as a release-candidate snapshot:
crypta-app compat verify \
--bundle-dir build/dev-apps/hello-queue \
--contract build/platform-api-contract.jsonThe verifier checks unknown manifest permissions, unknown optional capabilities, malformed
api.* metadata, app-declared minimum contract versions above the target, target contract versions
above the app's maximum-tested version, experimental capability use without
api.experimentalCapabilitiesAccepted=true, and deprecated or scheduled capabilities. It also
checks catalog entry descriptors against the referenced bundle when --catalog-entry is used.
Warnings become failures with --strict.
crypta-app catalog create can build a cryptad-app-catalog.properties file from one or more app
entry descriptors. A descriptor is CLI input, not the installed app manifest and not the generated
catalog entry. It points at a local ZIP artifact so the catalog writer can compute the public
catalog size and SHA-256 fields from the exact bytes:
# catalog-entry.properties
artifact.path=/abs/path/to/dist/apps/hello-queue-0.1.0.zip
bundle.uri=https://example.invalid/apps/hello-queue-0.1.0.zip
summary=Example static UI that reads the local queue.
name=Hello Queue
version=0.1.0
permissions=queue.read,queue.write
app.id=hello-queue
homepage=https://example.invalid/apps/hello-queue
source=https://example.invalid/src/hello-queue
license=MIT
categories=productivity,network
minimumCryptaVersion=1481
review.status=reviewed
review.note=Reviewed for local operator safety.
permissions.rationale.queue.read=Reads the local transfer queue.
permissions.rationale.queue.write=Lets the app cancel or reprioritize requests.
screenshot.1=https://example.invalid/assets/hello-queue-1.png
changelog.summary=Adds queue retry controls.
changelog.uri=https://example.invalid/apps/hello-queue-0.1.0-changelog.txt
api.minimumVersion=1
api.maximumTestedVersion=1
api.optionalCapabilities=alerts.read,diagnostics.read
api.experimentalCapabilitiesAccepted=falseA Site Publisher local catalog descriptor should include explicit permission rationales:
artifact.path=/abs/path/to/dist/apps/site-publisher-1.0.0.zip
bundle.uri=file:/abs/path/to/dist/apps/site-publisher-1.0.0.zip
summary=Reference app for publishing a local static site through Crypta.
name=Site Publisher
version=1.0.0
permissions=queue.read,queue.write,content.insert
app.id=site-publisher
homepage=https://example.invalid/apps/site-publisher
source=https://example.invalid/src/site-publisher
license=GPL-3.0-only
categories=publishing,content
review.status=reviewed
review.note=First-party content reference app.
permissions.rationale.content.insert=Submits selected local site content to the insert pipeline.
permissions.rationale.queue.write=Creates insert requests for the publish operation.
permissions.rationale.queue.read=Displays publish progress from the local transfer queue.
changelog.summary=Adds the first content reference app.
api.minimumVersion=3
api.maximumTestedVersion=5
api.experimentalCapabilitiesAccepted=falseDo not include vault.identities.* permissions for Site Publisher. Profile Publisher owns the
identity-profile reference flow. A Profile Publisher catalog descriptor should include the
route-specific vault and generated-document rationale:
artifact.path=/abs/path/to/dist/apps/profile-publisher-1.0.0.zip
bundle.uri=file:/abs/path/to/dist/apps/profile-publisher-1.0.0.zip
summary=Reference app for publishing an identity-bound profile document.
name=Profile Publisher
version=1.0.0
permissions=queue.read,queue.write,content.insert.app-document,vault.identities.read,vault.identities.create,vault.identities.use
app.id=profile-publisher
homepage=https://example.invalid/apps/profile-publisher
source=https://example.invalid/src/profile-publisher
license=GPL-3.0-only
categories=publishing,identity
review.status=reviewed
review.note=First-party profile reference app.
permissions.rationale.vault.identities.create=Creates an app-owned profile identity without exporting private material.
permissions.rationale.vault.identities.use=Uses the profile-document route for identity-bound profile publishing.
permissions.rationale.content.insert.app-document=Queues the generated profile document through app-document insert without local source-path authority.
permissions.rationale.queue.write=Creates the generated document insert request.
permissions.rationale.queue.read=Displays publish progress from the local transfer queue.
changelog.summary=Adds the first identity-profile reference app.
api.minimumVersion=5
api.maximumTestedVersion=5
api.experimentalCapabilitiesAccepted=trueDo not include tokens, form passwords, private insert URIs, raw request bodies, private keys, signatures, or absolute staging paths in descriptor notes, generated catalog metadata, or release evidence.
Only artifact.path, bundle.uri, and summary are required. The writer derives the catalog app
id and version from the ZIP artifact's root cryptad-app.properties; descriptor app.id and
version values are optional consistency checks and must match the artifact manifest. The name
and permissions fields can override the display metadata and permission hints written to the
catalog.
Descriptors can also author optional app-store metadata:
| Descriptor property | Generated catalog property |
|---|---|
homepage |
app.<id>.homepage |
source |
app.<id>.source |
license |
app.<id>.license |
categories |
app.<id>.categories |
minimumCryptaVersion |
app.<id>.minimumCryptaVersion |
review.status |
app.<id>.review.status |
review.note |
app.<id>.review.note |
permissions.rationale.<permission> |
app.<id>.permissions.rationale.<permission> |
screenshot.N |
app.<id>.screenshot.N |
changelog.summary |
app.<id>.changelog.summary |
changelog.uri |
app.<id>.changelog.uri |
api.minimumVersion |
app.<id>.api.minimumVersion |
api.maximumTestedVersion |
app.<id>.api.maximumTestedVersion |
api.optionalCapabilities |
app.<id>.api.optionalCapabilities |
api.experimentalCapabilitiesAccepted |
app.<id>.api.experimentalCapabilitiesAccepted |
These fields are optional. A descriptor and artifact with no app-store metadata and no API
compatibility metadata generate a minimal catalog.version=1 catalog. Descriptors that include
any app-store metadata, or descriptors/artifacts that declare API compatibility metadata, generate
catalog.version=2 so strict v1 catalog consumers reject the expanded schema cleanly instead of
accepting unknown fields.
homepage, source, screenshot.N, and changelog.uri are URI metadata for operator display.
review.status and review.note are advisory and do not replace signed catalog or signed bundle
verification. minimumCryptaVersion is advisory and does not block install/update by itself;
integer Cryptad build labels are the comparable form used by Platform API responses. Permission
rationales explain declared permissions; they do not grant capabilities.
API compatibility metadata is advisory. The signed bundle manifest remains authoritative for the
app artifact, and catalog-vs-bundle mismatches are reported by crypta-app compat verify.
Independent review receipts are separate from descriptor review.status and review.note. A
receipt signs a canonical payload that binds the reviewer decision to the descriptor's app id,
version, artifact SHA-256, artifact size, reviewer policy, reviewer key id, review timestamps, and
optional evidence metadata. The receipt is later embedded into the signed catalog with
--review-receipt, but the catalog signature does not make the receipt trusted by itself.
artifact.path is local authoring input and is not written to the public catalog. Do not change
the ZIP after creating the catalog; the generated catalog records its size and lowercase SHA-256
digest.
bundle.uri is the public ZIP artifact location written into the signed catalog entry. Current
catalog entries can use file:, https:, or loopback http: artifact URIs. crypta: catalog
sources are supported by the runtime catalog refresh flow, but bundle.uri=crypta:... is rejected
unless platform-appcatalog adds explicit Crypta artifact fetching. Keep Crypta keys for the
catalog properties and catalog signature sidecars, not for app bundle artifacts.
Create and verify an independent app review receipt before signing the catalog:
crypta-app review sign \
--catalog-entry catalog-entry.properties \
--receipt-file review-receipt.properties \
--reviewer-key-id crypta-first-party-review \
--reviewer-private-key-file /abs/path/to/reviewer-private.pem \
--policy-id crypta-app-review-v1 \
--policy-version 1 \
--status reviewed \
--evidence-file review-evidence.json \
--overwrite
crypta-app review verify \
--catalog-entry catalog-entry.properties \
--receipt-file review-receipt.properties \
--trusted-reviewer-keys-file /abs/path/to/trusted-reviewers.propertiesReviewer private keys are local reviewer material, not app or catalog signing keys. Do not commit them. The trusted reviewer keys file is distinct from the AppHost trusted app-key file:
trusted.reviewers.version=1
reviewer.1.id=crypta-first-party-review
reviewer.1.algorithm=Ed25519
reviewer.1.public.key.base64=<X.509 Ed25519 public key bytes>
reviewer.1.display.name=Crypta First-Party Review
reviewer.1.policy.id=crypta-app-review-v1review sign accepts --reviewer-private-key-base64, --reviewer-private-key-file, or
--reviewer-private-key-env. --evidence-file records the file's SHA-256 in the receipt; use
--evidence-sha256 when the evidence digest was computed elsewhere. Verification fails closed for
app id/version mismatches, artifact digest or size mismatches, unknown reviewer keys, expired
receipts, invalid signatures, malformed fields, and unsupported algorithms. A verified rejected
receipt is trusted evidence but is not a positive review.
Create the catalog properties file after the receipt exists:
crypta-app catalog create \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--catalog-id dev \
--name "Development Apps" \
--entry catalog-entry.properties \
--review-receipt review-receipt.propertiesSign the exact catalog bytes:
crypta-app catalog sign \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--key-id dev-local \
--private-key-file /abs/path/to/dev-app-signing-private.pemVerify the catalog and sibling signature:
crypta-app catalog verify \
--catalog-file dist/catalog/cryptad-app-catalog.properties \
--trusted-key-id dev-local \
--trusted-public-key-file /abs/path/to/dev-app-signing-public.pemThe sign command writes cryptad-app-catalog.signature beside
cryptad-app-catalog.properties. Do not rewrite, sort, or reformat the catalog after signing.
crypta-app does not publish catalogs or signatures to Crypta. To offer a public
catalog-over-Crypta source, create and sign the catalog locally, then publish
cryptad-app-catalog.properties and cryptad-app-catalog.signature with the existing Crypta
publishing workflow. Runtime catalog sources can reference a path-like USK/SSK catalog location
with sibling signature sidecar, or the CHK v1 companion form
crypta:CHK@<catalog-key>?signature=CHK@<signature-key>.
The developer CLI is for standalone bundle directories. First-party apps in this repository already have Gradle staging tasks that wire the same signing and verification libraries into the app projects:
./gradlew :apps:queue-manager:stageApp
./gradlew :apps:queue-manager:signApp
./gradlew :apps:queue-manager:verifyApp
./gradlew :apps:publisher:stageApp
./gradlew :apps:publisher:signApp
./gradlew :apps:publisher:verifyApp
./gradlew :apps:site-publisher:stageApp
./gradlew :apps:site-publisher:signApp
./gradlew :apps:site-publisher:verifyApp
./gradlew :apps:profile-publisher:stageApp
./gradlew :apps:profile-publisher:signApp
./gradlew :apps:profile-publisher:verifyAppThose signApp and verifyApp tasks still require the signing inputs documented in
app-distribution.md. Use the root stageFirstPartyApps,
signFirstPartyApps, and verifyFirstPartyApps tasks when you need to process all first-party
apps together.
The public beta catalog for first-party apps reuses the existing Gradle and crypta-app tooling.
Maintainers should keep generated artifacts under build/ or dist/ and keep private signing and
review keys outside the repository.
-
Stage, sign, and verify the first-party bundles:
./gradlew stageFirstPartyApps ./gradlew signFirstPartyApps ./gradlew verifyFirstPartyApps ./gradlew :platform-devtools:installDist
-
Pack
queue-manager,publisher,site-publisher, andprofile-publisherwithcrypta-app pack. Insert each ZIP into Crypta as an immutable CHK artifact through the maintainer publishing workflow. -
Write catalog descriptors whose public artifact location is the returned CHK:
artifact.path=/abs/path/to/queue-manager.zip bundle.uri=crypta:CHK@<artifact-key> summary=Manage local Crypta transfer queues. name=Queue Manager permissions=queue.read,queue.write permissions.rationale.queue.read=Reads local transfer queue state. api.minimumVersion=1 api.maximumTestedVersion=5 review.status=reviewed changelog.summary=First public beta catalog entry.
-
Create, sign, and verify the catalog with
crypta-app catalog create,crypta-app catalog sign, andcrypta-app catalog verify. Publish bothcryptad-app-catalog.propertiesandcryptad-app-catalog.signatureto the configured catalog USK. -
Configure runtime onboarding with
CRYPTAD_FIRST_PARTY_CATALOG_SOURCEandCRYPTAD_FIRST_PARTY_CATALOG_TRUSTED_KEY_ID, plus the normalCRYPTAD_APPHOST_*trusted public key settings. Operators add the catalog through Web Shell orPOST /api/v1/app-catalogs/recommended/crypta-first-party-beta/add; apps are not installed until the operator confirms an install/update for each entry.
See first-party-beta-catalog.md for the full maintainer and operator flow.
- developer-beta-toolkit.md gives the PR-225 beta toolkit walkthrough for queue-dashboard scaffolding, mock dev runs, strict tests, key generation, signing, cataloging, and dry-run USK publication.
- app-distribution.md describes the signed bundle sidecars, manifest fields, and first-party Gradle tasks.
- app-ui-design-system.md describes canonical app UI assets and
crypta-app ui lint. - app-catalogs.md describes the runtime catalog format, verification order, and Platform API install/update flow.
- platform-sdk-js.md describes the browser SDK used by app-owned static UI.
- app-permissions-and-audit.md lists the current Platform API capability names.
- platform-api-contract.md explains contract versions, stability
levels, manifest
api.*fields, and offline compatibility verification.