OCM (Open Cloud Mesh) compatibility adapter — interop with Nextcloud/ownCloud #117

Closed
opened 2026-08-04 23:00:31 +00:00 by Cordy · 5 comments
Owner

Decided 2026-08-05 (Q7 on #100): revisit and implement in v0.6, beside — not instead of — Cairn's own peering.

Why. Open Cloud Mesh is the incumbent federation protocol: in the field since ~2016, implemented by Nextcloud, ownCloud, OpenCloud, CERNBox and Seafile, and on the IETF standards track since March 2026 (draft-ietf-ocm-open-cloud-mesh-04). Speaking it turns "migrate off Nextcloud" into "put Cairn next to your Nextcloud and share across" — the single strongest migration story available to us, and a procurement checkbox in exactly the federated-research and public-sector markets that already deployed OCM.

What it is not. OCM does not replace our peering (IMPLEMENTED-PEERING.md). They answer different questions:

  • OCM is a control-plane notification protocol: it tells a remote party they have been granted access to a resource, over federated cloud IDs and discovery.
  • Cairn peering moves the bytes between two instances whose admins explicitly paired out of band, with no discovery and no identity federation.

Our positioning is the narrow trust model; OCM is the interop bridge. Both can exist — nothing in the peering design forecloses it, because OCM would sit beside the transfer plane rather than inside it.

Scope to settle at implementation time:

  • Inbound only first (accept an OCM share from a Nextcloud/ownCloud/OpenCloud instance and surface it), or bidirectional?
  • Which spec revision to target — the IETF draft vs. what Nextcloud/ownCloud actually ship today (the drafts note ownCloud's implementation lags OpenCloud's).
  • The identity question: OCM assumes federated cloud IDs (user@server). We have deliberately avoided identity federation. Does an OCM share map onto a local user, an Inbox drop, or a distinct "external shares" surface?
  • Discovery and abuse: OCM's open-federation posture is precisely what our peering avoids. Whatever we build must be opt-in per instance, with an admin allow-list of federating servers — not open federation by default.
  • Encryption: OCM shares of encrypted content hit the same wall #19 solved for links; likely reuse the same on-the-fly re-encryption pipeline.

Prerequisite: v0.5 peering shipped, so this is a bridge from something rather than the first federation implementation.

Decided 2026-08-05 (Q7 on #100): revisit and implement in v0.6, beside — not instead of — Cairn's own peering. **Why.** [Open Cloud Mesh](https://datatracker.ietf.org/doc/draft-ietf-ocm-open-cloud-mesh/) is the incumbent federation protocol: in the field since ~2016, implemented by Nextcloud, ownCloud, OpenCloud, CERNBox and Seafile, and on the IETF standards track since March 2026 (`draft-ietf-ocm-open-cloud-mesh-04`). Speaking it turns "migrate off Nextcloud" into "put Cairn next to your Nextcloud and share across" — the single strongest migration story available to us, and a procurement checkbox in exactly the federated-research and public-sector markets that already deployed OCM. **What it is not.** OCM does not replace our peering (`IMPLEMENTED-PEERING.md`). They answer different questions: - OCM is a **control-plane notification** protocol: it tells a remote party they have been granted access to a resource, over federated cloud IDs and discovery. - Cairn peering **moves the bytes** between two instances whose admins explicitly paired out of band, with no discovery and no identity federation. Our positioning is the narrow trust model; OCM is the interop bridge. Both can exist — nothing in the peering design forecloses it, because OCM would sit beside the transfer plane rather than inside it. **Scope to settle at implementation time:** - Inbound only first (accept an OCM share from a Nextcloud/ownCloud/OpenCloud instance and surface it), or bidirectional? - Which spec revision to target — the IETF draft vs. what Nextcloud/ownCloud actually ship today (the drafts note ownCloud's implementation lags OpenCloud's). - The identity question: OCM assumes federated cloud IDs (`user@server`). We have deliberately avoided identity federation. Does an OCM share map onto a local user, an Inbox drop, or a distinct "external shares" surface? - Discovery and abuse: OCM's open-federation posture is precisely what our peering avoids. Whatever we build must be **opt-in per instance**, with an admin allow-list of federating servers — not open federation by default. - Encryption: OCM shares of encrypted content hit the same wall #19 solved for links; likely reuse the same on-the-fly re-encryption pipeline. **Prerequisite:** v0.5 peering shipped, so this is a bridge from something rather than the first federation implementation.
Author
Owner

Triage: tandem (large; needs a real Nextcloud/ownCloud). OCM (Open Cloud Mesh) adapter for cross-product share/interop. Big surface; design + scope first, and interop testing needs a live Nextcloud/ownCloud peer. Post-launch tier — park until the closed beta settles.

**Triage: tandem (large; needs a real Nextcloud/ownCloud).** OCM (Open Cloud Mesh) adapter for cross-product share/interop. Big surface; design + scope first, and interop testing needs a live Nextcloud/ownCloud peer. Post-launch tier — park until the closed beta settles.
Author
Owner

Design approved 2026-09-07 and recorded in OCM.md (root, playing the role PEERING.md played for peering). Mockup rev 2 approved (ocm-federation-mockup.html). Summary of the settled scope questions from this issue:

  • Bidirectional in v1 — inbound-only fails the first share-back; both directions or it isn't sellable.
  • Spec target: draft-ietf-ocm-open-cloud-mesh-04, with deliberate dialect compat for what's deployed: legacy /ocm-provider + 1.0-proposal1 + publicKey for vanilla Nextcloud/ownCloud 10, /.well-known/ocm 1.1 for OpenCloud/CERNBox. No /status.php shim — that out-of-spec probe only matters for NC "trusted servers" directory sync, which is not our model.
  • Identity: an OCM share maps to a local user as recipient and surfaces in a new Federated view (live access grant, Cairn as WebDAV client) — not an Inbox drop (wrong semantics: revocation/write-back must be real) and not a Personal mount (Nextcloud's etag/sync bug farm).
  • Discovery/abuse: opt-in per instance (off = endpoints don't answer), server allow-list as the hard gate both directions, per-server strictness ("invited contacts only" default, "any user" as deliberate per-server relaxation for vanilla NC/Seafile), user-owned invites/contacts (spec's invite flow, philosophically identical to peering's key handover), RFC 9421 signatures, no lookup server ever.
  • Encryption: outbound shares of encrypted content reuse the #19 on-the-fly pipeline, as anticipated.
  • Implementation: hand-rolled internal/ocm (Reva rejected as dependency; kept as dialect documentation), cs3org OCM Test Suite pattern as CI interop harness + live tests against the lab Nextcloud.
  • UI: admin rail splits into "Access & sharing" + new Federation section holding Cairn Peering and Open Cloud Mesh; new audit verb group ocm (registry + scanner per #433).

Interop acceptance matrix and build phasing are in OCM.md §6/§10. Next: implementation plan, then the phased TDD build; ARCHITECTURE/handbook/wiki flowchart land with the ship per §10.7.

Design approved 2026-09-07 and recorded in **[OCM.md](http://192.168.10.245/Cordy/Cairn/src/branch/main/OCM.md)** (root, playing the role PEERING.md played for peering). Mockup rev 2 approved (`ocm-federation-mockup.html`). Summary of the settled scope questions from this issue: - **Bidirectional in v1** — inbound-only fails the first share-back; both directions or it isn't sellable. - **Spec target:** `draft-ietf-ocm-open-cloud-mesh-04`, with deliberate dialect compat for what's deployed: legacy `/ocm-provider` + `1.0-proposal1` + `publicKey` for vanilla Nextcloud/ownCloud 10, `/.well-known/ocm` 1.1 for OpenCloud/CERNBox. **No `/status.php` shim** — that out-of-spec probe only matters for NC "trusted servers" directory sync, which is not our model. - **Identity:** an OCM share maps to a **local user** as recipient and surfaces in a new **Federated view** (live access grant, Cairn as WebDAV client) — not an Inbox drop (wrong semantics: revocation/write-back must be real) and not a Personal mount (Nextcloud's etag/sync bug farm). - **Discovery/abuse:** opt-in per instance (off = endpoints don't answer), **server allow-list as the hard gate both directions**, per-server strictness ("invited contacts only" default, "any user" as deliberate per-server relaxation for vanilla NC/Seafile), user-owned invites/contacts (spec's invite flow, philosophically identical to peering's key handover), RFC 9421 signatures, no lookup server ever. - **Encryption:** outbound shares of encrypted content reuse the #19 on-the-fly pipeline, as anticipated. - **Implementation:** hand-rolled `internal/ocm` (Reva rejected as dependency; kept as dialect documentation), cs3org OCM Test Suite pattern as CI interop harness + live tests against the lab Nextcloud. - **UI:** admin rail splits into "Access & sharing" + new **Federation** section holding **Cairn Peering** and **Open Cloud Mesh**; new audit verb group `ocm` (registry + scanner per #433). Interop acceptance matrix and build phasing are in OCM.md §6/§10. Next: implementation plan, then the phased TDD build; ARCHITECTURE/handbook/wiki flowchart land with the ship per §10.7.
Author
Owner

Shipped in v0.6.139 (PR #448), live on both dogfoods (cairn_build_info verified).

Decision record (full design: OCM.md; guide: docs/handbook/federation-ocm.md; flowchart: wiki OCM-Federation)

Every scope question from this issue, settled and built:

  • Bidirectional in v1 — inbound-only fails the first share-back.
  • Spec revision: draft-ietf-ocm-open-cloud-mesh-04 target with deliberate dialect compat for what's deployed: we serve both /.well-known/ocm (1.1) and legacy /ocm-provider (1.0-proposal1 with publicKey), and we parse all three field dialects including ownCloud 10's shareTypes-as-resourceTypes quirk and trailing-slash discovery. Signatures both ways: RFC 9421 and draft-cavage. No /status.php shim (NC "trusted servers" directory sync is not federation).
  • Identity: an OCM share maps to a local user and surfaces in the Federated view — a live access grant browsed through Cairn as WebDAV client, never a Personal mount, never a silent copy ("Save a copy to Personal" is explicit and the copy is then encrypted at rest). Remote shares never appear in WebDAV/sync trees.
  • Discovery/abuse: opt-in per instance (disabled = endpoints don't answer at all — black-box verified), server allow-list enforced at every endpoint in both directions, per-server strictness (invited-contacts-only default; stamped, audited relaxation for invite-less peers), user-owned invites (spec-canonical string, single-use, 7-day), readable refusals on the wire.
  • Encryption: outbound decrypts on the fly through the live driver stack (same as share links, per #19); no key ever crosses the wire; download marking (#423) does not apply to server-to-server grant reads — the audit log is that trail.
  • Implementation: hand-rolled internal/ocm, stdlib crypto only; Reva rejected as dependency, kept as dialect documentation. Ten audit verbs in group ocm (the #433 scanner caught this feature's own non-literal carrier during development).

Verification: TDD with witnessed reds throughout; ocm-interop.yml (kept) black-boxes the real binary — discovery both dialects, jwks, inbound share, refusal-names-the-server, bearer-jailed grant, disabled-is-silent — ALL GREEN on main before tagging.

Open follow-ups (deliberate v1 scope lines, filed here for the beta):

  1. OCM-sharing of /spaces/ paths (v1 shares Personal only).
  2. Live Nextcloud interop: ocm-live-nc.yml is ready but needs operator setup — enable OCM on the dogfood, allow nextcloud.c0rdyceps.ch (policy "any user"), add Forgejo secrets NC_URL/NC_USER/NC_APP_PW — then run it watching the Federated view.
  3. Local-mode UserExists tightening + per-peer full-URI signing conventions, both riding the interop findings.
  4. OCM reachability gauge + Prometheus alert (deferred with the metrics pass).
Shipped in **v0.6.139** (PR #448), live on both dogfoods (`cairn_build_info` verified). ## Decision record (full design: OCM.md; guide: docs/handbook/federation-ocm.md; flowchart: wiki [[OCM-Federation]]) Every scope question from this issue, settled and built: - **Bidirectional in v1** — inbound-only fails the first share-back. - **Spec revision:** `draft-ietf-ocm-open-cloud-mesh-04` target with deliberate dialect compat for what's deployed: we *serve* both `/.well-known/ocm` (1.1) and legacy `/ocm-provider` (1.0-proposal1 with `publicKey`), and we *parse* all three field dialects including ownCloud 10's shareTypes-as-resourceTypes quirk and trailing-slash discovery. Signatures both ways: RFC 9421 and draft-cavage. No `/status.php` shim (NC "trusted servers" directory sync is not federation). - **Identity:** an OCM share maps to a local user and surfaces in the **Federated view** — a live access grant browsed through Cairn as WebDAV client, never a Personal mount, never a silent copy ("Save a copy to Personal" is explicit and the copy is then encrypted at rest). Remote shares never appear in WebDAV/sync trees. - **Discovery/abuse:** opt-in per instance (disabled = endpoints don't answer at all — black-box verified), **server allow-list enforced at every endpoint in both directions**, per-server strictness (invited-contacts-only default; stamped, audited relaxation for invite-less peers), user-owned invites (spec-canonical string, single-use, 7-day), readable refusals on the wire. - **Encryption:** outbound decrypts on the fly through the live driver stack (same as share links, per #19); no key ever crosses the wire; download marking (#423) does not apply to server-to-server grant reads — the audit log is that trail. - **Implementation:** hand-rolled `internal/ocm`, stdlib crypto only; Reva rejected as dependency, kept as dialect documentation. Ten audit verbs in group `ocm` (the #433 scanner caught this feature's own non-literal carrier during development). **Verification:** TDD with witnessed reds throughout; `ocm-interop.yml` (kept) black-boxes the real binary — discovery both dialects, jwks, inbound share, refusal-names-the-server, bearer-jailed grant, disabled-is-silent — **ALL GREEN** on main before tagging. **Open follow-ups** (deliberate v1 scope lines, filed here for the beta): 1. OCM-sharing of `/spaces/` paths (v1 shares Personal only). 2. Live Nextcloud interop: `ocm-live-nc.yml` is ready but needs operator setup — enable OCM on the dogfood, allow `nextcloud.c0rdyceps.ch` (policy "any user"), add Forgejo secrets `NC_URL`/`NC_USER`/`NC_APP_PW` — then run it watching the Federated view. 3. Local-mode `UserExists` tightening + per-peer full-URI signing conventions, both riding the interop findings. 4. OCM reachability gauge + Prometheus alert (deferred with the metrics pass).
Cordy closed this issue 2026-09-07 23:31:40 +00:00
Author
Owner

Archive: the implementation plan this feature was built from (written 2026-09-07 against OCM.md + mockup rev 3, executed 2026-09-07/08; kept here so the plan, the spec and the decision record live in one place).


OCM Federation Implementation Plan (#117)

Goal: Bidirectional Open Cloud Mesh federation (Nextcloud/ownCloud/OpenCloud/Seafile/CERNBox interop) beside Cairn Peering, per OCM.md.

Architecture: New internal/ocm package following the internal/peering registry idiom (atomic JSON through statestore.Read/Write, mutex-guarded, fail-closed). Three signed inbound JSON endpoints + two discovery documents + a jwks endpoint; outbound = dialect-detecting discovery client + signed share POSTs; bytes move over WebDAV (inbound: Cairn as WebDAV client proxying to the user; outbound: a bearer-scoped /dav/ocm/<providerId>/ grant surface). UI per approved mockups rev 3.

Tech Stack: Go stdlib only (crypto/rsa, crypto/sha256, net/http); no new dependencies. Frontend: vanilla JS in web/static/index.html house style. CI: Forgejo one-shot workflows on the runner (house TDD: witnessed red → splice → green).

Global Constraints

  • No new Go dependencies. RFC 9421 + legacy draft-cavage signatures hand-rolled on stdlib crypto.
  • Off by default: settings.OCMEnabled false ⇒ discovery/jwks/ocm endpoints return 404 and no outbound calls occur.
  • Allow-list checked at every inbound endpoint AND before every outbound POST. Per-server policy default contacts (strict).
  • en-GB copy; i18n ×4; mockup rev 3 is the UI contract.
  • House workflow: branch ocm-117; every task = red test pushed via MCP (witnessed failing on runner) → one-shot splice workflow → green full suite.
  • New audit verbs go into the registry group ocm in the same task that first emits them (scanner tripwire enforces this).
  • Federated identities always rendered with their server; never addable to Spaces.

Tasks (each: red test → witnessed red → implement → green full suite → commit)

  1. State registriesinternal/ocm/state.go: Server (allow-list entry: fqdn, addedBy/At, Policy contacts|any, relaxedBy/At stamp, cached Disco), per-user Contact + single-use 7-day Invite, ShareIn/ShareOut ledgers; Open(4 paths), Allow/Get/List/SetPolicy/Remove (Remove ends every share both ways and returns them), contacts CRUD + HasContact(user, addr), MintInvite/TakeInvite (prune on take), share status transitions + secret/providerId lookups. Persisted via statestore.Read/Write, peering idiom.
  2. Keys + discoveryEnsureKey (stable RSA-2048, persisted), JWKS(), PublicPEM(); ParseDiscovery normalising IETF 1.1 / NC 1.0-proposal1 (legacy publicKey captured) / oC10 shareTypes-as-resourceTypes quirk; OurDiscovery (1.1) + OurLegacyDiscovery (1.0 shape), round-tripped through our own parser in tests. Fixtures verbatim from field research.
  3. HTTP signaturesSign (RFC 9421 over @method/@target-uri/content-digest/date, rsa-v1_5-sha256, fqdn#key1), SignLegacy (draft-cavage: (request-target) content-length date digest), Verify auto-detecting with errors naming exactly what failed; KeyFromJWKS/KeyFromPEM. (Execution finding: sign the request-target, not the absolute URL — the base must survive the client→server hop.)
  4. Inbound endpointsocm.Service with injected closures (Enabled, UserExists, PeerKey): both discovery routes (+ oC10 trailing slash), jwks, POST /ocm/shares pipeline (allow-list → signature, unsigned tolerated only for keyless peers → per-server policy → known recipient → ledger, refusals naming the reason), POST /ocm/notifications (ACCEPTED/DECLINED/UNSHARED). settings.OCMEnabled; main wires the four state files into the statestore binds; every route 404s while disabled.
  5. Outbound + grantClient.Discover (well-known → /ocm-provider → trailing slash), SendShare dialect-adaptive (legacy options vs protocol.webdav), SendNotification, FetchRemote (+ RFC 6749 code-flow client for must-exchange-token), ListRemote (namespace-agnostic multistatus, proven against our own grant); GrantHandler for /dav/ocm/<pid>/ — constant-time bearer, subtree jail, GET/HEAD/PROPFIND always, PUT only on write shares, dead when the share ends; mounted over the unscoped driver (audit + #19 decryption apply).
  6. Invites + APIsEncodeInvite/DecodeInvite (canonical base64 token@provider, last-@ split), inviter-side /ocm/invite-accepted (allow-list before token burn), Client.AcceptInvite; user API (shares list/send/accept/decline/end both directions, browse/fetch proxy, invites, contacts) + admin API (status, servers list/check/allow/policy/remove); ten audit verbs group ocm.
  7. Frontend — Federation rail split (Cairn Peering + Open Cloud Mesh), OCM admin topic (Settings + Servers per mockup), Federated view (Shares = federations as folders → inside view with Shared-with-you/You-shared groups + live remote browse + explicit Save-a-copy; Requests with badge + dot; user-owned Contacts), share-dialog federated mode, boot feature probe, i18n ×4. (Execution finding: a new admin topic needs FOUR touch points — ADM_TOPICS, ADM_TABS, slot HTML, renderAdminTopic branch + save-button list.)
  8. Interop harness — runner has no container capability → black-box fallback: ocm-interop.yml (kept) builds the real binary, seeds state, drives the wire with curl (discovery both dialects, jwks, inbound share, named refusal, grant jail, disabled-is-silent); ocm-live-nc.yml (kept, secrets-gated) drives the lab Nextcloud via OCS.
  9. Docs + release — handbook federation-ocm.md, README bullet, ARCHITECTURE entry, CHANGELOG; wiki page with the end-to-end Mermaid flowchart; PR → CI → merge → interop green on main → tag → bump dogfoods → verify → close with decision record.

Self-review notes

  • Spec coverage: OCM.md §2 decisions → tasks 1 (policy), 4 (gate, readable refusals), 5 (live view backend, grant), 6 (invites user-owned), 7 (UI incl. federation-as-folder), 8 (harness), 9 (docs/flowchart). §5 encryption needs no extra task: the grant reads through the live driver stack which already carries the #19 decrypt pipeline; marking exclusion holds because the grant mounts below marks.
  • Type consistency: Store, Discovery, ShareIn/Out, Policy used identically across tasks; Verify returns (string, error) everywhere.
  • Known risk, stated upfront: task 8 depended on the runner's container capability — probed, fallback executed, never silently skipped.

(The full step-level plan with verbatim test code lives in the session file 2026-09-07-ocm-federation-plan.md; deltas between plan and execution are recorded in the closing decision-record comment above.)

**Archive: the implementation plan this feature was built from** (written 2026-09-07 against OCM.md + mockup rev 3, executed 2026-09-07/08; kept here so the plan, the spec and the decision record live in one place). --- # OCM Federation Implementation Plan (#117) **Goal:** Bidirectional Open Cloud Mesh federation (Nextcloud/ownCloud/OpenCloud/Seafile/CERNBox interop) beside Cairn Peering, per OCM.md. **Architecture:** New `internal/ocm` package following the `internal/peering` registry idiom (atomic JSON through `statestore.Read/Write`, mutex-guarded, fail-closed). Three signed inbound JSON endpoints + two discovery documents + a jwks endpoint; outbound = dialect-detecting discovery client + signed share POSTs; bytes move over WebDAV (inbound: Cairn as WebDAV client proxying to the user; outbound: a bearer-scoped `/dav/ocm/<providerId>/` grant surface). UI per approved mockups rev 3. **Tech Stack:** Go stdlib only (crypto/rsa, crypto/sha256, net/http); no new dependencies. Frontend: vanilla JS in `web/static/index.html` house style. CI: Forgejo one-shot workflows on the runner (house TDD: witnessed red → splice → green). ## Global Constraints - No new Go dependencies. RFC 9421 + legacy draft-cavage signatures hand-rolled on stdlib crypto. - Off by default: `settings.OCMEnabled` false ⇒ discovery/jwks/ocm endpoints return 404 and no outbound calls occur. - Allow-list checked at every inbound endpoint AND before every outbound POST. Per-server policy default `contacts` (strict). - en-GB copy; i18n ×4; mockup rev 3 is the UI contract. - House workflow: branch `ocm-117`; every task = red test pushed via MCP (witnessed failing on runner) → one-shot splice workflow → green full suite. - New audit verbs go into the registry group `ocm` in the same task that first emits them (scanner tripwire enforces this). - Federated identities always rendered with their server; never addable to Spaces. ## Tasks (each: red test → witnessed red → implement → green full suite → commit) 1. **State registries** — `internal/ocm/state.go`: `Server` (allow-list entry: fqdn, addedBy/At, `Policy` contacts|any, relaxedBy/At stamp, cached `Disco`), per-user `Contact` + single-use 7-day `Invite`, `ShareIn`/`ShareOut` ledgers; `Open(4 paths)`, `Allow/Get/List/SetPolicy/Remove` (Remove ends every share both ways and returns them), contacts CRUD + `HasContact(user, addr)`, `MintInvite/TakeInvite` (prune on take), share status transitions + secret/providerId lookups. Persisted via `statestore.Read/Write`, peering idiom. 2. **Keys + discovery** — `EnsureKey` (stable RSA-2048, persisted), `JWKS()`, `PublicPEM()`; `ParseDiscovery` normalising IETF 1.1 / NC 1.0-proposal1 (legacy publicKey captured) / oC10 shareTypes-as-resourceTypes quirk; `OurDiscovery` (1.1) + `OurLegacyDiscovery` (1.0 shape), round-tripped through our own parser in tests. Fixtures verbatim from field research. 3. **HTTP signatures** — `Sign` (RFC 9421 over @method/@target-uri/content-digest/date, rsa-v1_5-sha256, fqdn#key1), `SignLegacy` (draft-cavage: (request-target) content-length date digest), `Verify` auto-detecting with errors naming exactly what failed; `KeyFromJWKS`/`KeyFromPEM`. (Execution finding: sign the request-target, not the absolute URL — the base must survive the client→server hop.) 4. **Inbound endpoints** — `ocm.Service` with injected closures (`Enabled`, `UserExists`, `PeerKey`): both discovery routes (+ oC10 trailing slash), jwks, `POST /ocm/shares` pipeline (allow-list → signature, unsigned tolerated only for keyless peers → per-server policy → known recipient → ledger, refusals naming the reason), `POST /ocm/notifications` (ACCEPTED/DECLINED/UNSHARED). `settings.OCMEnabled`; main wires the four state files into the statestore binds; every route 404s while disabled. 5. **Outbound + grant** — `Client.Discover` (well-known → /ocm-provider → trailing slash), `SendShare` dialect-adaptive (legacy options vs protocol.webdav), `SendNotification`, `FetchRemote` (+ RFC 6749 code-flow client for must-exchange-token), `ListRemote` (namespace-agnostic multistatus, proven against our own grant); `GrantHandler` for `/dav/ocm/<pid>/` — constant-time bearer, subtree jail, GET/HEAD/PROPFIND always, PUT only on write shares, dead when the share ends; mounted over the unscoped driver (audit + #19 decryption apply). 6. **Invites + APIs** — `EncodeInvite`/`DecodeInvite` (canonical base64 token@provider, last-@ split), inviter-side `/ocm/invite-accepted` (allow-list before token burn), `Client.AcceptInvite`; user API (shares list/send/accept/decline/end both directions, browse/fetch proxy, invites, contacts) + admin API (status, servers list/check/allow/policy/remove); ten audit verbs group `ocm`. 7. **Frontend** — Federation rail split (Cairn Peering + Open Cloud Mesh), OCM admin topic (Settings + Servers per mockup), Federated view (Shares = federations as folders → inside view with Shared-with-you/You-shared groups + live remote browse + explicit Save-a-copy; Requests with badge + dot; user-owned Contacts), share-dialog federated mode, boot feature probe, i18n ×4. (Execution finding: a new admin topic needs FOUR touch points — ADM_TOPICS, ADM_TABS, slot HTML, renderAdminTopic branch + save-button list.) 8. **Interop harness** — runner has no container capability → black-box fallback: `ocm-interop.yml` (kept) builds the real binary, seeds state, drives the wire with curl (discovery both dialects, jwks, inbound share, named refusal, grant jail, disabled-is-silent); `ocm-live-nc.yml` (kept, secrets-gated) drives the lab Nextcloud via OCS. 9. **Docs + release** — handbook `federation-ocm.md`, README bullet, ARCHITECTURE entry, CHANGELOG; wiki page with the end-to-end Mermaid flowchart; PR → CI → merge → interop green on main → tag → bump dogfoods → verify → close with decision record. ## Self-review notes - Spec coverage: OCM.md §2 decisions → tasks 1 (policy), 4 (gate, readable refusals), 5 (live view backend, grant), 6 (invites user-owned), 7 (UI incl. federation-as-folder), 8 (harness), 9 (docs/flowchart). §5 encryption needs no extra task: the grant reads through the live driver stack which already carries the #19 decrypt pipeline; marking exclusion holds because the grant mounts below marks. - Type consistency: `Store`, `Discovery`, `ShareIn/Out`, `Policy` used identically across tasks; `Verify` returns `(string, error)` everywhere. - Known risk, stated upfront: task 8 depended on the runner's container capability — probed, fallback executed, never silently skipped. *(The full step-level plan with verbatim test code lives in the session file `2026-09-07-ocm-federation-plan.md`; deltas between plan and execution are recorded in the closing decision-record comment above.)*
Author
Owner

Dogfood finding 4 → fixed in v0.6.142 (PR #453, live on both dogfoods).

Finding: the Servers-tab policy chip toggled on a single click — accident-prone and not identifiable as a setting — and the mockup's Status column was absent (reachability had been deferred to the metrics pass, follow-up 4, without saying so in the UI).

Design (approved mockup ocm-servers-policy-mockup.html, rev 1):

  • Policy is a per-row dropdown; changing it applies nothing. It highlights the row, shows "currently: X", and raises a pane-local Apply/Discard bar that names the consequence in words (relax: "anyone on this server will be able to share with your users…" / tighten: "only invited contacts…"). Multiple changed rows queue into one bar. Note: policy lives in OCM server state, not the settings object, so this bar is pane-local pending-change tracking (one POST per row on Apply), not the settings snapshot bar — behaviour is identical for the admin.
  • Status column now exists: opening the tab probes each allowed server in parallel via POST /api/v1/admin/ocm/servers/check → reachable/unreachable + "checked just now". Retention: nothing is stored and nothing is audited — the result lives only in the response/page memory, so no logs accumulate. The continuous reachability gauge + Prometheus alert remain deferred to the metrics pass (follow-up 4 stands for those). If "unreachable since N days" is ever wanted, that is one fixed-size timestamp per server in the servers doc, not a log.

Cleanup: .ocmchip/.ocmchip.strict and the click-to-toggle handler removed entirely. 12 new i18n keys ×4.

**Dogfood finding 4 → fixed in v0.6.142** (PR #453, live on both dogfoods). Finding: the Servers-tab policy chip toggled on a single click — accident-prone and not identifiable as a setting — and the mockup's Status column was absent (reachability had been deferred to the metrics pass, follow-up 4, without saying so in the UI). Design (approved mockup `ocm-servers-policy-mockup.html`, rev 1): - Policy is a per-row **dropdown**; changing it applies nothing. It highlights the row, shows "currently: X", and raises a pane-local **Apply/Discard bar** that names the consequence in words (relax: "anyone on this server will be able to share with your users…" / tighten: "only invited contacts…"). Multiple changed rows queue into one bar. Note: policy lives in OCM server state, not the settings object, so this bar is pane-local pending-change tracking (one POST per row on Apply), not the settings snapshot bar — behaviour is identical for the admin. - **Status column** now exists: opening the tab probes each allowed server in parallel via `POST /api/v1/admin/ocm/servers/check` → reachable/unreachable + "checked just now". Retention: **nothing is stored and nothing is audited** — the result lives only in the response/page memory, so no logs accumulate. The continuous reachability gauge + Prometheus alert remain deferred to the metrics pass (follow-up 4 stands for those). If "unreachable since N days" is ever wanted, that is one fixed-size timestamp per server in the servers doc, not a log. Cleanup: `.ocmchip`/`.ocmchip.strict` and the click-to-toggle handler removed entirely. 12 new i18n keys ×4.
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#117
No description provided.