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§
- DocFormat
Entry - One entry of the listing: a resolved format plus which tier it came from.
- PutDoc
Format - 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.