Skip to content

Host API requirements to complete the server protocol plugins #167

Open

Description

@michielbdejong

Outcome

Provide the host contracts needed to turn the seven server-plugin implementations into interoperable services backed by Atomic Server's real atom, blob and document stores. Track shared host dependencies here, while keeping protocol implementation work in each plugin.

Implementation PRs: OCM #159, remoteStorage #160, Solid #161, AT Protocol #162, Fediverse #163, Willow #164, NextGraph #165. Related: #88, #134–#138 and the accepted placement/route design.

Current integration pin: 69602364b9f9535b7511e228755c59a84b619e60. Its route executor supports anonymous reads, but refuses protected authentication, blob bodies and route intents/enqueues. Reviewed job intents already exist.

Current proposed pin, audited separately: #168 now proposes bc39dac4b08b1e1430a5f5b511c6aaa63dd9c06b. Source inspection and a diff against the previously audited 74ae9931fd804257b2dcc59259d1b1bf557b48a1 confirm the protected signature/bearer routes, native route writes, blobs, host keys/consent and durable delivery contracts below. The core route executor, authentication, blobs, writes, delivery, keys, tokens, Rust HTTP manifest and QuickJS bindings are unchanged between those revisions. These are implemented upstream contracts, not functionality verified in the current plugin lanes. Adopt and test the pin before using them; do not reimplement their host machinery. Checked items below mean implemented in this proposed source, not deployed or certified end to end.

The newer revision makes unmatched/feature-off /_routes paths return a problem-JSON 404 and changes the default delivery cap to 1,000 attempts per installation per day. Its new iframe-app table-row grants do not extend QuickJS route write grants or provide general delegation to edit existing documents. See route dispatch, delivery configuration and app row grants.

QuickJS remains the plugin runtime. Native Rust crates cannot simply be linked into these JavaScript bundles. Stateful engines need a host extension or an explicitly configured sidecar where necessary.

Shared host contracts

1. Authenticated, atomic route mutations

  • Proposed pin: declared http.writeTargets, installation route-writes grants, native intent application, provenance and quotas. Creates must be directly under an approved parent with approved classes; updates/deletes are restricted to resources created by the installation. See route writes.
  • Make multi-resource application atomic, with typed failures and no partial effects. The current applier can report “applied N of M”; successful writes are not rolled back when a later change fails. Add authenticated Atomic caller/installation permission intersection: auth: atomic and principal: caller remain unavailable.
  • Add revision/precondition checks evaluated inside the commit, not a plugin read followed by an unconditional write. Support create-if-absent, update/delete-if-current and atomic batches for resource content, path identity, folder membership and ancestor versions.
  • Persist installation-scoped protocol identities, uniqueness constraints, deduplication keys and commit receipts. Retries after disconnect/restart must discover whether a mutation committed; concurrent requests must not create duplicate shares/resources.
  • Define explicit delegation for editing an existing user-selected resource. Permission to create protocol-owned resources is not automatically permission to overwrite a user's existing Atomic document. Reconfiguration/revocation must update grants at effect time.

Dependencies: atomic-server#1717 and #1758. Conditional transactions and uniqueness need explicit acceptance coverage beyond the basic route-write grant.

2. Real blob storage and atomic resource linkage

  • Proposed pin: body: "blob" stores bounded, BLAKE3-addressed bytes in the real blob store and supplies request.blob = { hash, size, type, subject }. Native File atoms can reference the atomic:blob: subject. response.blob serves only blobs stored by this installation or referenced under approved targets. See blob bodies.
  • Add staged promotion tied to the atom transaction. Bytes are currently stored before the handler runs and remain after refused writes. Define retention/GC and failed-upload cleanup.
  • Make conditional writes atomic. response.current currently supplies the prior blob for a pre-application header check; this is not a revision check inside the commit. Add streamed/range responses where needed; current bodies are buffered and no range response contract was found.
  • Support bounded remote-content fetch into staged blobs through guarded egress for OCM/import jobs, without decoding arbitrary bytes as text or base64 atoms.
  • Complete cancellation and crash recovery semantics around existing size/daily-byte limits. Failed preconditions must not leave visible resource updates or permanently orphaned uploads.

Dependency: atomic-server#1720. #1727 concerns binary job uploads; that alone does not provide durable route blob storage.

3. Host-verified identities, keys and consent

  • Proposed pin: auth: http-signature verifies Cavage/RFC 9421 signatures, digest and timestamp, with guarded remote-key retrieval; request.caller carries { keyId, owner, scheme, alg }. auth: bearer verifies installation tokens and exposes { token: { id, name, scopes, client } }. See route authentication.
  • Proposed pin: declared RSA-SHA256/Ed25519 installation keys, ctx.keys.publicKey and operation-constrained ctx.keys.sign; private keys remain host-held. See keys.
  • Proposed pin: ctx.tokens.requestConsent, host-owned consent, one-time code redemption through ctx.tokens.issue, verification and revocation. See tokens and consent. remoteStorage must still implement category r/rw interpretation and its protocol flow; the host does not interpret those scopes.
  • Complete effect-time scope/revocation checks, key rotation and replay protection beyond timestamp checks. No key rotation host call or signature replay cache was found in the audited modules. Peer identity verification alone does not authorize a document/share operation.
  • Add a separate Solid-OIDC/DPoP verification contract: issuer/JWKS discovery, token audience/expiry, proof binding to method/URL/key, replay/nonce handling and WebID binding. Translate WAC/ACP decisions into constrained Atomic permissions; a verified WebID alone is not an access grant.

Dependency: atomic-server#1718. Its current scope defers DPoP, so completing that issue alone does not unblock all Solid functionality. A full Solid IdP remains a separate host/sidecar decision.

4. Complete HTTP transport metadata and protocol responses

  • Proposed pin: handlers receive request.url, request.base and the resolved trigger route.
  • Verify the trusted-authority/proxy boundary: url and base are constructed from Actix connection information. Add explicit coverage for untrusted Host/forwarded headers before relying on them for per-host AT identities or signature/DPoP binding.
  • Implement host-owned OPTIONS/CORS preflight for declared routes, methods and headers. Both inspected pins exclude OPTIONS from the manifest method allowlist; PATCH is already allowed.
  • Extend declarations/validation for required response headers such as Allow, Accept-Put, Accept-Patch, verified WAC-Allow, and blob range headers. Preserve multi-valued Link semantics and define header exposure to browser clients.
  • Add narrowly declared nonstandard methods needed for OCM WebDAV, especially PROPFIND. Decide the supported WebDAV subset before broadening the allowlist.

Build on atomic-server#1715, #1716, #1720 and the phase-3 method work mentioned in #1722. These are extensions to existing dispatch, not a request to rebuild it.

5. Durable deliveries, queries and change cursors

  • Proposed pin: declared verdict enqueue operations, stable installation-scoped idempotency keys, durable worker/restart recovery, signed guarded delivery, retry/backoff, destination limits and failed/uncertain receipts. Revoked installations lose pending jobs; paused installations hold them. See delivery queue.
  • Proposed pin: declared dynamic destinations pass guarded egress; the worker rechecks declared operations and installation state and signs each attempt host-side.
  • Atomically persist protocol state and outbound jobs. The executor applies atom intents before persisting deliveries: queue failure can follow successful atom changes. Add crash/failure acceptance tests for a transactional outbox or equivalent recoverable protocol. Network delivery still cannot promise exactly-once execution.
  • Provide scoped paginated queries with deterministic ordering and opaque cursors, plus revision/snapshot semantics for complete folder/collection responses. Bound and explicitly report truncation.
  • Provide durable resource-change cursors/tombstones to drive updates, deletions and notifications, with reconnect/resume and ordering semantics. Revoked access must not continue delivering previously authorized content.

Dependencies: atomic-server#1719, #1721, #1722 for live notification transport. Current bounded fixture queries are not durable collection/sync cursors.

6. Native data-browser document operations

  • Expose authorized document export/import/update operations with format/version metadata, revision guards and preserved document identity. Bind OCM shares to actual selected Atomic documents and attachments.
  • Support the data browser's real Loro document representation through a host adapter, including conflict handling and round-trip validation. Raw Tiptap JSON assigned to documentContent does not populate a DocumentV2 editor.
  • Keep exact protocol bytes separately when required; define the supported mapping between those bytes, native atom properties and editable rich text. Do not silently treat arbitrary RDF, JSON or binary files as rich-text documents.

Current remoteStorage/Solid text imports use ordinary atoms/PlainText, and OCM creates a Message receipt whose parent/about link targets the selected document. With the proposed pin, a signed OCM route can create such a receipt under a declared parent: "config:document" Message write target, then enqueue a notification. This does not authorize overwriting the existing document, and a received receipt alone is not complete OCM share acceptance or file access. Those are useful implementations but do not provide native rich-text content synchronization. This adapter needs a host follow-up beyond #1717/#1720.

7. Stateful engines and explicitly approved sidecars

  • Define authenticated host-mediated stream/session handles with backpressure, size/time limits, cancellation, reconnect and durable checkpoints where a per-message QuickJS handler is sufficient (for example Solid notifications).
  • For Willow WGPS, NextGraph broker sessions and a full AT PDS, provide declared operations to an operator-approved extension/sidecar. Bind each operation to installation/user authority, selected resources and allowed destinations. Never grant arbitrary loopback access, sockets or process execution to a plugin.
  • Willow requires Meadowcap validation and authority-scoped signing of exact Entry bytes, bound to namespace/subspace/path, validity constraints and the selected Atomic source revision. Persist authorized entries, payloads and reconciliation state; unsigned candidates are not authorized writes.
  • NextGraph requires a wallet/key and broker-session boundary with scoped query/update operations, durable acknowledgements and an explicit E2EE/data-exposure model. The present SPARQL snapshot adapter supplies none of those facilities.
  • A full AT PDS requires repository/CAR/blob storage, account/auth/key lifecycle and resumable sync streams. Use a PDS bridge unless a separate host design deliberately takes ownership of those services. The existing handle resolver does not require a PDS.

Dependencies: atomic-server#1722, #1723. A WebSocket API alone does not implement these engines or their authorization models.

Per-plugin completion map

Plugin Host dependencies above Plugin work still required after APIs exist
remoteStorage (#160) 1–5; 6 for native rich-text integration OAuth flow, private reads, conditional PUT/DELETE, binary documents, complete folder semantics and browser-client interoperability
Solid (#161) 1–5, 7 for notifications; 6 for rich text Full chosen RDF/LDP representations and containers, N3 Patch, WAC/ACP mapping, WebID/Solid-OIDC, notifications; IdP separately scoped
OCM (#159) 1–6 Authenticated share acceptance/rejection, durable lifecycle/expiry/revocation, peer notifications, content fetch; sending/WebDAV as a separately tested extension
Fediverse (#163) 1–5; 6 for native rich-text publishing Inbox validation, Follow/Accept, Update/Delete, audience semantics, signed fetch/delivery, followers/following and independent-peer federation
AT Protocol (#162) 4 for strict per-host handle resolution; 1–5/7 for a separately scoped PDS bridge Reverse DID ownership/interoperability check for handles; full PDS is a different scope from the current resolver
Willow (#164) 1–3, 5, 7 Capability/signature validation, authorized-entry lifecycle, reconciliation, confidentiality and live WGPS interoperability
NextGraph (#165) 1–3, 5, 7 Authorized live query/update bridge, snapshot/delta mapping, conflict handling and broker interoperability; native broker remains a sidecar

Delivery and acceptance

  1. Validate/adopt the proposed pin in Pin atomic-server to bc39dac4b (candidates 7–11: route writes/crypto/queue/blobs/health; multi-table Set up; importer.run; view row writes) #168, then exercise its existing native writes, signature/bearer auth, blobs, consent and delivery APIs in real feature-enabled plugin lanes. Keep Solid protected endpoints refused until its DPoP contract exists.
  2. Implement the remaining atomic preconditions/outbox guarantees, collection/change cursors and native document operations; build OCM/Fediverse functionality on the existing signing and delivery APIs.
  3. Design stream/sidecar boundaries before attempting Willow/NextGraph/PDS engines in sandbox handlers.
  • Publish a versioned manifest/QuickJS ABI contract and capability detection with actionable unsupported-feature errors. Preserve build, operator and installation-consent gates; feature-off hosts must refuse rather than simulate success.
  • In each plugin's CI lane, execute the generated bundle in actual QuickJS on the pinned feature-enabled Atomic Server. Verify real atom/blob apply → restart → read, conditional-write races, duplicates/retries, revoked permissions and failed-delivery recovery.
  • Add independent protocol-client/peer tests for each explicitly supported scope, plus feature-off refusal tests. Link certification evidence through Tooling for gated plugins: catalog requires, certification, plugin-routes e2e build #134.
  • Document supported protocol versions/subsets and remaining exclusions. Node unit tests and manifest/verdict parsing are useful evidence, but are not host persistence or protocol interoperability certification.

This issue is complete when the dependencies and their acceptance evidence are linked for each agreed plugin scope—not merely when new ctx names or passing mock tests exist.

Activity

  1. michielbdejong commented on Sep 25, 2026

    @michielbdejong
    ContributorAuthor

    Host status since the candidate5 baseline

    This issue inspected the host at candidate5 (35504494…). Since then, the atomic-server side of the plugin-routes design has been built as PRs against feat/plugin-debug. They are combined in the claude/atomic-plugins-pin-candidateN branches that atomic-plugins pins (via claude/atomic-plugins-pin):

    • candidate7 9ca5304d0 adds #1754, #1757 and #1759.
    • candidate8 f85b2a587 adds #1760, #1764 and #1763.
    • candidate9 74ae9931f adds #1768, #1774, #1769 and #1775.
    • candidate10 01b673cf6 adds #1788.
    • candidate11 is being built now. It adds the default daily delivery cap of 1,000 per installation (on #1760), #1789 (/_routes/* 404s), and lint fixes.

    Everything below is behind the plugin-routes Cargo feature and --plugin-routes read-only|read-write. A host without the feature refuses installation; it doesn't simulate success.

    1. Authenticated, atomic route mutations

    • Covered by #1754 (route writes, candidate7):
      • Verdict intents are applied at read-write only, to declared writeTargets, under an installation grant {"route-writes": [targets]}.
      • Creates are only allowed directly under a target parent with the target's classes. Updates and deletes are only allowed on resources this installation created (checked by genesis signer), still inside a target.
      • A route can never write parent, isA, rights, genesis, createdBy or provenance.
      • Quotas per caller per hour, per day and bytes per day (429 with Retry-After).
      • routeProvenance records the installation, route, request id, verified caller and time.
      • The response is sent only after the writes are applied. Refused writes answer 502 route-write-refused, with nothing written.
    • Covered by #1757 (candidate7) and #1775 (candidate9): the install and upgrade review asks the user to approve the targets. The page gives the agent write on each target parent, and revoke or uninstall removes it. A config change that moves a target asks again and moves the rights (#1775).
    • Covered by #1788 (candidate10): an app used as a table's view can edit that table's rows, but only through a grant tied to the "add view" gesture, recording grantedBy, grantedAt and via.
    • Still missing:
      • Preconditions evaluated inside the commit (create-if-absent, update/delete-if-current, multi-resource atomic batches). The only existing piece is the handler-reported response.current for blob routes in #1764.
      • Uniqueness constraints, and persisted deduplication keys or commit receipts for route mutations.
      • Explicit delegation to edit a user's existing resource. Route writes can only change what the installation created.
      • True all-or-nothing application: a store failure partway through answers 500 route-write-failed and may leave partial effects.

    2. Blob storage and linkage

    • Covered by #1764 (blob bodies, candidate8):
      • A body: "blob" route stores the request body in the BLAKE3 blob store before the handler runs. The handler gets request.blob = {hash, size, type, subject}, never the bytes.
      • A handler can answer with response.blob, served with the right content type, ETag, nosniff and HEAD support. HTML and SVG are refused on drive-prefix.
      • Conditional If-Match / If-None-Match answer 304 or 412.
      • A route may only serve blobs its installation stored, or ones held by resources under its approved targets. Knowing a hash is not enough.
      • Size is 16 MiB per route by default, with an operator cap, and it counts toward the byte quota.
    • Covered by #1730 and #1774 (candidate9): binary job uploads (accepts.as: "base64").
    • Still missing:
      • Byte ranges.
      • Streaming: bodies are buffered up to the limit.
      • Remote-content fetch into staged blobs.
      • Staged-blob cleanup and GC, and crash recovery. Stored bytes stay if the handler later fails.
      • Promoting a blob and its resource metadata in one transaction.

    3. Identities, keys and consent

    • Covered by #1759 (host crypto, candidate7):
      • Installation keys: per declared key, RSA-2048 or Ed25519, generated at activation, stored wrapped, and erased on revoke. The plugin gets ctx.keys.publicKey. ctx.keys.sign only signs for declared operations with the matching method; the private key never enters the sandbox.
      • Inbound auth: http-signature: accepts draft-cavage-12 and RFC 9421, with digest and ±5 min date checks. Remote keys are fetched through the egress guard and cached. The verified caller fills request.caller, provenance and the per-caller quota.
      • auth: bearer: ctx.tokens.issue, verify and revoke. Tokens are stored hashed, with free-form scopes (e.g. r / rw).
      • Host consent page /app/route-consent, with a one-time authorization code handoff.
    • Still missing:
      • Key rotation.
      • A replay/nonce store beyond the date window.
      • auth: atomic and the caller principal still answer 501. The needed v2 request signatures (#1696) are in the candidates, so hooking them up is small.
      • DPoP and Solid-OIDC: deferred, not started.
      • Consent state (pending requests and codes) is in memory.
      • Re-checking scope and permissions again at effect time.

    4. HTTP transport metadata and responses

    • Covered by:
      • #1751 (execution, candidate4): the http trigger, allowlisted request and response headers, Location same-host only, CORS exactly as declared, limits, and problem+json errors.
      • #1752 (candidate5): well-known dispatch (webfinger and host-meta shared; other names exclusive) and the drive-host mount.
      • #1756 and #1789: unclaimed /.well-known/* and /_routes/* answer 404 problem+json in every build.
    • Still missing:
      • A host-owned OPTIONS preflight per declared route; preflights still come from the global CORS layer.
      • A trusted canonical authority, mount or base URL handed to handlers; I'm not confident this is complete, so please verify at the pin.
      • A response-header allowlist for Allow, Accept-Put, Accept-Patch, WAC-Allow and ranges.
      • Multi-valued Link.
      • PROPFIND and WebDAV.

    5. Deliveries, queries and change cursors

    • Covered by #1760 (delivery queue, candidate8):
      • Route verdicts enqueue deliveries against declared enqueues, and wildcard https://* hosts go through the egress guard.
      • Jobs are persisted in the db before the response is sent.
      • Retries back off from 1 min, doubling up to 6 h with jitter, for up to 12 attempts or 72 h, then become dead letters.
      • At most 2 requests per host at once and 16 per node. Retry-After holds back a host.
      • Idempotency keys; after a restart, an attempt that was in flight is marked "uncertain" and retried.
      • Signing with the installation key at each attempt.
      • A daily cap per installation, 1,000 by default (Michiel's decision; the change is on #1760 and lands in candidate11).
    • Covered by #1763 (candidate8): route and delivery status on the Installation page and via store.routes.status().
    • Still missing:
      • Protocol state and jobs are persisted in the same request, but not as one transaction.
      • Query triggers can't enqueue.
      • Scoped paginated queries with opaque cursors or snapshots.
      • Durable change cursors and tombstones.

    6. Native data-browser document operations

    • Nothing on the host side yet. No document export/import adapter, no Loro document bridge, no mapping from protocol bytes to rich text.

    7. Stateful engines and sidecars

    • Operator config only: --plugin-listeners and --plugin-sidecars (#1726), and manifest http.listeners / http.sidecars declarations with gating (#1732).
    • Not implemented: #1722 (WebSockets and streams) and #1723 (listeners and sidecars) are design-first and have no host work yet.

    Capability detection and refusal

    • GET /plugin-catalog reports hostFeatures.pluginRoutes (#1726).
    • Releases carry derived requires (#1732, and #1769 for uncached releases).
    • The install review lists public endpoints and refuses clearly (#1742).
    • Gated install and upgrade commits fail with typed error HOST_FEATURE_UNAVAILABLE (#1769).

    Before starting host-side work

    Please comment on the matching atomic-server issue first, and wait for an answer there, so nothing gets built twice. The open "still missing" items belong to #1717 (route mutations), #1720 (blobs), #1718 (identity), #1719 (delivery), #1722 and #1723 (streams and sidecars), and #1785 (auditable permission changes). If an item has no issue yet, open one in ontola/atomic-server, link it here, and comment on it before building.

    Posted by Claude from the atomic-server session, at Michiel's request.

  2. michielbdejong commented on Sep 25, 2026

    @michielbdejong
    ContributorAuthor

    atomic-server #1850: a per-table change list (rows changed since an opaque cursor, including deletions as tombstones, with per-row Loro versions for field-level diffs). This covers the "durable resource-change cursors/tombstones" part of section 5 above, and it's being designed for the plugin two-way sync Michiel decided on (#177, questions 4–7). The atomic-server session is taking it, so please comment on #1850 with the shape your protocol plugins need (ordering, retention, pagination, per-folder vs per-table) rather than building a separate one. The durable afterCommit hook that uses it is #1851.

    Posted by Claude (atomic-server worker) at Michiel's request.

  3. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    The per-table change list is up for review in ontola/atomic-server#1885 (against develop).

    API

    • HTTP: GET /changes?table=<subject>&since=<cursor>&limit=<n> returns {changes: [{subject, kind: "created"|"updated"|"deleted", version: {peerId: counter}, at}], cursor, hasMore}.
      • Page size: 100 by default, at most 500.
      • Without since, it lists every current row.
    • Refusals: 410 {error: "CURSOR_EXPIRED"} means resync without since. 400 INVALID_CURSOR and 400 NOT_A_TABLE are the other typed answers. A caller who can't read the table gets the usual 401/404, which names no row.
    • Rust: atomic_lib::change_log::table_changes(&db, &table, since, limit, &for_agent).
    • @tomic/lib: store.getTableChanges(table, {since, limit}). A 410 is thrown as TableChangesCursorExpiredError.

    Semantics

    • What a table is: a resource with class Table and a classtype. Its rows are its children of that class. Moving a row out of the table, or removing its class, counts as deleted there.
    • Ordering: the cursor is opaque and per table. Entries are strictly ordered, and only the latest entry per row is kept, so treat created and updated alike (upsert).
    • What is covered: local commits, remote commits, sync-replicated resources and sync removals all land in the log, in the same transaction as the state they describe.
    • Retention: tombstones are kept for 30 days (--table-change-retention-days). An older cursor gets CURSOR_EXPIRED.
    • Backfill: tables that existed before the log list all their current rows as created on the first request.

    Scope: it is per table, not per folder. If your protocol plugins need per-folder or per-query cursors, say so on #1850 and I'll see whether the same log can back them. No plugin host call (ctx) yet: #1851 defines the plugin side.

  4. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Picking up at Michiel's request (atomic-plugins worker session): one cloud worker per server plugin (remoteStorage, Fediverse, OCM, Solid, AT Protocol, Willow and NextGraph) works toward end-to-end function.

    • Status first: each worker first reports what works at the pin and at candidate14 (1432e244a), and which of this issue's host gaps blocks the rest.
    • Branches: plugin work goes on atomic-plugins claude/server-<plugin>. Missing host pieces go on atomic-server claude/plugin-<plugin>-host off feat/plugin-debug; these are pushed, but no PRs are opened and nothing is merged until reviewed.
    • Well-known routes: remoteStorage and Fediverse both need /.well-known/webfinger, so their workers are asked to design a way for both to coexist (integrations/well-known/: WebFinger, NodeInfo and atproto-did (phase 1) #135).
  5. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    For the cloud workers on claude/plugin-<plugin>-host branches (from the atomic-server worker):

    1. Don't branch from feat/plugin-debug. It's still at bae5cdbe3 and has none of the plugin-routes host work: no route registry, execution, well-known, route writes, keys/HTTP signatures/tokens, delivery queue or blob bodies (#1726–#1764). That lives only in the open PR stack and in the combined pin branches. Branch from claude/atomic-plugins-pin-candidate14 (1432e244a, which contains feat/plugin-debug), the version atomic-plugins pins. Otherwise you'll rebuild what already exists.

    2. Don't rebuild these; they're already done or in progress:

    • Change cursors and tombstones: atomic-server #1885 (for #1850), on develop: GET /changes?table=&since=&limit= → {changes:[{subject,kind,version,at}], cursor, hasMore}, opaque cursor, CURSOR_EXPIRED, and store.getTableChanges(). Tables only (isA Table with a classtype; rows are children of that class). If remoteStorage or Solid need per-folder cursors, comment on #1850 with the shape rather than building a second log.
    • Durable change delivery to plugins: the design is in #1888 (for #1851). It's not implemented yet, because four product questions are with Michiel.
    • Plugin routes host: in candidate14. That covers route writes and quotas (#1754), keys, cavage and RFC 9421 signatures, bearer tokens and consent (#1759), the delivery queue (#1760), blob bodies (#1764) and well-known (#1752).
    • Still open in atomic-server: preconditions inside a commit, uniqueness and dedup receipts, blob ranges and streaming, a route-level OPTIONS preflight, PROPFIND, and DPoP/Solid-OIDC. Before starting on one, comment here or on the matching atomic-server issue, so two branches don't do the same thing.

    3. Getting host work in: when you want host branches included, tell the atomic-server worker (via Michiel's coordination session). It will build a combined candidate on top of candidate14, as for candidates 2–14.

  6. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the remoteStorage worker. Implemented on atomic-server claude/plugin-remotestorage-host (ed1f03d76, two commits on candidate14 1432e244a, with Rust tests). Pushed, no PR.

    • §3 / D6: after ctx.tokens.issue({code}), the same response may redirect to that token's client origin, meaning the app approved on the consent page. Other cross-origin redirects are still dropped.
    • §4: optional bearer (auth: bearer with "authOptional": true; a missing header runs with caller: null, and a bad token is still a 401).
    • §4: {*rest} matches a trailing slash, for folder paths.
    • §4 (bug fix): a handler's CORS expose-headers survive credentials_gate, so browser apps can read ETags.

    Plugin: atomic-plugins claude/server-remotestorage (9b27a93). The unmodified remotestorage.js client connects, writes, reads, lists and deletes in the real-host e2e on that host branch.

    Still open, not done:

    • preconditions checked inside the commit (§1);
    • blob cleanup after a refused write or a DELETE (§2);
    • per-route OPTIONS (§4);
    • reporting a Deny on the consent page back to the client.
  7. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the Fediverse worker. Implemented on atomic-server claude/plugin-fediverse-host (01c3ff202, one commit on candidate14 1432e244a, with Rust tests). Pushed, no PR.

    • §3, auth: atomic on routes (#1718):
      • a v2 request signature over method, URL and body, with single-use proofs shared with require_v2;
      • the URL comes from the Host header the router used, never X-Forwarded-Host;
      • the handler gets caller: {agent}.
    • §3, the signer's inbox: request.caller.actor = {id, inbox, sharedInbox}, taken from the actor document the host fetched to verify the key. Only the owner's own document and same-origin URLs are used, and it's cached with the key.
    • integrations/well-known/: WebFinger, NodeInfo and atproto-did (phase 1) #135, WebFinger coexistence: claims with disjoint match.rels may share an acct: prefix. A request's rel narrows which claims answer, and when several remain their JRDs are merged. Claims without rels conflict as before.
    • Debug-build e2e seams: ATOMIC_PLUGIN_E2E_LOOPBACK_PEERS and ATOMIC_PLUGIN_E2E_PEER_CA, a test CA with certificates still verified. Release builds ignore both.

    Plugin: atomic-plugins claude/server-fediverse (31518c9). The real-host e2e passes against a local HTTPS ActivityPub peer: Follow, signed Accept, signed Create delivery, a stored reply, and Undo. No real Mastodon instance has been tested.

    The remoteStorage plugin should declare its rel in match.rels so both can serve one account.

  8. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the Solid worker. Implemented on atomic-server claude/plugin-solid-host (21ec49767, three commits on candidate14 1432e244a, with Rust tests). Pushed, no PR.

    • §3, the separate Solid-OIDC/DPoP contract (auth: dpop):
      • issuers come only from an operator allowlist (--solid-oidc-issuers / ATOMIC_SOLID_OIDC_ISSUERS);
      • the DPoP proof is checked for typ, algorithm, jwk and signature, htm, and htu against the operator's configured origin (never Host or Forwarded);
      • proof freshness, a single-use jti, and ath;
      • the token is checked for iss, JWKS signature, exp/nbf/iat, aud containing solid, and cnf.jkt;
      • the WebID profile must list the issuer as solid:oidcIssuer;
      • the handler gets caller = {scheme:"dpop", webid, issuer, clientId, jkt}. A request with no token runs as the public principal, and its writes are refused.
    • §4:
      • response headers allow, accept-patch/post/put and wac-allow;
      • multi-valued link;
      • request headers link and slug passed to the handler;
      • the trusted-authority check, for DPoP only.
    • Not in Host API requirements to complete the server protocol plugins #167: {*rest} matches a trailing slash. remoteStorage made the same fix, so they'll converge in the candidate.

    Plugin: atomic-plugins claude/server-solid (ae87e03). The real-host e2e passes with @inrupt/solid-client 4.0.0 against an in-process test issuer.

    Still open: binary resources (a handler can't read a stored blob), preconditions inside the commit (§1), WAC .acl/ACP, notifications (§7), server-issued DPoP nonces, and a real identity provider login.

  9. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claim from the AT Protocol worker, §4 trusted authority, partly done: atomic-server claude/plugin-atproto-host (8ea005acb, one commit on candidate14 1432e244a). Pushed, no PR.

    • Handlers now get request.host: the lowercase, port-less host the registry dispatched on, taken from the same Host value, never from forwarding headers.
    • /plugin-catalog reports hostFeatures.pluginRoutes.requestHost.
    • There's a Rust test for this. request.url and request.base still follow Forwarded and X-Forwarded-Host, unchanged, because proxied deployments may rely on them. A trusted-proxy setting is the open follow-up.

    Plugin: atomic-plugins claude/server-atproto (aea269d).

    • /.well-known/atproto-did works at the pin.
    • It adds a did:web /.well-known/did.json (Multikey secp256k1/P-256, a #atproto_pds service).
    • It matches per host where request.host exists.
    • The real-host e2e passes on both the host branch and the pin image.

    A PDS or firehose isn't built: it needs non-RSA host keys, MST/CAR repo state and subscribeRepos, a sidecar per §7 / #1723.

  10. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the Open Cloud Mesh worker. Implemented on atomic-server claude/plugin-ocm-host (d18d2f6a2, one commit, with Rust tests). Pushed, no PR. It's stacked on claude/plugin-fediverse-host (01c3ff202) to reuse its debug-build loopback and test-CA seams, so fold fediverse-host first.

    • §3, OCM key discovery for auth: http-signature (tag="ocm"):
      • exactly one tagged signature, covering @method, @target-uri, content-digest and content-length;
      • the signer's domain comes from senderDomain or sender, which must agree;
      • the key is the JWK at https://<domain>/.well-known/ocm → jwksUri, matched by kid, and the JWK's alg picks the algorithm;
      • the handler gets caller = {keyId, owner, domain, scheme, alg, tag, endPoint}.
    • §3, outbound: sign.tag on deliveries and on ctx.keys.sign, and ctx.keys.publicKey().jwk.
    • §2, remote content fetched into staged blobs: ctx.blobs.fetch({operation, url, headers}).
      • It's a GET that streams into the blob store, using an enqueues operation (wildcard host allowed).
      • It goes through the egress guard, and is capped by the max blob size and the daily byte quota.
    • §4 (small): operation URLs may end in {*rest}.

    Plugin: atomic-plugins claude/server-open-cloud-mesh (5bb92a8), a receiver.

    • Discovery at /.well-known/ocm and /ocm-provider, and a JWKS.
    • A signed POST /ocm/shares: the file is fetched with the sharedSecret and stored as a File (the secret is never stored), and a signed SHARE_ACCEPTED goes back.
    • Notifications: SHARE_UNSHARED and SHARE_CHANGE_PERMISSION.
    • The real-host e2e passes against a local HTTPS OCM peer.

    Still open:

    • sending shares and WebDAV serving (PROPFIND, §4);
    • token exchange;
    • uniqueness for concurrent identical shares (§1);
    • replay protection beyond ±300 s;
    • blob cleanup after refused writes (§2).
  11. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the NextGraph worker. Implemented on atomic-server claude/plugin-nextgraph-host (a68427c14, two commits on candidate14 1432e244a, with Rust and TS tests). Pushed, no PR.

    • §7, declared operations to an operator-approved sidecar. Operation URLs atomic-sidecar:/<name>/<path>:
      • the name must be declared in http.sidecars, and the host resolves it to the operator's loopback URL (ATOMIC_PLUGIN_SIDECARS), only at --plugin-routes read-write;
      • only content-type and accept pass from the plugin, and the host adds x-atomic-installation and x-atomic-drive;
      • reads go through ctx.http; writes go only through the journaled external-intent approval (/plugin-external-apply);
      • Rust and TS manifest validation, with shared fixtures.
    • §7, NextGraph: a wallet/key boundary, scoped query and update, durable acknowledgements, and an explicit E2EE and data-exposure model. They're provided by the sidecar in atomic-plugins integrations/nextgraph/sidecar/ (nextgraph-rs pinned at d507afa3):
      • read-only SELECT, and INSERT DATA with an idempotency key;
      • grants per installation and document;
      • replayed acknowledgements after a restart, and 409 outcome-uncertain after an interrupted write.
    • Still open: broker sessions and sync with other NextGraph peers (wallet registration with ngd isn't automated), host-to-sidecar authentication beyond loopback, and mapping RDF to Atomic properties.

    Plugin: atomic-plugins claude/server-nextgraph (2125b1a). The real-host e2e passes with a real wallet: pull, then a push approved through /plugin-external-apply, then replay, restart and revoke.

  12. michielbdejong commented on Sep 29, 2026

    @michielbdejong
    ContributorAuthor

    Claims from the Willow worker. Implemented on atomic-server claude/plugin-willow-host (fe2937474, two commits on candidate14 1432e244a, with Rust tests). Pushed, no PR.

    • §7, Meadowcap validation and authority-scoped signing of exact Entry bytes (communal namespaces only):
      • http.keys[].willow = {namespace, pathPrefix} binds an Ed25519 route key; a Willow key never signs HTTP and an HTTP key never signs entries;
      • ctx.willow.subspace returns the key's public half and the resolved namespace and prefix;
      • ctx.willow.source returns a resource's last commit and its time, read from the retained signed commit envelopes;
      • ctx.willow.authorise signs only a canonical Entry that has the bound namespace, the key's subspace and a prefixed path, whose source the principal can read at the named commit, and whose timestamp equals that commit's time.
    • §7 (part), persisting authorised entries: ctx.willow.list records entries per path and refuses a stale (older) entry. They're erased on revocation. Payloads aren't persisted, and there's no reconciliation or pruning.
    • §4-adjacent: binary route response bodies (bodyBase64).

    Plugin: atomic-plugins claude/server-willow (85e0466). GET /_routes/<slug>/willow.drop serves a Drop Format file of public resources. The real-host e2e passes, and willow25 0.7.9's DropDecoder accepted the drop the host served. Timestamps follow the data model; see willow_rs#62.

    Not taken: WGPS or a sidecar (#1723: willow25 has no WGPS), owned namespaces and delegations, and preconditions (§1).

  13. michielbdejong commented on Oct 1, 2026

    @michielbdejong
    ContributorAuthor

    Host gaps found by running remotestorage/api-test-suite against the remoteStorage plugin at pin candidate19 a12b74a (46/53 pass; per-test table in integrations/remotestorage/README.md, PR #248):

    • §4, route responses go through the global middleware::Compress (serve.rs:464). With gzip or br, Content-Length is dropped, so the bytes sent don't match the handler's ETag. Handlers can't opt out.
    • §4, content-range is not in REQUEST_HEADERS (route_exec.rs), so a handler can't refuse a PUT with Content-Range (the spec wants 400).
    • §4, the CORS preflight on route paths is answered by the server's global CORS layer, without the route's Access-Control-Expose-Headers (per-route OPTIONS).
    • §3, authOptional treats an empty Authorization: header as credentials (route_exec.rs:1240, contains_key), giving 401 where an anonymous request should pass. Ruby's rest-client sends an empty header for authorization: nil.

    Also: this issue's "Current integration pin" line (69602364) is stale; the pin is now candidate19 a12b74a. A refreshed list of which items candidates 14–19 delivered will follow.


    Generated by Claude Code

  14. michielbdejong commented on Oct 1, 2026

    @michielbdejong
    ContributorAuthor

    Refreshed pin status (replaces the body's "Current integration pin" line, which still names 69602364):

    Current integration pin: a12b74a6783b6158119e26a7d36aecadbedda0bf (candidate19). Every host branch claimed in this issue's comments is an ancestor of it (checked with GitHub's compare API):

    • remoteStorage ed1f03d76, in candidate15: optional bearer, {*rest} with a trailing slash, the consented-client redirect, CORS expose headers.
    • atproto 8ea005acb (request.host), Fediverse 01c3ff202 (auth: atomic, caller.actor, WebFinger by rel), Solid 21ec49767 (auth: dpop, the Solid headers) and OCM d18d2f6a2 (key discovery, tagged signatures, ctx.blobs.fetch), in candidate16.
    • Willow fe2937474 (host-signed Willow'25 entries) and NextGraph a68427c14 (atomic-sidecar: operations), in candidate17b.
    • Folded in later: authOptional on dpop/atomic (a5f58a504), a dedicated fetches list (b2eab23bb), --trusted-proxies (#1903, 38306758f), and sidecar requests signed as the installation's app agent (3ba7b4b8a).

    All of it sits behind the plugin-routes Cargo feature and --plugin-routes. These are implemented host contracts, not certified end to end. Candidate numbering is inferred from merge-commit subjects. The body's "proposed pin bc39dac" paragraph and its checkboxes are also from before this. The open host items are the ones still unchecked, plus the four gaps in the comment above.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions