Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions content/docs/(overview)/access.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- | --- |
Expand All @@ -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:
Expand All @@ -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

Expand Down
17 changes: 17 additions & 0 deletions content/docs/(reference)/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 6 additions & 2 deletions content/docs/(reference)/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <path> 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
Expand Down
15 changes: 14 additions & 1 deletion content/docs/catalog/permissions.mdx
Original file line number Diff line number Diff line change
@@ -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.
---

<include cwd>src/generated/permissions.md</include>
Expand All @@ -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.
33 changes: 32 additions & 1 deletion content/docs/guides/authenticate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <credential>
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.
Expand Down Expand Up @@ -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.
2 changes: 1 addition & 1 deletion content/docs/guides/deploy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Claim it with `marfa --socket <path> 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.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/guides/manage-keys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 10 additions & 6 deletions content/docs/guides/settings.mdx
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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")
```
Expand Down Expand Up @@ -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. |
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -103,14 +106,15 @@ Authorization: Bearer <key>
Content-Type: application/json

{
"name": "Library",
"enforcement": { "strict_mode": { "types": ["core.note"] } },
"trash_retention_days": 30
}
```

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

Expand Down
Loading