diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cf3df57..22cc108 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,14 +25,14 @@ jobs:
uses: actions/checkout@v7
with:
repository: mdbase-dev/mdbase-connect
- ref: 8e960317f0886f8c504023dad873cf8cec82fd99
+ ref: c4fd2f801b12686e1fd1bd25de4a72cf9e14bc5c
path: .sources/mdbase-connect
- name: Check out Rust implementation
uses: actions/checkout@v7
with:
repository: callumalpass/mdbase-rs
- ref: fafea262cffbce1205563a580e4d5850e7e942b2
+ ref: e9297f465f779d0bf6bb3a361abf16eaf80a6944
path: .sources/mdbase-rs
- name: Check out TypeScript implementation
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e3a9b5c..3e733be 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -32,14 +32,14 @@ jobs:
uses: actions/checkout@v7
with:
repository: mdbase-dev/mdbase-connect
- ref: 8e960317f0886f8c504023dad873cf8cec82fd99
+ ref: c4fd2f801b12686e1fd1bd25de4a72cf9e14bc5c
path: .sources/mdbase-connect
- name: Check out Rust implementation
uses: actions/checkout@v7
with:
repository: callumalpass/mdbase-rs
- ref: fafea262cffbce1205563a580e4d5850e7e942b2
+ ref: e9297f465f779d0bf6bb3a361abf16eaf80a6944
path: .sources/mdbase-rs
- name: Check out TypeScript implementation
diff --git a/public/connect/schemas/connect-protocol.v1.schema.json b/public/connect/schemas/connect-protocol.v1.schema.json
index 42786c8..14f9fdb 100644
--- a/public/connect/schemas/connect-protocol.v1.schema.json
+++ b/public/connect/schemas/connect-protocol.v1.schema.json
@@ -6,7 +6,11 @@
"oneOf": [
{ "$ref": "#/$defs/relayOperationRequest" },
{ "$ref": "#/$defs/relayOperationResponse" },
- { "$ref": "#/$defs/relayPolicySnapshot" }
+ { "$ref": "#/$defs/relayPolicySnapshot" },
+ { "$ref": "#/$defs/authorizationOfferRequest" },
+ { "$ref": "#/$defs/authorizationOfferResponse" },
+ { "$ref": "#/$defs/authorizationActivationRequest" },
+ { "$ref": "#/$defs/authorizationActivationResponse" }
],
"$defs": {
"uuid": {
@@ -14,7 +18,7 @@
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
},
"operation": {
- "enum": ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "create_view_source", "update_view_source", "delete_view_source", "validate", "create", "update", "delete", "rename", "read_type", "create_type", "update_type", "list_timers", "put_timer", "cancel_timer", "reconcile_timers"]
+ "enum": ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "create_view_source", "update_view_source", "delete_view_source", "validate", "create", "update", "delete", "rename", "read_type", "create_type", "update_type", "list_timers", "put_timer", "cancel_timer", "reconcile_timers", "sync"]
},
"contractRequirement": {
"type": "object",
@@ -145,6 +149,140 @@
"encryption": { "$ref": "#/$defs/grantEncryption" }
}
},
+ "authorizationCollectionOffer": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["collection_id", "display_name", "spec_version", "contracts"],
+ "properties": {
+ "collection_id": { "$ref": "#/$defs/uuid" },
+ "display_name": { "type": "string", "minLength": 1, "maxLength": 200 },
+ "spec_version": { "type": "string", "minLength": 1, "maxLength": 30 },
+ "contracts": {
+ "type": "array",
+ "maxItems": 100,
+ "items": { "$ref": "#/$defs/contractRequirement" }
+ }
+ }
+ },
+ "applicationRequirements": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["contracts"],
+ "properties": {
+ "contracts": {
+ "type": "array",
+ "maxItems": 20,
+ "items": { "$ref": "#/$defs/contractRequirement" }
+ },
+ "access": { "enum": ["contract", "full_collection"] }
+ }
+ },
+ "typeProvision": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "document", "provides"],
+ "properties": {
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
+ "path": { "type": "string", "minLength": 1, "maxLength": 500 },
+ "document": { "type": "string", "minLength": 1, "maxLength": 262144 },
+ "provides": {
+ "type": "array",
+ "maxItems": 20,
+ "items": { "$ref": "#/$defs/contractRequirement" }
+ }
+ }
+ },
+ "applicationProvisions": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["types"],
+ "properties": {
+ "types": {
+ "type": "array",
+ "maxItems": 100,
+ "items": { "$ref": "#/$defs/typeProvision" }
+ }
+ }
+ },
+ "authorizationOfferRequest": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["type", "protocol_version", "request_id", "authorization_id"],
+ "properties": {
+ "type": { "const": "authorization_offer_request" },
+ "protocol_version": { "const": 1 },
+ "request_id": { "$ref": "#/$defs/uuid" },
+ "authorization_id": { "$ref": "#/$defs/uuid" }
+ }
+ },
+ "authorizationOfferResponse": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["type", "protocol_version", "request_id", "paused", "collections"],
+ "properties": {
+ "type": { "const": "authorization_offer_response" },
+ "protocol_version": { "const": 1 },
+ "request_id": { "$ref": "#/$defs/uuid" },
+ "paused": { "type": "boolean" },
+ "collections": {
+ "type": "array",
+ "maxItems": 1000,
+ "items": { "$ref": "#/$defs/authorizationCollectionOffer" }
+ }
+ }
+ },
+ "authorizationActivationRequest": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["type", "protocol_version", "request_id", "authorization_id", "collection_id", "requirements", "provisions", "grant"],
+ "properties": {
+ "type": { "const": "authorization_activation_request" },
+ "protocol_version": { "const": 1 },
+ "request_id": { "$ref": "#/$defs/uuid" },
+ "authorization_id": { "$ref": "#/$defs/uuid" },
+ "collection_id": { "$ref": "#/$defs/uuid" },
+ "requirements": { "$ref": "#/$defs/applicationRequirements" },
+ "provisions": { "$ref": "#/$defs/applicationProvisions" },
+ "grant": { "$ref": "#/$defs/grantPolicy" }
+ }
+ },
+ "authorizationActivationResponse": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["type", "protocol_version", "request_id", "ok", "contracts"],
+ "properties": {
+ "type": { "const": "authorization_activation_response" },
+ "protocol_version": { "const": 1 },
+ "request_id": { "$ref": "#/$defs/uuid" },
+ "ok": { "type": "boolean" },
+ "contracts": {
+ "type": "array",
+ "maxItems": 100,
+ "items": { "$ref": "#/$defs/contractRequirement" }
+ },
+ "error": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["code", "message"],
+ "properties": {
+ "code": { "type": "string", "minLength": 1 },
+ "message": { "type": "string", "minLength": 1 },
+ "details": true
+ }
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "ok": { "const": true } }, "required": ["ok"] },
+ "then": {
+ "not": { "properties": { "error": true }, "required": ["error"] }
+ },
+ "else": {
+ "required": ["error"]
+ }
+ }
+ ]
+ },
"notificationCriterion": {
"type": "object",
"additionalProperties": false,
diff --git a/src/components/DocsLayout.astro b/src/components/DocsLayout.astro
index 78c4687..ff839ef 100644
--- a/src/components/DocsLayout.astro
+++ b/src/components/DocsLayout.astro
@@ -26,7 +26,7 @@ const docGroups = [
{ href: "/sdk/contracts/", label: "Contracts and adapters", key: "contracts" },
{ href: "/sdk/authorization/", label: "Authorization", key: "authorization" },
{ href: "/sdk/operations/", label: "Records and operations", key: "operations" },
- { href: "/sdk/routing/", label: "Local and hosted routes", key: "routing" },
+ { href: "/sdk/routing/", label: "Authority routes", key: "routing" },
{ href: "/sdk/testing/", label: "Testing", key: "testing" },
{ href: "/sdk/notifications/", label: "Notifications and timers", key: "notifications" },
{ href: "/sdk/offline-sync/", label: "Offline sync", key: "offline-sync" }
@@ -66,7 +66,7 @@ const docGroups = [
@mdbase/connect
- 0.1.0-beta.1
+ 0.1.0-beta.7
diff --git a/src/data/conformance.json b/src/data/conformance.json
index 82d4009..bbbb1f6 100644
--- a/src/data/conformance.json
+++ b/src/data/conformance.json
@@ -1,5 +1,5 @@
{
- "generated_at": "2026-07-26",
+ "generated_at": "2026-07-27",
"spec_version": "0.3.0",
"implementations": [
{
diff --git a/src/pages/connect/index.astro b/src/pages/connect/index.astro
index c8c018e..56b5cd1 100644
--- a/src/pages/connect/index.astro
+++ b/src/pages/connect/index.astro
@@ -80,7 +80,8 @@ const steps = [
Local and hosted collections
Applications use the same SDK and operation envelopes. Routing changes
- according to where the authoritative collection lives.
+ according to which connector or provider currently holds the collection's
+ authority.
@@ -116,6 +117,64 @@ const steps = [
+
+ Why one movable authority?
+
+ Authority is a coordination role, not the only copy of a collection.
+ Writable mirrors can keep ordinary Markdown on several devices, accept
+ changes while offline, and synchronize them later. One authority orders
+ the final accepted changes, enforces collection-wide rules, and gives
+ every mirror the same history to converge on.
+
+
+ Removing that role would not remove coordination. It would require every
+ peer—including filesystem editors that only know how to rewrite a
+ Markdown file—to preserve causal history and agree continuously about
+ concurrent edits, renames, deletes, schema changes, revocation, and
+ garbage collection.
+
+
+
+
+ | Concern |
+ Equal peers would require |
+ One movable authority provides |
+
+
+
+
+ | Ordinary Markdown tools |
+ CRDT or event metadata preserved by every writer |
+ Open files remain the editable local format |
+
+
+ | Concurrent changes |
+ A deterministic merge for every body and frontmatter value |
+ Revision checks, durable mutation receipts, and explicit conflicts |
+
+
+ | Renames, deletes, and configuration |
+ Distributed agreement over paths, tombstones, schemas, and constraints |
+ One canonical order for collection-wide decisions |
+
+
+ | Offline devices |
+ Split-brain reconciliation and peer-membership machinery |
+ Queued local work that safely converges when the mirror reconnects |
+
+
+
+
+
The role can move without changing the collection.
+
+ Authority transfer preserves the collection and record identities,
+ advances an authority epoch, and retires the previous authority. When
+ the previous authority was a local folder, that folder can continue as
+ a mirror. A stale authority cannot accept writes after cutover.
+
+
+
+
The authorization sequence
@@ -203,7 +262,7 @@ const steps = [
Authority
- The local connector or hosted provider that controls the main copy and makes the final authorization decision.
+ The movable coordination role that orders accepted changes, enforces collection rules, and makes the final authorization decision.
Control plane
diff --git a/src/pages/sdk/api/index.astro b/src/pages/sdk/api/index.astro
index 54b1910..7c382b1 100644
--- a/src/pages/sdk/api/index.astro
+++ b/src/pages/sdk/api/index.astro
@@ -265,7 +265,7 @@ const error = `try {
- Connection, routing, and hosted sync
+ Connection, routing, and sync
| Method | Purpose |
@@ -280,7 +280,7 @@ const error = `try {
checkDirectAccess() | Read same-computer availability silently |
requestDirectAccess() | Prompt from a user gesture where required |
disableDirectAccess() | Persist a relay-only preference for this app |
- hostedSync() | Return a refresh-aware sync transport for a hosted grant, or null |
+ sync() | Return one credential-hiding sync transport for a remote, direct, or relayed authority, or null |
pendingMutation() | Inspect an interrupted encrypted mutation |
resumePendingMutation() | Recover its exact durable connector receipt |
diff --git a/src/pages/sdk/index.astro b/src/pages/sdk/index.astro
index b95e3e0..53beba7 100644
--- a/src/pages/sdk/index.astro
+++ b/src/pages/sdk/index.astro
@@ -77,7 +77,7 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {
MdbaseConnection |
- One collection's operations, renewal, routing, notifications, and hosted sync |
+ One collection's operations, renewal, routing, notifications, and provider-neutral sync |
Browser APIs or adapters supplied by a native shell |
@@ -97,7 +97,7 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {
OfflineReplica |
- Offline-first applications backed by a hosted collection |
+ Offline-first applications backed by any sync-capable authority |
@mdbase/connect-sync |
@@ -143,7 +143,7 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {
@mdbase/connect |
- Authorization, routes, operations, notifications, and hosted sync handoff |
+ Authorization, routes, operations, notifications, and authority-neutral sync handoff |
@mdbase/connect-dev |
@@ -155,7 +155,7 @@ export async function listOpenTasks(client: MdbaseCollectionClient) {
@mdbase/connect-sync |
- Hosted replication state machine, IndexedDB replica, conflicts, and offline mutations |
+ Authority-neutral replication state machine, IndexedDB replica, conflicts, and offline mutations |
@mdbase/connect-webhooks |
diff --git a/src/pages/sdk/offline-sync/index.astro b/src/pages/sdk/offline-sync/index.astro
index 0dde310..741c8a2 100644
--- a/src/pages/sdk/offline-sync/index.astro
+++ b/src/pages/sdk/offline-sync/index.astro
@@ -8,24 +8,24 @@ const createReplica = `import {
type ReplicaData
} from "@mdbase/connect-sync";
-const hosted = connection.hostedSync();
-if (!hosted) {
- throw new Error("Authorize a hosted collection first.");
+const sync = connection.sync();
+if (!sync) {
+ throw new Error("Authorize the sync operation first.");
}
const initial: ReplicaData = {
- replicaId: hosted.replicaId,
+ replicaId: sync.replicaId,
records: {},
pending: [],
conflicts: {}
};
const store = new IndexedDbReplicaStore(
- \`tasks:\${hosted.collectionId}\`,
+ \`tasks:\${sync.collectionId}\`,
initial
);
-const replica = new OfflineReplica(hosted.transport, store);
+const replica = new OfflineReplica(sync.transport, store);
await replica.initialize();`;
const mutate = `const optimistic = await replica.queueCreate({
@@ -55,7 +55,7 @@ const promote = `mdbase-mirror promote ./worklog-mirror`;
- Require a hosted collection
+ Require sync access
- Add "collection_kind": "hosted" to the manifest requirements
- when the application needs an always-online authority and{" "}
- hostedSync(). The same application can still maintain an
- IndexedDB cache and operate while offline.
+ Request the sync operation, then call{" "}
+ connection.sync(). The returned transport is the same whether
+ the collection authority is remote, directly connected, or reached through
+ Connect's relay. Add "collection_kind": "hosted" only when the
+ application specifically requires an always-online hosted authority.
Add both packages:
@@ -81,9 +82,8 @@ const promote = `mdbase-mirror promote ./worklog-mirror`;
Create a persistent replica
- The SDK keeps the short-lived provider capability private and refreshes it
- before sync calls. The sync package receives only the provider-neutral
- transport.
+ The SDK keeps authority credentials and route selection private. The sync
+ package receives only the provider-neutral transport.
Queue optimistic local mutations
@@ -99,31 +99,32 @@ const promote = `mdbase-mirror promote ./worklog-mirror`;
Keeping the local version rebases it as a new idempotent mutation against
- the current hosted revision. Keeping the hosted version discards only that
- record's queued mutations. Neither choice silently affects other records.
+ the current authority revision. Keeping the authority version discards only
+ that record's queued mutations. Neither choice silently affects other
+ records.
Filesystem mirrors use the same protocol
- mdbase-mirror materializes a hosted collection as Markdown,
- either receive-only or writable. On first connection to an existing
- directory it compares the complete remote snapshot before writing or
- uploading. Differing paths stop for explicit review.
+ mdbase-mirror materializes a remotely authoritative collection
+ as Markdown, either receive-only or writable. On first connection to an
+ existing directory it compares the complete remote snapshot before writing
+ or uploading. Differing paths stop for explicit review.
-
-
+
+
Promotion starts from a converged full writable mirror and opens a
- short-lived approval in Connect. Approval freezes hosted writes at a final
+ short-lived approval in Connect. Approval freezes remote writes at a final
sequence. The CLI pulls through that sequence, proves the exact record and
resource manifest, registers the folder with the local connector, and then
advances the collection authority epoch.
- Old hosted grants, provider capabilities, and replicas are revoked at
+ Old remote grants, authority capabilities, and replicas are revoked at
cutover. Applications authorize the new local authority explicitly.
- Cancellation or expiry before completion restores hosted writes. The
+ Cancellation or expiry before completion restores remote writes. The
command is resumable after local materialization.
diff --git a/src/pages/sdk/portable-apps/index.astro b/src/pages/sdk/portable-apps/index.astro
index 755c1a6..a80acca 100644
--- a/src/pages/sdk/portable-apps/index.astro
+++ b/src/pages/sdk/portable-apps/index.astro
@@ -15,7 +15,7 @@ const manifest = `{
}`;
const browserBundle = ``;
diff --git a/src/pages/sdk/routing/index.astro b/src/pages/sdk/routing/index.astro
index d86af2a..f9962d3 100644
--- a/src/pages/sdk/routing/index.astro
+++ b/src/pages/sdk/routing/index.astro
@@ -11,7 +11,7 @@ if (status === "permission_required") {
}
connection.onConnectionChange((info) => {
- // "direct", "relay", or "hosted"
+ // "direct", "relay", or "remote"
renderRoute(info?.route);
});`;
@@ -25,14 +25,14 @@ const options = `const mdbase = new MdbaseConnect({
---
Routing state belongs to a collection-bound MdbaseConnection.
- Different saved collections can be hosted, direct, or relayed at the same
+ Different saved collections can be remote, direct, or relayed at the same
time.
@@ -82,26 +82,27 @@ const options = `const mdbase = new MdbaseConnect({
that intentionally lack end-to-end relay encryption.
- Hosted-provider route
+ Remote-authority route
- Authorization for a hosted collection returns a short-lived,
- grant-bound provider capability. The SDK sends operations directly to the
- provider, binds browser requests to the approved callback origin, and
- refreshes that capability as needed.
+ Authorization may return a short-lived, grant-bound remote-authority
+ capability. The SDK sends operations directly to that authority, binds
+ browser requests to the approved callback origin, and refreshes the
+ capability as needed.
- Hosted operations go directly from the application to the provider. The
- provider decrypts records to execute canonical mdbase operations and acts as
- a trusted data processor.
+ An mdbase hosted provider is one remote-authority deployment. Its operations
+ go directly from the application to the provider. The provider decrypts
+ records to execute canonical mdbase operations and acts as a trusted data
+ processor.
Portable files use the same route-neutral connection
- A downloaded file can use direct, encrypted-relay, or hosted-provider access
- after its short-code approval. For hosted access, its short-lived capability
- is bound to the exact grant, collection, scope, operations, expiry, and
- opaque Origin: null, and remains in process memory. The
- consuming operation API is identical across all three routes.
+ A downloaded file can use direct, encrypted-relay, or remote-authority
+ access after its short-code approval. For remote access, its short-lived
+ capability is bound to the exact grant, collection, scope, operations,
+ expiry, and opaque Origin: null, and remains in process
+ memory. The consuming operation API is identical across all three routes.
Native shells