Skip to main content

Module format

Module format 

Source
Expand description

/api/doc-formats — which app indexes a document type, and how.

§The claim rule

(tn_id, content_type) is a primary key, so exactly one app may index a document type per tenant. Other apps may still open documents of that type; this is only about who owns the index rules. An upsert succeeds when there is no active row, when the caller is the same (publisher_tag, app_name) that already holds it, or when the caller is site admin. Anything else is a Error::PermissionDenied naming the incumbent.

Reads are gated too, one step looser: the listing is owner / community leader / site admin ([require_format_reader]), not any authenticated caller. The full manifest set names every app a tenant runs and everything each of them indexes, which is the same enumeration [check_claim] refuses to put in its 403 body.

§Two tiers

A doc_formats row is not the only source. The apps this build bundles ship their manifests inside dist, and cloudillo_core::bundled_apps::BundledAppRegistry loads them once at startup as an in-memory global default — no rows written, nothing duplicated per tenant. cloudillo_core::doc_format::resolve is the choke point every reader goes through: tenant row first, bundled entry as the fallback.

Only the row is a claim. [check_claim] is deliberately fed the tenant row alone, so a tenant installing a different app for a content type this build bundles is allowed — its row then wins on every read, and DELETE reverts to the bundled default. There is no way to turn a bundled format off, only to override it.

§Why apps cannot call this directly

Apps run in sandboxed iframes with opaque origins and only ever hold file-scoped tokens. cloudillo_core::scope::scope_permits whitelists /api/search for TokenScope::File and nothing else here, so this route is unreachable with an app’s own credential. Registration goes app → shell → backend, and the shell attests the app’s identity from its own window tracker rather than from the message body. A rogue app therefore cannot claim a content type it does not own on either side of the boundary.

§The version gate

format_version is a document format version, not an app version: it describes the search-index contract an app declares for one content type. major.minor is the contract, patch the app’s counter for compatible tweaks, and the wire carries the integer encoding MMMmmmppp — three decimal digits per component, so 2.1 patch 42 is 2_001_042 and one < comparison orders two registrations.

Ordering exists because a registering client does so from every device its user owns, on every app start. Under last-writer-wins an older build overwrites a newer one, and since a changed search payload drops and rebuilds the whole content type’s index, two devices on different versions bounce a full tenant reindex between them indefinitely. [gate] ignores a registration older than the stored one.

Bundled apps do not register at all — their versions are compared inside the bundle, not on the wire — so the gate covers installed (packaged) apps and older shells.

An ignored registration answers 200 with the stored row, not 409. The shell memoises a success like any other, so a stale device goes quiet after one call; a 409 would make every tab of that build retry forever.

Orthogonal to crate::rules::SUPPORTED_VERSION, which is the platform-owned schema version of the rules DSL itself.

Structs§

DocFormatEntry
One entry of the listing: a resolved format plus which tier it came from.
PutDocFormat
Body of PUT /api/doc-formats/{content_type}.

Functions§

delete_doc_format
DELETE /api/doc-formats/{content_type} — drop the tenant’s row.
list_doc_formats
GET /api/doc-formats — every format in effect for this tenant.
put_doc_format
PUT /api/doc-formats/{content_type} — register or update a manifest.