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
8 changes: 6 additions & 2 deletions .github/scripts/sync-from-marfa.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ sync good
echo "a good description"
check "the script succeeds" 0 "${status}"
check "it names what it synced and the release" \
"openapi.json, the API overview, GLOSSARY.md, the CLI's global options and exit codes in src/generated/cli, the error codes, the housekeeping jobs, the settings, the edge type, permission and event type catalogues, the Catalog in src/generated/catalog, the contract index, the release (v1.2.3) and its CLI download synced from ${work}/good/marfa" "${out}"
"openapi.json, the API overview, GLOSSARY.md, the CLI's global options and exit codes in src/generated/cli, the error codes, the housekeeping jobs, the settings, the edge type, permission and event type catalogues, the Catalog in src/generated/catalog, the contract index, the release (v1.2.3), its CLI download and its server image commands synced from ${work}/good/marfa" "${out}"
check "the overview is the description with each heading one level lower" \
"${expected}" "$(cat "${work}/good/docs/src/generated/api-overview.md")"
check "the overview ends with one newline" "" "$(tail -c 1 "${work}/good/docs/src/generated/api-overview.md" | tr -d '\n')"
Expand Down Expand Up @@ -764,8 +764,12 @@ check "the CLI download names that release and its archive" \
'curl -fsSL https://github.com/withmarfa/marfa/releases/download/v1.2.3/marfa-1.2.3-darwin-arm64.tar.gz | tar -xz' \
"$(sed -n 2p "${work}/good/docs/src/generated/cli-download.md")"
check "the CLI download is one bash block" '```bash|```' "$(head -1 "${work}/good/docs/src/generated/cli-download.md")|$(tail -1 "${work}/good/docs/src/generated/cli-download.md")"
check "the server image commands name that release's image, on loopback" \
'docker pull ghcr.io/withmarfa/marfa-server:1.2.3|docker run --name marfa -d --restart unless-stopped -p 127.0.0.1:8600:8600 \| -v marfa-data:/data --env-file marfa.env ghcr.io/withmarfa/marfa-server:1.2.3' \
"$(sed -n 2p "${work}/good/docs/src/generated/server-image.md")|$(sed -n 3p "${work}/good/docs/src/generated/server-image.md")|$(sed -n 4p "${work}/good/docs/src/generated/server-image.md")"
check "the server image commands are one bash block" '```bash|```' "$(head -1 "${work}/good/docs/src/generated/server-image.md")|$(tail -1 "${work}/good/docs/src/generated/server-image.md")"
check "nothing else is left in the generated folder" \
"GLOSSARY.md api-overview.md catalog cli cli-download.md contract-index.json edge-types.md error-codes.md event-types.md housekeeping.md marfa-release.json permissions.md settings.md" "$(ls "${work}/good/docs/src/generated" | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//')"
"GLOSSARY.md api-overview.md catalog cli cli-download.md contract-index.json edge-types.md error-codes.md event-types.md housekeeping.md marfa-release.json permissions.md server-image.md settings.md" "$(ls "${work}/good/docs/src/generated" | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//')"


# The Catalog's files: a Markdown file for each top-level type with its subtypes
Expand Down
2 changes: 2 additions & 0 deletions content/docs/(overview)/devices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Each write the instance answers gets one verdict:

A device's working copy can also be a directory of files on your computer: **a folder**. Each item the folder holds is a file, a note becomes a Markdown file, and an edit to either the file or the item reaches the other.

A file moved to another folder on the same computer stays the same item, because the file carries its item's ID, even when an editor saves the moved file again as UTF-16.

A folder keeps a working copy and a queue like any device. [Sync a folder](/guides/sync-a-folder) sets one up, and [Keep folders running](/guides/keep-folders-running) keeps every folder in step from login.

The rules behind this page are in the [device](https://github.com/withmarfa/marfa/blob/main/conformance/spec/device.md), [queue and verdicts](https://github.com/withmarfa/marfa/blob/main/conformance/spec/queue-and-verdicts.md) and [folders](https://github.com/withmarfa/marfa/blob/main/conformance/spec/folders.md) chapters of the contract.
2 changes: 1 addition & 1 deletion content/docs/(overview)/files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The same bytes uploaded again answer the same hash and are stored once, so two i

**Knowing a hash isn't enough to read the bytes.** A credential reads a blob through an item that names it: if you can read a file item, you can read its blob. A hash anywhere else in an item counts too, such as an image link inside a note's Markdown body.

A reference counts only if whoever wrote it had uploaded the bytes or could already read them, so naming a hash you found somewhere gives you nothing.
A reference counts only if whoever wrote it had uploaded the bytes or could already read them, so naming a hash you found somewhere gives you nothing. **A file item can't name such a hash at all**: the write is refused `404 blob_not_found`, so the item is never made with bytes that nobody can read. Upload the bytes with the same credential first, as [Handle files](/guides/handle-files#point-an-item-at-the-bytes) shows.

A blob you may not read answers `404 blob_not_found`, exactly as a hash that doesn't exist does, so a refusal never confirms that a file exists. That includes a blob you uploaded yourself, until an item names it.

Expand Down
23 changes: 22 additions & 1 deletion content/docs/(reference)/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,31 @@ title: Changelog
description: What each release of Marfa changed for someone running an instance or building on it.
---

Each release of Marfa ships the server, the CLI for macOS on Apple silicon, and the TypeScript SDK on npm. Below is what each one changed that you'd notice when you run an instance or build on it, newest first, with a link to the full list on GitHub.
Each release of Marfa ships the server and its container image, the CLI for macOS on Apple silicon, and the TypeScript SDK on npm. Below is what each one changed that you'd notice when you run an instance or build on it, newest first, with a link to the full list on GitHub.

**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.

## v0.0.14

Released October 19, 2026. [v0.0.14 on GitHub](https://github.com/withmarfa/marfa/releases/tag/v0.0.14)

**Before you update, if your container keeps its database in a bucket:** the replica moves from `db` at the bucket's root to `<S3_PREFIX>/db`. On its next start, an existing deployment streams its database to the new path and takes a full snapshot there at once. The old `db/` objects are no longer read or pruned, and you can delete them once the new replica exists. **Every instance on a shared bucket now needs its own `S3_PREFIX`.** Before this release, instances on one bucket shared one replica, and a new volume of one could start on another's database. [Back up and restore](/guides/back-up-and-restore#with-a-bucket) and [Deploy](/guides/deploy#an-object-store) explain the prefix.

For running an instance:

- **Each release publishes the server image** to `ghcr.io/withmarfa/marfa-server`, for `linux/amd64` and `linux/arm64`, tagged with the release and `latest`. [Deploy](/guides/deploy#run-it-in-a-container) pulls it, and [Upgrade](/guides/upgrade) names the old build by its tag. `GET /` answers the `release` of such a build beside `version`, and `marfa status` and `marfa whoami` show both. A build made from a clone has no `release`. The container's port is published on the loopback address only, because Docker publishes a bare port on every interface.
- **A proxy on the same machine as the container needs `TRUSTED_PROXY_CIDRS`** set to the container network's range. Without it, every caller shares one address, and sign-in lockouts, which count attempts for each address, apply to everyone together. [Deploy](/guides/deploy#the-address) explains it.
- **A webhook to a private address is refused when you register it.** A `url` whose host is a private address, or a name that resolves to one, such as a Tailscale name, is refused `400 validation_error` that names `MARFA_WEBHOOK_ALLOW_PRIVATE_ADDRESSES`, and so is a change of `url` to one. A delivery refused for its address is `dead_letter` at once and not retried, so redeliver it after you allow private addresses. An outbound webhook reaches only global IPv6 addresses, and other IPv6 blocks are refused. [Receive webhooks](/guides/receive-webhooks#receive-deliveries-on-a-private-network) covers it.
- **Telemetry names the instance.** Every log record, span and reported exception carries `service.instance.id`, the `instance_id` that `GET /` answers. `OTEL_RESOURCE_ATTRIBUTES` marks an instance, such as `deployment.environment=staging` for a test one, for the server and the CLI, and `OTEL_SERVICE_NAME` wins over a `service.name` in it. The bare-metal start in [Deploy](/guides/deploy#run-it-without-a-container) loads the telemetry preload. [Send telemetry](/guides/telemetry#tell-instances-apart) covers it.
- **A backup names the files it lacks.** An archive leaves out a blob that no store holds whole and names it under `missing_blobs` in its manifest. `marfa export` then exits `1` with `blobs_missing`, a restore answers `blobs_missing`, and `marfa restore` exits `1` when it lists any. A cut `marfa export --output` leaves the path as it was and exits `3`. A blob store that fails answers `503 blob_store_unavailable`. [Back up and restore](/guides/back-up-and-restore#when-an-archive-lacks-a-files-bytes) covers it.
- **Headers a Tailscale proxy adds, those named `Tailscale-`, are not stored** with a connector's inbound delivery. [Receive vendor webhooks](/guides/connector-webhooks) covers it.

For apps and devices:

- **A file item names only bytes its writer uploaded or can already read.** Any other write of `blob_ref` is refused `404 blob_not_found`, where it was taken and left a file that nobody could download. Upload with the same credential first. The refusal isn't kept against an `Idempotency-Key`, and a working copy uploads again under its current key when the key changed. [Handle files](/guides/handle-files#point-an-item-at-the-bytes) covers it.
- **`marfa folders push` says whether it finished.** It exits `3` when a write is left unsent or the catch-up fails, and `5` when the credential is refused. Under a rate limit it doesn't wait: it reports the writes as not delivered, names the wait and exits `3`, and a watch waits on its own. The report counts files with an unanswered write as waiting. `marfa folders restore` lists the files it couldn't write back. A file moved to another folder and saved again as UTF-16 stays the same item, and a file that isn't text no longer holds up deletes in other folders. [Sync a folder](/guides/sync-a-folder#know-whether-a-push-finished) covers it.
- **`marfa keys keep` keeps any valid key**, including one that holds only `keys.mint`, `instance.read` or `audit.read`. `marfa events` ends with exit `1` for `cursor_ahead` and `catchup_too_old`, `3` for a stream that stopped early, and `5` for `credential_ended`. `marfa owner passkeys add` opens the ordinary sign-in page. [The CLI](/cli#connect-to-a-server) and [Keep a copy in sync](/guides/keep-in-sync#when-the-stream-ends) cover them.

## 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)
Expand Down
10 changes: 6 additions & 4 deletions content/docs/(reference)/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ Waiting for the approval...

The token holds only the scopes you approve. The CLI asks for everything an owner can grant except `instance.read`, `instance.maintain`, `connectors.manage`, `blobs.manage` and `keys.manage`, so the commands that need one of those need a key instead. `marfa logout` revokes the token and forgets it.

Or **keep a key** you already have with `marfa keys keep`. It reads the key from `MARFA_API_KEY`, or, if that isn't set, from standard input, so you can paste it and press Return. It checks the key with the server, then keeps it in the keychain:
Or **keep a key** you already have with `marfa keys keep`. It reads the key from `MARFA_API_KEY`, or, if that isn't set, from standard input, so you can paste it and press Return. It checks the key with the server, then keeps it in the keychain.

```bash
marfa keys keep --url https://marfa.example.com
```

**Any valid key is kept**, including one that holds only permissions such as `keys.mint`, `instance.read` or `audit.read` and reaches no type. A key the server doesn't accept, because it's revoked, expired or unknown, is kept nowhere and ends the command with exit code 5. An app's access token isn't a key: the command keeps nothing and exits 1, because the token ends within the hour and `marfa login` keeps a sign-in that renews it.

[Manage keys](/guides/manage-keys) shows how to make a key that reaches only what you need.

Either way, the server you sign in to or keep a key for becomes the current server, which a command uses when nothing names another. The keychain holds one credential for each server. For each command, the CLI takes the first server and the first credential that's set, in this order:
Expand Down Expand Up @@ -111,11 +113,11 @@ marfa whoami

```text
server https://marfa.example.com
instance 01a12312-7e62-70f2-a656-1c9eef418991 (Marfa dev)
instance 01a12312-7e62-70f2-a656-1c9eef418991 (Marfa 0.0.14, build 37eff92)
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.
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. After the name comes the **release** the server was built as, then the commit it was built from. `marfa status` names them the same way. A server built from a clone shows only the commit, because it has no release.

When a command reaches the wrong instance, or is refused, start here.

Expand All @@ -137,7 +139,7 @@ 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`, `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 owner passkeys add` opens the ordinary owner sign-in page in a browser, where you sign in yourself with the password or a passkey, and then reach the passkey page. 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.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/guides/authenticate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The owner signs in in a browser to manage the instance and approve apps. For dat

To add a passkey, open `/auth/owner/passkeys` with a browser session authenticated within the last five minutes. This page also lists and removes individual passkeys. **Renaming or removing a passkey requires authentication within the last five minutes**; listing them needs only a live owner session. Removing one prevents further sign-in with it, while existing sign-ins and other passkeys keep working.

On the instance's own machine, `marfa --socket <path> owner passkeys list` lists the passkeys. Use `marfa --socket <path> owner passkeys rename <id> --name "Laptop"` to rename one, or `marfa --socket <path> owner passkeys remove <id>` to remove it. `marfa --socket <path> owner passkeys add` opens a fresh browser sign-in, then the passkey page; `--no-browser` prints the URL instead. **The add command issues no sign-in credential**: you still authenticate in the browser and register the passkey there. [Recover the owner's password](/guides/manage-keys#recover-the-owners-password) remains the way to recover a lost password.
On the instance's own machine, `marfa --socket <path> owner passkeys list` lists the passkeys. Use `marfa --socket <path> owner passkeys rename <id> --name "Laptop"` to rename one, or `marfa --socket <path> owner passkeys remove <id>` to remove it. `marfa --socket <path> owner passkeys add` opens the ordinary sign-in page, where you sign in with the password or a passkey, and then the passkey page; `--no-browser` prints the URL instead. **The add command issues no sign-in credential**: you still authenticate in the browser and register the passkey there. [Recover the owner's password](/guides/manage-keys#recover-the-owners-password) remains the way to recover a lost password.

Passkeys need a supported browser and HTTPS, or localhost for development. **A valid passkey works even when password attempts are locked.** It creates the same owner browser authority used to approve apps and manage the instance; an app still needs its own token to read and write data.

Expand Down
Loading
Loading