Peering: end-user send API and cairnd wiring — closes #103 #127

Merged
Cordy merged 1 commit from feat/peering-send-api into main 2026-08-06 02:14:08 +00:00
Owner

The last piece of #103. Peering is now reachable end to end: an authorized user can send a file to a registered peer and watch it complete.

POST /api/v1/peering/send       start a transfer, returns its id
GET  /api/v1/peering/transfers  the caller's own transfers
GET  /api/v1/peering/peers      peers this caller may send to

Deliberately not admin-gated. Who may send is a per-peering decision (§4.2), not a function of operator status — an admin with no allow-list entry cannot send, and a non-admin with one can.

Two things worth calling out

GET /peering/peers returns a projection, never the stored Peer. That struct carries the peering key, which is a credential. Returning it directly would leak it to every authenticated user. There is a test asserting CAIRN-PEER1 never appears in the response, and that a peer the caller may not use is absent entirely — the dropdown #105 renders should only offer what will actually work.

Legal holds check both accessors. holdStore.Held(p) || holdStore.Affected(p), because your requirement is that a file inside a held folder is also blocked, and the two accessors' exact semantics aren't documented. Over-blocking a send is a recoverable annoyance; under-blocking defeats the hold entirely.

Space write-membership needs no new API

§4.2 requires write membership to send space content. A read-only member belongs to the read-only variant group, never to <space> itself — so plain group membership is the write test. No spaces-manager call, no extra round trip, and it works in every auth mode.

Wiring

Stat/Open go through the caller's scoped, decorated store, so read authorization and decryption happen with their keys — sending is a read like any other. The background job uses context.WithoutCancel, which keeps the user in context after the HTTP request returns.

New config: peering.transfersPath, defaulting to /data/.cairn/peer-transfers.json.

7 API tests, gofmt, vet, full suite and go build ./... all clean. Developed on a branch; main never went red.

Still open on the peering track

#105 (UI — needs your interaction design) and #106 (dogfood — needs the out-of-band key exchange only you can do). Also worth a v0.4.2 tag now that a transfer can complete end to end.

The last piece of #103. Peering is now reachable end to end: an authorized user can send a file to a registered peer and watch it complete. ``` POST /api/v1/peering/send start a transfer, returns its id GET /api/v1/peering/transfers the caller's own transfers GET /api/v1/peering/peers peers this caller may send to ``` Deliberately **not** admin-gated. Who may send is a per-peering decision (§4.2), not a function of operator status — an admin with no allow-list entry cannot send, and a non-admin with one can. ## Two things worth calling out **`GET /peering/peers` returns a projection, never the stored `Peer`.** That struct carries the peering **key**, which is a credential. Returning it directly would leak it to every authenticated user. There is a test asserting `CAIRN-PEER1` never appears in the response, and that a peer the caller may not use is absent entirely — the dropdown #105 renders should only offer what will actually work. **Legal holds check both accessors.** `holdStore.Held(p) || holdStore.Affected(p)`, because your requirement is that a file inside a held *folder* is also blocked, and the two accessors' exact semantics aren't documented. Over-blocking a send is a recoverable annoyance; under-blocking defeats the hold entirely. ## Space write-membership needs no new API §4.2 requires write membership to send space content. A read-only member belongs to the read-only variant group, never to `<space>` itself — so plain group membership *is* the write test. No spaces-manager call, no extra round trip, and it works in every auth mode. ## Wiring `Stat`/`Open` go through the **caller's scoped, decorated store**, so read authorization and decryption happen with their keys — sending is a read like any other. The background job uses `context.WithoutCancel`, which keeps the user in context after the HTTP request returns. New config: `peering.transfersPath`, defaulting to `/data/.cairn/peer-transfers.json`. 7 API tests, `gofmt`, `vet`, full suite and `go build ./...` all clean. Developed on a branch; `main` never went red. ## Still open on the peering track #105 (UI — needs your interaction design) and #106 (dogfood — needs the out-of-band key exchange only you can do). Also worth a `v0.4.2` tag now that a transfer can complete end to end.
feat(peering): end-user send API and cairnd wiring (#103)
All checks were successful
ci / test-and-build (pull_request) Successful in 34s
4febc49175
Cordy merged commit ca595d53f9 into main 2026-08-06 02:14:08 +00:00
Cordy deleted branch feat/peering-send-api 2026-08-06 02:14:28 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
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#127
No description provided.