WO-FL-009. The files family reaches the CMS File access grant store: a plugin grants one file to a member or an address, lists what a file has handed out, and revokes what it gave. The response carries the signed unlock link, so a seller can deliver a file to somebody with no account. Nothing on the wire names the calling plugin: the host stamps plugin:<name> on what this family writes and refuses a revoke of anything else. ADR 0008. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
55 lines
3.0 KiB
Markdown
55 lines
3.0 KiB
Markdown
# A plugin grants and revokes file access through the files family
|
|
|
|
Status: accepted (WO-FL-009, 2026-09-17)
|
|
|
|
The CMS File library gates a published file and hands it to one person at a time
|
|
through a File access grant (cms ADRs 0181, 0186, 0187). ADR 0181 named the
|
|
storefront case explicitly: selling a single file belongs to a plugin, which
|
|
creates a grant after payment and revokes it on a refund. Until now a plugin had
|
|
no way to say either thing, because the capability surface carried no file
|
|
family at all.
|
|
|
|
Decision: the ABI gains `files.grant_access`, `files.revoke_access` and
|
|
`files.list_grants`, mirrored by `plugin.FileGrants` on `CoreServices`. A grant
|
|
names a file by its attachment kind and id and exactly one subject: a public
|
|
user, or an email address that receives a signed unlock link instead. The
|
|
response carries the stored grant, including that link, so a plugin that sells a
|
|
file to somebody with no account can deliver it in its own receipt.
|
|
|
|
**The source is the host's to write, and so is the revoke rule.** Nothing in
|
|
these messages names the calling plugin: the host already authenticates the
|
|
caller at the capability boundary, stamps `plugin:<name>` on every grant this
|
|
family writes, and refuses a revoke of a grant with any other source. A plugin
|
|
therefore cannot attribute a grant to somebody else, and cannot take away access
|
|
that an administrator, a workflow or another plugin gave. Passing the plugin
|
|
name on the wire was rejected for the same reason the bridge does not: a value a
|
|
guest supplies is a value a guest can change.
|
|
|
|
A grant with no expiry is the ordinary case, because revocation is the control
|
|
the CMS relies on; `expires_at` is optional and absent means "until revoked".
|
|
|
|
Alternatives rejected: a general "write a row in a core table" capability, which
|
|
would put the CHECK constraints and the unique indexes of a security table
|
|
behind a generic escape hatch; and returning only a grant id, which would force
|
|
a second call for the link every seller needs.
|
|
|
|
Consequences:
|
|
|
|
- New: `abi/proto/v1/capability.proto` messages `FileAccessGrant`,
|
|
`FilesGrantAccessRequest`/`Response`, `FilesRevokeAccessRequest`/`Response`,
|
|
`FilesListGrantsRequest`/`Response`; `plugin/wasmguest/caps/files.go` and its
|
|
round-trip goldens; `plugin.FileGrants`, `plugin.FileGrantParams` and
|
|
`plugin.FileGrant` in `plugin/deps.go`.
|
|
- `CoreServices.FileGrants` is nil on a host that does not wire it, exactly like
|
|
the other optional members, and the guest stub then fails the call rather than
|
|
pretending.
|
|
- The host half, the plugin attribution and the revoke guard live in the cms
|
|
repo (`backend/plugin/wasmhost/caps/files.go`, ADR 0190 there).
|
|
|
|
Keywords: files.grant_access, files.revoke_access, files.list_grants,
|
|
FileGrants, FileGrantParams, FileGrant, FileAccessGrant, FilesGrantAccessRequest,
|
|
FilesRevokeAccessRequest, FilesListGrantsRequest, filesStub, capability.proto,
|
|
File access grant, unlock link, unlock_url, plugin grant, grant source,
|
|
storefront plugin, File purchase, revoke own grants, WO-FL-009, ADR 0181,
|
|
CoreServices, wasm ABI, host_call
|