@@ -24,25 +24,31 @@ are candidates, each with a consumer in this repo attached.
2424 correct. This removes a whole class of would-be seams (` api.wac.check ` )
2525 from the * necessary* list, leaving them merely * nice* .
2626- ** Conditional writes pass through loopback intact** (remotestorage/,
27- measured): ` If-Match ` /` If-None-Match ` forwarded verbatim are honored by
27+ measured; sparql/ is the second consumer): ` If-Match ` /` If-None-Match `
28+ forwarded verbatim are honored by
2829 the host end-to-end — stale ` If-Match ` PUT/DELETE → 412,
2930 ` If-None-Match: * ` on an existing resource → 412 with the body proven
30- not to land, ` If-None-Match ` GET → 304. Atomic at the host; no
31- plugin-side conditional logic needed.
31+ not to land, ` If-None-Match ` GET → 304. No plugin-side conditional
32+ logic needed for the stale-writer case. ** Caveat measured by sparql/:**
33+ the host's check is check-then-write, not atomic — see the bugs section.
3234
3335## Candidate seams (ranked by how many independent plugins demanded them)
3436
3537Twelve plugins in, the ranking is now empirical — a seam's rank is how many
3638ports reached for it without coordinating.
3739
38401 . ** ` api.authorize(request, path, mode) ` ** — "would the host's WAC allow
39- this?" ** Three independent consumers: notifications/, corsproxy/,
40- capability/.** The loopback trick (below) covers the case where the
41- * requester's own* credentials should decide (notifications, webdav,
41+ this?" ** Four independent consumers: notifications/, corsproxy/,
42+ capability/, caldav/ .** The loopback trick (below) covers the case where
43+ the * requester's own* credentials should decide (notifications, webdav,
4244 sparql all use it), but it can't cover authorization the requester
4345 doesn't drive: a proxy governed by a * pod owner's* ` .acl ` (corsproxy
44- #382 ), or a capability exercising the * issuer's* authority
45- (capability #506 ). This is the most-requested seam and the one that
46+ #382 ), a capability exercising the * issuer's* authority
47+ (capability #506 ), or CalDAV scheduling (RFC 6638), where Outbox→Inbox
48+ delivery is a write into the * recipient's* pod the sender has no WAC
49+ right to make — loopback structurally cannot express it (a
50+ server-mediated deliver-to-inbox primitive is the narrower
51+ alternative). This is the most-requested seam and the one that
4652 moves the most backlog issues from "plugin-approximation" to "faithful".
47532 . ** ` api.events.onResourceChange(cb) ` ** — ** seven consumers now:
4854 notifications/, sparql/, search/, matrix/, backup/, jmap/,
@@ -60,6 +66,13 @@ ports reached for it without coordinating.
6066 (` eventSourceUrl ` ) and delta sync (` */changes ` , ` Email/queryChanges ` )
6167 are omitted/refused in-protocol), and remotestorage (rS's
6268 descendant-version propagation at depth ≥ 2 needs a write-time index).
69+ sparql/'s UPDATE work sharpened the whole entry: ** owning a write
70+ endpoint does not buy the write-time index** — a plugin could index
71+ its own writes synchronously, but plain-LDP writes bypass it, yielding
72+ a silently-wrong index (worse than none); only the host's event stream
73+ sees all writes. caldav/'s free-busy added a wrinkle: a shared
74+ write-time index must answer "indexed under whose authority?" or it
75+ leaks WAC-hidden data.
6376 Core already has the
6477 emitter internally (` src/notifications/events.js ` ); this is the seam
6578 every "react to pod writes" app (webhooks, indexing, sync, search, live
@@ -132,9 +145,10 @@ ports reached for it without coordinating.
132145 endpoints are client-discovered rather than protocol-fixed — the seam
133146 is specifically about protocols that pin absolute paths, not API shims
134147 per se.
135- 6 . ** Can't set fastify server options** — consumer : capability/ hit
148+ 6 . ** Can't set fastify server options** — consumers : capability/ hit
136149 ` maxParamLength ` (100) silently 404ing long tokens in named params;
137- workaround is a wildcard route. A plugin has no way to raise per-route
150+ shortlink/ dodged it pre-emptively the same way. Workaround is a
151+ wildcard route. A plugin has no way to raise per-route
138152 limits. Minor, but sharp when it bites.
1391537 . ** Internal utility modules plugins re-vendor** — consumer: relay/
140154 (` src/nostr/event.js ` NIP-01 verify), pay/ (` src/mrc20.js ` ). Pure crypto.
@@ -233,6 +247,12 @@ this repo is its proof.
233247 candidate: when the basename is generic (` plugin ` , ` index ` ), derive from
234248 the parent directory (` relay/plugin.js ` → ` relay ` ). Small, backward-
235249 compatible, removes the most common footgun. ** Filed-worthy.**
250+ - ** The host's conditional write is check-then-write, not atomic**
251+ (sparql/, measured live): two * concurrent* PUTs carrying the same
252+ currently-valid ` If-Match ` both return 2xx and the loser is silently
253+ overwritten; the same two PUTs run sequentially 412 correctly. Two
254+ simultaneous writers can lose one with both told success — unfixable
255+ plugin-side; the host needs an atomic stat+write. ** Filed-worthy.**
236256- ** The loader's activate-failure wrap drops ` err.code ` **
237257 (remotestorage/): a route collision inside ` activate ` surfaces as
238258 ` plugin <id>: activate() failed: <message> ` with the original
@@ -280,3 +300,10 @@ this repo is its proof.
280300 exported (loopback enforces Control for free) but absolute-URL ACLs
281301 don't restore elsewhere — policy portability is a spec-level gap, not a
282302 plugin-api gap.
303+ - ** pluginDir vs pod resources — no doctrine** (shortlink/): stateful
304+ plugins choose between ` pluginDir ` (fast, private, invisible to WAC,
305+ backup, and portability) and pod resources (WAC-governed, portable,
306+ but a loopback round-trip per operation). The api offers one primitive
307+ and no guidance; a design note in the plugin docs would spare every
308+ author the same deliberation. Eleven plugins now persist in
309+ ` pluginDir ` — the most settled seam in the api.
0 commit comments