Repository documentation
Dynamic app descriptor
The validated discovery and execution contract for arbitrary installed Nextcloud applications.
On this page
DynamicAppDescriptor is the machine-readable boundary between API discovery,
native presentation and HTTP execution. Its canonical Rust model is
src/dynamic.rs; the serializable Compose Multiplatform mirror is
ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/nativeui/model/DynamicAppDescriptor.kt.
Both serialize camel-case JSON with descriptorVersion: "1.0".
Last reviewed: 2026-08-20. Discovery limits may have changed. The canonical Rust model, its validator, and its tests are the source of truth; the Kotlin model is its runtime mirror.
Contract
The top-level document contains:
- exact app identity and an endpoint policy containing the server origin and approved API path prefixes;
- capability facts and permission requirements with provenance and confidence;
- typed resources and fields;
- explicit list, detail or grid layouts;
- field links, where inferred URLs default to
allowExternal: false; - forms bound to explicit mutating actions;
- actions containing method, path, path/query parameters, body media type and schema, authentication requirements and OCS envelope metadata;
- warnings describing intentionally omitted or degraded behavior.
Every descriptor must pass DynamicAppDescriptor::validate() after compilation
and again after deserialization. Kotlin consumers use requireValid() for the
same trust-boundary checks. Validation rejects unsupported versions, duplicate
or dangling references, path-parameter mismatches, absolute/cross-origin paths,
paths outside the approved app prefixes and writes without advertised OpenAPI
or verified-adapter provenance.
Discovery inputs
DynamicDescriptorCompiler accepts only normalized facts in
DynamicDiscoveryInput:
- An OpenAPI 3.x document that the connected app advertised. The fetching layer supplies the document and its advertised same-origin URL. The compiler never searches arbitrary URLs.
- An OpenAPI 3.x document extracted from the exact installed release in the
official Nextcloud App Store. This source is accepted only after local
certificate-chain, signed-CRL, app-ID, version and archive-signature
verification. Its provenance is
verifiedAppPackage. - If that verified package contains no OpenAPI file, an OpenAPI 3.x document
from the exact GitHub release tag linked by the App Store entry. Repository
identity and tag come only from the official catalog metadata and release
URL; the tag's
appinfo/info.xmlmust reproduce the selected app ID and exact version. This unsigned, lower-trust provenance isappStoreLinkedSourceTag. Every operation still has to stay inside the connected server's approved same-origin app endpoint prefixes. - Successful 2xx JSON GET observations from an already approved endpoint. Response-shape inference creates read-only fields, a read action and a list/detail layout. It never creates a form or write action.
OpenAPI is authoritative for explicit write operations. An unnamed OpenAPI write is omitted; every declared mutation requires confirmation, and deletes are marked destructive.
Fixtures under tests/fixtures/ cover an advertised OCS/OpenAPI app and an
unknown app learned from a successful JSON list response.
Reviewed limits
- The Kotlin importer rejects OpenAPI 2/Swagger and ambiguous multiple server
bases. It permits whole-host server templates only for trusted package
or App Store-linked contracts. Concrete foreign origins and fixed-host
templates such as
https://{tenant}.vendor.testare never rebased onto the connected Nextcloud server. Relative and concrete same-origin servers remain valid. Host templates must retain the concrete scheme and effective port; only an explicitly templated port may adopt the account port. An omitted port means the scheme's default, not any account port. Acquisition rejects concrete package-server authorities because it cannot establish their ownership by the connected account. - Path and operation server overrides must pass the same origin checks and
resolve to the root server path base. Different override bases are unsupported
rather than ignored. An absent override inherits the base; an explicit empty
array resets it to
/and is rejected if that differs from the document base. See the OpenAPI server and override rules. - API-version defaults are bound only after inherited parameters, operation overrides, and local schema references resolve. A server-derived version must satisfy the effective parameter's declared enum/default. Unknown schema constraints prevent server-derived defaults instead of being silently ignored.
- Acquisition requires a declared app-owned server base or app-owned operation
paths. A root spec filename and
/api/...paths do not prove an app prefix. Missing or emptyserverslists are never rewritten to/apps/{appId}; OpenAPI defaults the server base to/. - Command-like GETs are excluded from root and contextual navigation, including linked child tabs and automatic child selection. Verified provenance does not make reset, delete, toggle, or similar commands safe navigation reads. This includes command segments before trailing identifiers and operation IDs with command verbs at any word position. Middle command words are accepted only when the actual terminal word is status, history, preview, export, or download. Repeated words do not change which word is terminal, and read suffixes cannot override command prefixes or command paths.
- Object
allOfflattening accepts only preserved shape keywords and known descriptive annotations. Conditional, unknown, and malformed member constraints withhold the write action and form. This includes OpenAPI 3.1if/then/else,const, and dependent schemas; see the OpenAPI schema rules. - External schema references in trusted Kotlin imports are sanitized before compilation; untrusted remote references remain unsupported.
- OpenAPI security alternatives are currently flattened into conservative authentication requirements; optional/alternative scheme selection is not modeled yet.
- JSON inference samples at most 64 objects and 128 fields. Empty collections remain fieldless until a non-empty successful response or schema is available.
- JSON examples cannot establish validation rules, pagination, sync semantics, ACL behavior or write payloads.
- GraphQL introspection, DAV XML schemas, HTML/accessibility inspection and JavaScript traffic instrumentation are not discovery sources yet.
- Inferred URL fields are display/copy metadata only. External navigation needs verified policy or an explicit user handoff.
toNativeAppSchema()adapts the descriptor for the current renderer, but the executor must retain the original dynamic action because schema 0.1 cannot carry query, authentication, permission or OCS metadata.- The common Kotlin compiler provides the same focused OpenAPI and read-shape paths for Android/desktop today. When both sources are supplied it currently prefers advertised OpenAPI instead of merging observed fields into it.