From 31b34d41525142dcf83d47696020aa2a75294c7b Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 18 Oct 2026 20:03:34 +0100 Subject: [PATCH] docs(release): explain instance names and profile updates --- content/docs/(overview)/access.mdx | 6 +++-- content/docs/(reference)/changelog.mdx | 17 +++++++++++++ content/docs/(reference)/cli.mdx | 8 +++++-- content/docs/catalog/permissions.mdx | 15 +++++++++++- content/docs/guides/authenticate.mdx | 33 +++++++++++++++++++++++++- content/docs/guides/deploy.mdx | 2 +- content/docs/guides/manage-keys.mdx | 2 +- content/docs/guides/settings.mdx | 16 ++++++++----- 8 files changed, 85 insertions(+), 14 deletions(-) diff --git a/content/docs/(overview)/access.mdx b/content/docs/(overview)/access.mdx index a38affd1..b88673e7 100644 --- a/content/docs/(overview)/access.mdx +++ b/content/docs/(overview)/access.mdx @@ -23,7 +23,7 @@ The computer the instance runs on has an authority of its own. The CLI uses it t A key or an app's token is limited in two ways. **Its maps say which data it reads and writes**, and **its permissions say which other operations it may use**. -There are five maps. Each names things at a level, `read` or `write`, which also covers reading. Anything a map doesn't name is out of reach: +There are five maps. Each names things at a level, `read` or `write`, which also covers reading. Email takes only `read`. Anything a map doesn't name is out of reach: | Map | What it names | Example | | --- | --- | --- | @@ -33,6 +33,8 @@ There are five maps. Each names things at a level, `read` or `write`, which also | Metadata | Whether it may register types and edge types | `"types": "write"` | | Profile | The owner's name, email and avatar | `"email": "read"` | +**Profile write access changes only the owner's name and avatar.** The email used to sign in stays under the owner's control. An app asks for profile write access explicitly, and the approval screen identifies what it can change. [Authenticate](/guides/authenticate#change-profile-fields-with-a-credential) shows how to use it. + The type map matters most, and only it takes a third level, `none`, which shuts out a type its wildcard covers. **An item of a type the credential can't read answers `404 item_not_found`**, exactly as an item that doesn't exist does, so a refusal never tells a credential what it can't see. An item it can read but not write is refused with `403 type_not_permitted`. Pick a key and a kind of item to see the answer for reading the item and for changing it: @@ -43,7 +45,7 @@ Pick a key and a kind of item to see the answer for reading the item and for cha A permission opens a group of operations, such as reading the audit log or making keys. It opens no items: a key with every permission and no type map reads nothing, and every item operation is refused with `403 type_not_permitted`. -The Catalog's [Permissions](/catalog/permissions) page lists every permission, and the levels a key's type and edge maps take. +The Catalog's [Permissions](/catalog/permissions) page lists every permission, and the levels a key's type, edge and profile maps take. ## Sources diff --git a/content/docs/(reference)/changelog.mdx b/content/docs/(reference)/changelog.mdx index 2a4b5c9d..c4f7d4bb 100644 --- a/content/docs/(reference)/changelog.mdx +++ b/content/docs/(reference)/changelog.mdx @@ -7,6 +7,23 @@ Each release of Marfa ships the server, the CLI for macOS on Apple silicon, and **Until the first public release, Marfa does not upgrade a database in place.** A build refuses a database whose schema differs from its own or that holds retired tables or settings, and leaves the file unchanged. If the build refuses your database, [Upgrade](/guides/upgrade) covers exporting with the old build and restoring into fresh storage. A version change alone does not cause a refusal. +## Swift SDK v0.0.21 + +Released October 18, 2026. [v0.0.21 on GitHub](https://github.com/withmarfa/marfa-swift/releases/tag/v0.0.21) + +- The package builds against Marfa v0.0.13, including the folder handover repair below. +- Generated API types cover owner and delegated profile updates, and accept the instance's configured display name as a string. +- Optional nullable fields distinguish omission from an explicit null, including profile avatars, key expiry, webhook filters and feed retention. + +## v0.0.13 + +Released October 18, 2026. [v0.0.13 on GitHub](https://github.com/withmarfa/marfa/releases/tag/v0.0.13) + +- An instance has a display name in its live configuration. It defaults to Marfa, is returned in the instance description, and appears on existing pages. Changing it takes effect without restarting and keeps the instance ID. +- The owner can change the sign-in email and display name through `PATCH /owner`, using a browser session authenticated within the last five minutes or the private local command `marfa owner update`. Changing email needs no confirmation; the owner's password, passkeys, existing sessions, keys and app grants keep working. +- A key or app can change name and avatar through `PATCH /profile` or `marfa profile update`, with profile write access for each submitted field. Email remains owner-only, and explicit email-write grants are refused. The approval screen identifies profile write access. +- Folder handover observers no longer mistake one another for commands requesting the folder. A watcher yields to an actual command without competing watchers taking the folder from one another. + ## Swift SDK v0.0.20 Released October 18, 2026. [v0.0.20 on GitHub](https://github.com/withmarfa/marfa-swift/releases/tag/v0.0.20) diff --git a/content/docs/(reference)/cli.mdx b/content/docs/(reference)/cli.mdx index 2ab86fba..4549c07a 100644 --- a/content/docs/(reference)/cli.mdx +++ b/content/docs/(reference)/cli.mdx @@ -103,10 +103,12 @@ marfa whoami ```text server https://marfa.example.com -instance 01a12312-7e62-70f2-a656-1c9eef418991 (marfa dev) +instance 01a12312-7e62-70f2-a656-1c9eef418991 (Marfa dev) a key from the keychain ``` +The instance name comes from its live configuration and defaults to Marfa. [Settings](/guides/settings#change-settings-while-it-runs) shows how to change it. + When a command reaches the wrong instance, or is refused, start here. ## On the instance's own machine @@ -125,10 +127,12 @@ marfa --socket /path/to/data/control/marfa.sock setup status } ``` -That's how the owner makes the first key, as in the [Quickstart](/quickstart), and recovers a lost password. A few commands work only this way: `marfa setup status`, `marfa setup code`, `marfa setup open`, `marfa owner recover`, the `marfa owner passkeys` commands, and the `marfa sign-ins` commands. +That's how the owner makes the first key, as in the [Quickstart](/quickstart), and recovers a lost password. A few commands work only this way: `marfa setup status`, `marfa setup code`, `marfa setup open`, `marfa owner recover`, `marfa owner update`, the `marfa owner passkeys` commands, and the `marfa sign-ins` commands. `marfa owner passkeys add` opens a fresh owner sign-in in a browser, followed by passkey registration. Use `--no-browser` to print the sign-in URL instead. The command does not sign you in or make a setup credential. [Authenticate](/guides/authenticate#sign-in-as-the-owner) covers passkey management. +`marfa --socket owner update --email owner@example.com --name "Reader"` changes the owner's sign-in address and display name. `marfa profile update --name "Reader"` uses a key or app token with profile write access instead; it can change name and avatar, never email. [Authenticate](/guides/authenticate#change-the-owners-email-or-name) explains both operations. + **The socket carries no key.** A command given `--socket` refuses to run while `MARFA_API_URL` or `MARFA_API_KEY` is set, and an operation that needs a key, such as `marfa events` or `marfa export`, is refused. Run those without `--socket`, with a key. ## Use it in a script diff --git a/content/docs/catalog/permissions.mdx b/content/docs/catalog/permissions.mdx index 6a02f30e..fc4c1c30 100644 --- a/content/docs/catalog/permissions.mdx +++ b/content/docs/catalog/permissions.mdx @@ -1,6 +1,6 @@ --- title: Permissions -description: The permissions a credential can hold, and the access it can hold to a type or an edge type. +description: The permissions a credential can hold, and its access levels for types, edge types and profile fields. --- src/generated/permissions.md @@ -19,4 +19,17 @@ A key's `type_permissions` and `edge_permissions` name a type or an edge type, a A `write` grant includes `read` ([`keys-and-oauth/type-write-includes-read`](https://github.com/withmarfa/marfa/blob/main/conformance/spec/keys-and-oauth.md#keys-and-oauthtype-write-includes-read), [`keys-and-oauth/edge-write-includes-read`](https://github.com/withmarfa/marfa/blob/main/conformance/spec/keys-and-oauth.md#keys-and-oauthedge-write-includes-read)). +## Access to profile fields + +A key's `profile_permissions` names `name`, `email`, `avatar`, or `*`. The wildcard covers every profile field. A field with neither an entry nor a wildcard is denied. + +| Field | Levels | What write access changes | +| --- | --- | --- | +| `name` | `read`, `write` | The owner's display name. | +| `email` | `read` | Nothing. Only the owner can change the sign-in address. | +| `avatar` | `read`, `write` | The avatar URL, including clearing it. | +| `*` | `read`, `write` | Name and avatar only; email stays read-only. | + +Profile write includes read. An explicit key grant for email write is refused, as is the OAuth scope `profile.email:write`. OAuth uses `profile.name:write` and `profile.avatar:write` for individual fields, or `profile:write` for both ([owner and delegated profile changes](https://github.com/withmarfa/marfa/blob/main/conformance/spec/instance-claim.md#instance-claimprofile-grant-closed)). + [Access](/access) explains the maps, and [Manage keys](/guides/manage-keys) shows how to give a key its access. diff --git a/content/docs/guides/authenticate.mdx b/content/docs/guides/authenticate.mdx index f922ecb2..fb4faad4 100644 --- a/content/docs/guides/authenticate.mdx +++ b/content/docs/guides/authenticate.mdx @@ -19,6 +19,37 @@ The owner's browser and the private local command can list, rename and end sign- [Manage keys](/guides/manage-keys) covers key access, and `marfa sign-ins --help` lists the local sign-in commands. +## Change the owner's email or name + +**The owner controls the sign-in address.** On the instance's machine, use its private socket to change the email, display name, or both: + +```bash +marfa --socket /srv/marfa/data/control/marfa.sock owner update \ + --email owner@example.com --name "Reader" +``` + +A browser client uses [Update the owner](/api-reference/access/updateOwner) with the owner's session authenticated within the last five minutes. Keys and app tokens cannot use this operation. The email is stored in lowercase, needs no confirmation, and becomes the address for password sign-in. The owner's identity, password, passkeys, existing sessions, keys and app grants stay in place. + +## Change profile fields with a credential + +A key or app with **profile write access for each field it sends** uses [Update the profile](/api-reference/access/updateProfile) to change the owner's name or avatar. This example needs `"name": "write"` in a key's `profile_permissions`, or the app scope `profile.name:write`: + +```bash tab="CLI" tab-group="client" +marfa profile update --name "Reader" +``` + +```http tab="HTTP" +PATCH /profile +Authorization: Bearer +Content-Type: application/json + +{ "name": "Reader" } +``` + +The answer is `{ "ok": true }`. To clear an avatar, use `marfa profile update --clear-avatar`, or send `"avatar": null`. An avatar URL must use HTTP or HTTPS; Marfa stores it without fetching it. If any submitted field lacks write access or is invalid, none of the submitted fields changes. + +**Profile write cannot change email**, including through a wildcard. Explicit email-write grants are refused. [Permissions](/catalog/permissions#access-to-profile-fields) lists the accepted fields and levels. + ## Send a key **Send the key in the `Authorization` header as a bearer token.** [Get the current key](/api-reference/access/getCurrentKey) is a quick way to check one: it answers what the key holds, without the key itself. @@ -280,4 +311,4 @@ A key that can't write notes, for example, is refused like this: A refusal never tells a credential what it can't see. To find out what a key holds, read it with [Get the current key](/api-reference/access/getCurrentKey); an app reads the `scope` in its token response. -The rules behind this guide are in the [keys and sign-in](https://github.com/withmarfa/marfa/blob/main/conformance/spec/keys-and-oauth.md) and [authentication](https://github.com/withmarfa/marfa/blob/main/conformance/spec/authentication.md) chapters of the contract. +The rules behind this guide are in the [keys and sign-in](https://github.com/withmarfa/marfa/blob/main/conformance/spec/keys-and-oauth.md), [authentication](https://github.com/withmarfa/marfa/blob/main/conformance/spec/authentication.md), and [owner and delegated profile changes](https://github.com/withmarfa/marfa/blob/main/conformance/spec/instance-claim.md#owner-and-delegated-profile-changes) chapters of the contract. diff --git a/content/docs/guides/deploy.mdx b/content/docs/guides/deploy.mdx index 4424c466..998bc7f7 100644 --- a/content/docs/guides/deploy.mdx +++ b/content/docs/guides/deploy.mdx @@ -177,7 +177,7 @@ Claim it with `marfa --socket setup open`, as the account that runs the s When the server can't start, its exit status tells your supervisor whether to try again: - `1` is a failed start that a restart may mend, such as a port in use. A setting outside its rules is reported this way too, with every wrong setting named in the log. -- `78` means the database was written by another build. The server changed nothing, and starting it again meets the same refusal, so **don't restart on `78`**: [Upgrade](/guides/upgrade) says what to do. The container doesn't exit on `78`; it stays up and unhealthy with the reason in its log, so a restart policy can't loop on it. +- `78` means the database has an incompatible schema or holds retired tables or settings. The server changed nothing, and starting it again meets the same refusal, so **don't restart on `78`**: [Upgrade](/guides/upgrade) says what to do. The container doesn't exit on `78`; it stays up and unhealthy with the reason in its log, so a restart policy can't loop on it. A server that crashes can leave its socket file behind. On restart, the server replaces a socket that refuses connections. If another listener accepts connections there, the new start refuses and preserves it: check which process is running rather than removing its socket. diff --git a/content/docs/guides/manage-keys.mdx b/content/docs/guides/manage-keys.mdx index b890a0b4..e1dd4a33 100644 --- a/content/docs/guides/manage-keys.mdx +++ b/content/docs/guides/manage-keys.mdx @@ -43,7 +43,7 @@ Each map and permission has a flag of its own: `--edge-permission`, `--extension What a new key holds depends on what the request names. -If the request names no permission, map or claimed source, **the key takes everything its maker could give**. A key the owner makes this way holds every [permission](/catalog/permissions) and `write` on everything in all five maps, which suits your own laptop and nothing else. A key made this way by another key holds a copy of that key's whole set. +If the request names no permission, map or claimed source, **the key takes everything its maker could give**. A key the owner makes this way holds every [permission](/catalog/permissions) and wildcard `write` in all five maps, which suits your own laptop and nothing else. **Profile write changes only name and avatar; email stays read-only.** A key made this way by another key inherits only that key's effective access. If the request names any of them, the key holds exactly what it names in each part it names, and **nothing in each part it leaves out**. The notes app's key above named one map, so it holds that map and no permission at all. A key made with only `--permission audit.read` reads the audit log and no item. diff --git a/content/docs/guides/settings.mdx b/content/docs/guides/settings.mdx index 20d96e2f..614ba1b8 100644 --- a/content/docs/guides/settings.mdx +++ b/content/docs/guides/settings.mdx @@ -1,9 +1,9 @@ --- title: Settings -description: Set the environment settings an instance reads when it starts, and change its enforcement levers and retentions while it runs. +description: Set the environment an instance starts with, and change its name, enforcement levers and retentions while it runs. --- -An instance has two kinds of settings. Environment settings are read once, when the server starts, and cover everything from where the database lives to how often each housekeeping job runs. The configuration at `/config` changes while the server runs: it holds three enforcement levers, which hold chosen types to stricter rules, and the retentions you're most likely to adjust. +An instance has two kinds of settings. Environment settings are read once, when the server starts, and cover everything from where the database lives to how often each housekeeping job runs. The configuration at `/config` changes while the server runs: it holds the instance's display name, three enforcement levers, which hold chosen types to stricter rules, and the retentions you're most likely to adjust. ## Set an environment setting @@ -20,7 +20,7 @@ If any setting is outside its allowed values, the server opens no port and creat ```text The server cannot start; fix these settings: - PORT must be a whole number, from 1 to 65535 (got "99999") + PORT must be a whole number, from 0 to 65535 (got "99999") MARFA_AUTH_SECRET must be at least 32 characters; generate one with `openssl rand -hex 32` TRASH_RETENTION_DAYS must be a whole number, from 0 to 36500 (got "forever") ``` @@ -57,6 +57,7 @@ The configuration at `/config` **takes effect at once, with no restart**. Readin | Field | Type | Description | | --- | --- | --- | +| `name` | `string` | The instance's display name. Defaults to `Marfa`. | | `enforcement.strict_mode` | `{ types }` | The types that refuse a property their type doesn't declare. | | `enforcement.source_allowlist` | `{ types, sources }` | The types that take new items only from the listed sources. | | `enforcement.source_filter` | `{ types, sources }` | The types whose listings, searches, exports, event streams and webhook deliveries show only items from the listed sources. | @@ -70,6 +71,8 @@ The configuration at `/config` **takes effect at once, with no restart**. Readin **A retention of `0` keeps those records with no age limit.** A retention you leave unset takes the environment setting's value. +The display name identifies the instance to apps and on its existing pages. **A name change keeps the same instance ID** and takes effect on subsequent requests. Marfa trims whitespace from each end and accepts up to 200 Unicode code points, with no control or bidirectional formatting characters. Leaving `name` out of a replacement restores `Marfa`. + ### Read the configuration ```bash tab="CLI" tab-group="client" @@ -91,10 +94,10 @@ An instance that has set nothing answers its `instance_id` alone: ### Replace the configuration -**A replace sets the whole configuration: a field you leave out goes back to its default.** Read the configuration first, change what you need, and send all of it back. This keeps the trash for 30 days and makes `core.note` refuse a property it doesn't declare: +**A replace sets the whole configuration: a field you leave out goes back to its default.** Read the configuration first, change what you need, and send all of it back. This names the instance Library, keeps the trash for 30 days and makes `core.note` refuse a property it doesn't declare: ```bash tab="CLI" tab-group="client" -marfa config replace --body '{"enforcement": {"strict_mode": {"types": ["core.note"]}}, "trash_retention_days": 30}' +marfa config replace --body '{"name": "Library", "enforcement": {"strict_mode": {"types": ["core.note"]}}, "trash_retention_days": 30}' ``` ```http tab="HTTP" @@ -103,6 +106,7 @@ Authorization: Bearer Content-Type: application/json { + "name": "Library", "enforcement": { "strict_mode": { "types": ["core.note"] } }, "trash_retention_days": 30 } @@ -110,7 +114,7 @@ Content-Type: application/json The answer is the configuration as it now stands. `marfa config replace --file config.json` reads the body from a file, and `--file -` from standard input. **Sending `{}` clears every field.** -The server refuses a body with a field it doesn't know or a retention out of range, and keeps the configuration as it was. Each accepted replace is recorded in the [audit log](/guides/audit-log) as `config.update`. [Replace the configuration](/api-reference/instance/replaceConfig) has every refusal. +The server refuses a body with a field it doesn't know, an invalid name or a retention out of range, and keeps the configuration as it was. Each accepted replace is recorded in the [audit log](/guides/audit-log) as `config.update`. [Replace the configuration](/api-reference/instance/replaceConfig) has every refusal. ## What the enforcement levers do