Skip to content

Commit dfea5c9

Browse files
docs: true-up the seam consumer counts and the stale test tally
- 374 -> 386 tests everywhere the old number survived (ISSUES, NEXT, STRATEGY; README/AGENT/SECURITY already said 386). - api.serverInfo: the honest count is 23 consumers (grep-verified config.baseUrl/loopbackUrl readers), not ~16/~10 — the list gains matrix, metrics, s3, search, shortlink, didweb, admin and drops nip05, whose own README finding says NIP-05 docs carry no absolute self-URLs (its repetition is the podsRoot/data-root class, which serverInfo does not cover). Stage-3 first-retrofit picks are now webfinger + didweb, the two whose wrong-origin failure is silent. - api.authorize: one set of four consumers everywhere (corsproxy, capability, pay, caldav) — NOTES had counted notifications, which its own next sentence says the loopback trick covers. - api.reservePath: four protocol-shim confirmations, not three — matrix's /_matrix root is witnessed in matrix/test.js and was already counted in REPORT. - Footguns: fs.watch needs a free inotify instance; at the max_user_instances cap the three notifications tests time out (EMFILE) — an environment failure, not a regression.
1 parent db2e22e commit dfea5c9

6 files changed

Lines changed: 51 additions & 30 deletions

File tree

AGENT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,21 @@ Full ranking in `NOTES.md`. The ones you'll hit:
7878
allow this?" for authority the *requester* doesn't drive (a proxy governed
7979
by a pod owner's `.acl`, a capability exercising the *issuer's* right).
8080
Workaround: loopback with the requester's own creds covers the common
81-
case; the issuer-authority case has no workaround — document it. (3
81+
case; the issuer-authority case has no workaround — document it. (4
8282
consumers; the top blocking seam.)
8383
- **No `api.reservePath()`** — you can register routes outside your one
8484
`prefix`, but only `prefix` is WAC-exempt. `/.well-known/*` works by luck
8585
(core blanket-exempts it). Fixed roots like `/api`, `/xrpc`, `/ap` do
8686
**not** work until the operator passes `appPaths: ['/api',...]` to
8787
`createServer`. **If you build an API shim, your `test.js` must pass those
88-
`appPaths`, and your README must say the operator does too.** (3 shim
88+
`appPaths`, and your README must say the operator does too.** (4 shim
8989
consumers.)
9090
- **No `api.events.onResourceChange`** — you can't react to pod writes, so a
9191
write-time index / cached feed is impossible; do read-time work (walk the
9292
container per request) and note the O(N) cost. See `sparql/`, `rss/`.
9393
- **No `api.serverInfo`** — a plugin can't learn its own origin at
9494
`activate` time. Take `config.baseUrl` (and `loopbackUrl`), and `throw` if
95-
missing. ~10 plugins do this.
95+
missing. ~23 plugins do this.
9696
- **No `api.mcp.registerTool`** — MCP tools can't be added by a plugin
9797
(that's why #495/#496/#500/#501 aren't here).
9898
- **Can't set Fastify server options** — e.g. `maxParamLength` (100) 404s

ISSUES.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ Of ~40 plugin-tagged issues: **14 built as plugins here** (plus `rss/`,
111111
demonstrations with no single issue), **7 ported**, **5 shipped upstream**,
112112
**2 more plugin-able with no blocker**, **6 clusters blocked on a named
113113
seam** (each with a proof-of-need consumer), the rest core-by-nature or
114-
product-scale. **33 plugins total, 374 tests.** The plugin api reaches most
114+
product-scale. **33 plugins total, 386 tests.** The plugin api reaches most
115115
of the backlog today; ranked by demand, the seams that would unlock the
116-
most next are `api.authorize` (3 blocking consumers), `api.events` (7
116+
most next are `api.authorize` (4 consumers, 3 hard-blocked), `api.events` (7
117117
consumers — matrix `/sync` needs live push, jmap can't do push or delta
118118
sync, backup can only pull-on-demand),
119119
`api.reservePath` (API-shims + didweb's parameterized case + the
120120
**witnessed** webfinger collision), and
121-
`api.serverInfo` (broadest: ~16 plugins hand-roll their own origin).
121+
`api.serverInfo` (broadest: ~23 plugins hand-roll their own origin).

NEXT.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ plugin), `NOTES.md` (the findings/seams — the real deliverable), and
88

99
## Where things stand
1010

11-
- **33 plugins, 374 tests, all green** (`npm test`), all pushed to
11+
- **33 plugins, 386 tests, all green** (`npm test`; the three
12+
`notifications/` fs.watch tests need a free inotify instance — see
13+
Footguns), all pushed to
1214
`github.com/JavaScriptSolidServer/plugins` (branch `gh-pages`).
1315
- `compose.test.js` boots all 33 on **one** JSS from pure config; `serve.js`
1416
is the runnable demo — its front door is now `admin/` (`/admin/`), the
@@ -93,9 +95,14 @@ rather than deleting it everywhere.
9395

9496
### Recommended first move
9597

96-
Don't retrofit all ~16 at once. Do **`nip05/` + `webfinger/`** first — the
97-
two where a wrong origin fails *silently* (nip05 serves an empty identity
98-
map), so the before/after story is strongest. One plugins-repo PR against
98+
Don't retrofit all ~23 at once. Do **`webfinger/` + `didweb/`** first — the
99+
two where a wrong origin fails *silently* (webfinger mints WebIDs/JRD links
100+
on the wrong origin; didweb serves a `did.json` whose `id` doesn't match
101+
the URL it's fetched from), so the before/after story is strongest. (An
102+
earlier draft named nip05/ here — wrongly: its own README finding says
103+
NIP-05 documents contain no absolute self-URLs, so it needs no `baseUrl`;
104+
its silent-empty-map failure is the *data-root* repetition class,
105+
`config.podsRoot`, which `serverInfo` does not cover.) One plugins-repo PR against
99106
a locally-linked core, proving the loop closes; once core publishes, flip
100107
the pin and merge. The rest follow the same shape. Then the next seam:
101108
`reservePath` (#602) → `events` (#603) → `authorize` (#604).
@@ -174,8 +181,8 @@ it sharpens the case.
174181
`NOTES.md` ranks candidate seams by how many independent plugins demanded
175182
each. Current top four (keep this current as you add consumers):
176183

177-
1. **`api.authorize(request, path, mode)`** — 4 consumers (caldav
178-
scheduling joined); the top
184+
1. **`api.authorize(request, path, mode)`** — 4 consumers (corsproxy,
185+
capability, pay, caldav — caldav scheduling joined); the top
179186
*blocking* seam (authority the requester doesn't drive).
180187
2. **`api.events.onResourceChange`** — 7 consumers (backup, jmap,
181188
remotestorage joined); matrix `/sync` needs
@@ -186,7 +193,7 @@ each. Current top four (keep this current as you add consumers):
186193
prefix and can't self-exempt; didweb needs a *parameterized* form.
187194
(micropub/ is the counter-witness: client-discovered endpoints need no
188195
reservation — the seam is about protocol-fixed paths.)
189-
4. **`api.serverInfo`** — broadest (~16 plugins hand-roll their origin).
196+
4. **`api.serverInfo`** — broadest (~23 plugins hand-roll their origin).
190197
**FILED #601, MERGED as core #605** — see the Stage-3 plan above for
191198
consuming it. reservePath #602, events #603, authorize #604 still open.
192199

@@ -216,6 +223,11 @@ empirically in NOTES: **route-owning → plugin, pipeline-modifying → core**.
216223
- Dotted prefixes (`/.foo`) fail the WS upgrade — use plain prefixes for
217224
anything with a socket. (`/.well-known/*` HTTP works, by core's blanket
218225
exemption — but that's the reserved-path finding, not a guarantee.)
226+
- `fs.watch` needs a free inotify instance: when the machine is at
227+
`fs.inotify.max_user_instances`, watch creation fails (EMFILE) and the
228+
three `notifications/` fan-out tests time out. An environment failure,
229+
not a regression — check `sysctl fs.inotify.max_user_instances` before
230+
debugging the plugin.
219231

220232
## Issue tracker (new — 2026-07)
221233

@@ -237,6 +249,6 @@ narrative, issues are the actionable subset.
237249
#596#600, four seams as core **#601 (serverInfo), #602 (reservePath),
238250
#603 (events), #604 (authorize)**. Next upstream step (Stage 3, needs
239251
the core-freeze lifted): ship `serverInfo` (#601) end-to-end as the
240-
reference PR — cheapest seam, ~16 consumers — then retrofit the plugins
252+
reference PR — cheapest seam, ~23 consumers — then retrofit the plugins
241253
that hand-roll their origin. Secondary asks (api.plugins, api.isOperator)
242254
not filed yet; raise when a design discussion on the four opens.

NOTES.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,14 @@ Twelve plugins in, the ranking is now empirical — a seam's rank is how many
3838
ports reached for it without coordinating.
3939

4040
1. **`api.authorize(request, path, mode)`** — "would the host's WAC allow
41-
this?" **Four independent consumers: notifications/, corsproxy/,
42-
capability/, caldav/.** The loopback trick (below) covers the case where
41+
this?" **Four independent consumers: corsproxy/, capability/, pay/,
42+
caldav/.** The loopback trick (below) covers the case where
4343
the *requester's own* credentials should decide (notifications, webdav,
4444
sparql all use it), but it can't cover authorization the requester
4545
doesn't drive: a proxy governed by a *pod owner's* `.acl` (corsproxy
4646
#382), a capability exercising the *issuer's* authority
47-
(capability #506), or CalDAV scheduling (RFC 6638), where Outbox→Inbox
47+
(capability #506), pay/'s 402 gate (which wants to *compose with* WAC
48+
rather than replace it), or CalDAV scheduling (RFC 6638), where Outbox→Inbox
4849
delivery is a write into the *recipient's* pod the sender has no WAC
4950
right to make — loopback structurally cannot express it (a
5051
server-mediated deliver-to-inbox primitive is the narrower
@@ -78,13 +79,18 @@ ports reached for it without coordinating.
7879
every "react to pod writes" app (webhooks, indexing, sync, search, live
7980
chat) will want, and it's now clearly the #2 most-demanded after
8081
`api.authorize`.
81-
3. **`api.serverInfo` (`{ baseUrl, port }` at listen)****a dozen+
82+
3. **`api.serverInfo` (`{ baseUrl, port }` at listen)****23
8283
consumers: notifications/, webdav/, carddav/, caldav/, sparql/, rss/,
83-
nip05/, webfinger/, mastodon/, bluesky/, activitypub/, micropub/,
84-
backup/, dashboard/, oembed/, jmap/, remotestorage/** — essentially
84+
webfinger/, mastodon/, bluesky/, activitypub/, matrix/, micropub/,
85+
backup/, metrics/, dashboard/, oembed/, jmap/, remotestorage/, s3/,
86+
search/, shortlink/, didweb/, admin/** — essentially
8587
every plugin that mints absolute URLs or reaches the host over loopback.
86-
All repeat the origin in config today; a wrong value fails quietly (nip05
87-
serves an empty identity map). The single most *broadly* needed seam
88+
All repeat the origin in config today; a wrong value fails quietly
89+
(webfinger mints WebIDs on the wrong origin; didweb serves a `did.json`
90+
whose `id` doesn't match its URL). (nip05/ was once listed here —
91+
wrongly: its README finding is that NIP-05 documents carry no absolute
92+
self-URLs; its config repetition is the *data-root* class, `podsRoot`,
93+
which serverInfo doesn't cover.) The single most *broadly* needed seam
8894
(vs. api.authorize being the most *blocking*); trivially cheap to provide.
8995
4. **The unconsumed-body-**stream** primitive (#583)** — consumers:
9096
gitscratch/ sharpened it; micropub/ adds a blocked one. tunnel/ needed
@@ -109,9 +115,10 @@ ports reached for it without coordinating.
109115
most-wanted `.well-known` docs a deployment serves**, both riding the
110116
same undocumented luck.
111117
- **fixed roots core does NOT exempt** — mastodon/ (`/api`,`/oauth`),
112-
bluesky/ (`/xrpc`), activitypub/ (`/ap`). Here the plugin **cannot
118+
bluesky/ (`/xrpc`), activitypub/ (`/ap`), matrix/ (`/_matrix`). Here
119+
the plugin **cannot
113120
serve its own surface**: every call 401s at the WAC hook until the
114-
operator hand-passes `appPaths`. **Three independent protocol-shim
121+
operator hand-passes `appPaths`. **Four independent protocol-shim
115122
confirmations**, each built separately. bluesky sharpened it (one root,
116123
still unreachable → the ask is "declare owned paths," not "more
117124
prefixes"); activitypub sharpened it further — its natural layout wants

REPORT.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,13 @@ exists, moving it from operator config to plugin declaration.
176176

177177
### 4. `api.serverInfo` — the broadest, and the cheapest (#601)
178178

179-
**~16 consumers** — every plugin that mints absolute URLs or loopbacks
180-
(the DAV family, the shims, rss, sparql, nip05, webfinger, notifications,
181-
micropub, backup, metrics, dashboard, oembed, jmap, remotestorage…)
179+
**23 consumers** — every plugin that mints absolute URLs or loopbacks
180+
(the DAV family, the four shims, rss, sparql, webfinger, notifications,
181+
micropub, backup, metrics, dashboard, oembed, jmap, remotestorage, s3,
182+
search, shortlink, didweb, admin)
182183
repeats `baseUrl`/`loopbackUrl` in config today. A wrong value fails
183-
*quietly* (nip05 serves an empty map). Test suites all need a
184+
*quietly* (webfinger mints WebIDs on the wrong origin; didweb serves a
185+
`did.json` whose `id` doesn't match its URL). Test suites all need a
184186
probe-port-then-boot dance for the same reason.
185187

186188
**Sketch:** `{ baseUrl, port }` available by `activate`-time or via an
@@ -335,7 +337,7 @@ hooks capability.
335337

336338
If effort is scarce, this order maximizes unblocked value per unit cost:
337339

338-
1. `api.serverInfo` (trivial; tidies ~16 plugins' config and every test
340+
1. `api.serverInfo` (trivial; tidies ~23 plugins' config and every test
339341
harness),
340342
2. `api.reservePath` (small-medium; makes four existing shims
341343
self-contained and didweb *possible* — pair it with a webfinger link

STRATEGY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Companion to [REPORT.md](./REPORT.md) (what to build into the api next).
44
This file answers the other question: **where does a JSS + plugins stack
55
sit in the ecosystem, what's missing, and what's the realistic play?**
6-
Written 2026-07, at 33 plugins / 374 tests.
6+
Written 2026-07, at 33 plugins / 386 tests.
77

88
## The one-line position
99

0 commit comments

Comments
 (0)