Skip to content

Commit a32bec2

Browse files
Integrate wave 7 (micropub, backup): 26 plugins, 264 tests + REPORT.md
compose.test.js and serve.js now boot all 26 from pure config (both new plugins live under their own prefix — no appPaths widening needed). Findings folded into NOTES.md: api.events gains its 5th consumer (backup — incremental/scheduled backup unbuildable), api.serverInfo now ~12, the raw-body-stream seam (#583) gains micropub's media endpoint, response-header injection gains micropub endpoint discovery, and micropub is the reservePath counter-witness (client-discovered endpoints fit the one-prefix model exactly). New: REPORT.md — the maintainer-facing summary. What the api already carries (26 plugins, eleven capability classes, zero core changes), the four seams to add ranked by independent demand (authorize, events, reservePath, serverInfo) each written as a fileable issue with consumers/sketch/cost, two loader bug fixes, the #564 core/plugin line, and a suggested order of work. NEXT.md updated: webhooks/webmention deliberately deferred (outbound fetches to arbitrary URLs need a focused, careful session).
1 parent 87b0153 commit a32bec2

8 files changed

Lines changed: 337 additions & 58 deletions

File tree

AGENT.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Out-of-tree plugins for [JavaScript Solid
1111
Server](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer),
1212
built on its **#206 loader** (`createServer({ plugins })`, JSS ≥ 0.0.215).
1313
It is an *experiment*: prove the plugin api by using it, and treat every
14-
wall you hit as a finding, not a blocker. **20 plugins, ~200 tests today.**
14+
wall you hit as a finding, not a blocker. **26 plugins, 264 tests today.**
1515

1616
### The one rule that makes the experiment valid
1717

@@ -149,6 +149,8 @@ Full ranking in `NOTES.md`. The ones you'll hit:
149149
| an object-storage / S3-style gateway | `s3/` | loopback LDP + hand-rolled XML + SigV4 |
150150
| a DID / identity document | `didweb/` | podsRoot scan + key derivation |
151151
| a dev/tooling subsystem | `gitscratch/` | shell a system binary via CGI |
152+
| a posting protocol (IndieWeb-style, client-discovered endpoint) | `micropub/` | pod bearer as the protocol token + loopback writes |
153+
| a data-export / archive download | `backup/` | loopback container walk streamed into a hand-rolled format |
152154
153155
## Footguns (every multi-boot suite rediscovered these)
154156
@@ -166,7 +168,8 @@ Full ranking in `NOTES.md`. The ones you'll hit:
166168
167169
## Current state
168170
169-
24 plugins (6 ports + 18 features), ~270 tests, all green (`npm test`).
171+
26 plugins (6 ports + 20 features), 264 tests, all green (`npm test`).
170172
`compose.test.js` runs every one on a single server from pure config. Two
171173
core PRs (#590 `api.mountApp`, #591 `/idp/refresh`) sit upstream, unmerged,
172174
for the maintainer's call. Everything else lives here, by design.
175+
[REPORT.md](./REPORT.md) is the maintainer-facing summary of it all.

ISSUES.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ evidence. Legend:
3535
|| `search/` | full-text search over pod resources |
3636
|| `didweb/` | `did:web` DID-document resolver |
3737
|| `s3/` | S3-compatible object-storage gateway |
38+
|| `micropub/` | IndieWeb Micropub endpoint (media upload → 🔩 raw-body stream #583) |
39+
|| `backup/` | pod → `.tar.gz` export (incremental backup → 🔩 `api.events`) |
3840

3941
Plus six **ports of bundled features** proving the migration path for #564 /
4042
#164: `relay/` `webrtc/` `terminal/` `tunnel/` `notifications/`, and `pay/`
@@ -96,13 +98,13 @@ exists.
9698
## Tally
9799

98100
Of ~40 plugin-tagged issues: **13 built as plugins here** (plus `rss/`,
99-
`matrix/`, `search/`, `didweb/`, `s3/` — capability demonstrations with no
100-
single issue), **6 ported**, **5 shipped upstream**, **2 more plugin-able
101-
with no blocker**, **6 clusters blocked on a named seam** (each with a
102-
proof-of-need consumer), the rest core-by-nature or product-scale. **24
103-
plugins total, ~270 tests.** The plugin api reaches most of the backlog
104-
today; ranked by demand, the seams that would unlock the most next are
105-
`api.authorize` (3 blocking consumers), `api.events` (4 consumers — matrix
106-
`/sync` needs live push), `api.reservePath` (API-shims + didweb's
107-
parameterized case), and `api.serverInfo` (broadest: ~10 plugins hand-roll
108-
their own origin).
101+
`matrix/`, `search/`, `didweb/`, `s3/`, `micropub/`, `backup/` — capability
102+
demonstrations with no single issue), **6 ported**, **5 shipped upstream**,
103+
**2 more plugin-able with no blocker**, **6 clusters blocked on a named
104+
seam** (each with a proof-of-need consumer), the rest core-by-nature or
105+
product-scale. **26 plugins total, 264 tests.** The plugin api reaches most
106+
of the backlog today; ranked by demand, the seams that would unlock the
107+
most next are `api.authorize` (3 blocking consumers), `api.events` (5
108+
consumers — matrix `/sync` needs live push, backup can only pull-on-demand),
109+
`api.reservePath` (API-shims + didweb's parameterized case), and
110+
`api.serverInfo` (broadest: ~12 plugins hand-roll their own origin).

NEXT.md

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

99
## Where things stand
1010

11-
- **24 plugins, ~270 tests, all green** (`npm test`), all pushed to
11+
- **26 plugins, 264 tests, all green** (`npm test`), all pushed to
1212
`github.com/JavaScriptSolidServer/plugins` (branch `gh-pages`).
13-
- `compose.test.js` boots all 24 on **one** JSS from pure config; `serve.js`
13+
- `compose.test.js` boots all 26 on **one** JSS from pure config; `serve.js`
1414
is the runnable demo. Both must be updated when you add a plugin.
1515
- Built so far: 6 ports (relay, webrtc, terminal, tunnel, notifications,
16-
pay) + 18 features (nip05, corsproxy, capability, webdav, sparql,
16+
pay) + 20 features (nip05, corsproxy, capability, webdav, sparql,
1717
gitscratch, otp, carddav, mastodon, bluesky, caldav, webfinger,
18-
activitypub, rss, matrix, search, didweb, s3).
18+
activitypub, rss, matrix, search, didweb, s3, micropub, backup).
1919
- Capability classes covered: realtime, WebDAV family, fediverse/social/chat
20-
(5 shims), identity, data/query/search, object storage, proxy, dev, pay.
20+
(5 shims), IndieWeb publishing, identity, data/query/search, object
21+
storage, proxy, dev, pay, data portability.
22+
- **REPORT.md exists** — the maintainer-facing summary (ranked seams, each
23+
fileable nearly verbatim). Keep its consumer counts current as plugins
24+
land.
2125

2226
## The hard rule (don't break it)
2327

@@ -33,17 +37,18 @@ plugin), `NOTES.md` (the findings/seams — the real deliverable), and
3337
pinned to 0.0.215 on `gh-pages`. You may **read** it for reference; keep
3438
it clean.
3539

36-
## Immediate next step: finish Wave 7
40+
## Wave 7 status
3741

38-
Four plugins were *planned but not yet built* — this is where to resume:
42+
`micropub/` and `backup/` are **built and integrated**. Two remain
43+
*deliberately deferred*, not forgotten:
3944

40-
| Plugin | What | Copy from |
45+
| Plugin | What | Why deferred |
4146
|---|---|---|
42-
| `webhooks/` | change-notification webhooks; **must poll** (no `api.events`) → the canonical 5th consumer of that seam, and a real integration primitive | notifications/ (fs.watch), rss/sparql (container walk) |
43-
| `micropub/` | IndieWeb Micropub server — POST creates a post in the pod | mastodon/ (auth + pod write via loopback) |
44-
| `webmention/` | IndieWeb Webmention receiver — store incoming mentions in the pod | activitypub/ inbox, capability/ |
45-
| `backup/` | pod container → `.tar.gz` download (data portability); hand-roll tar headers + `node:zlib` gzip (no deps) | webdav/rss (container walk) |
47+
| `webhooks/` | change-notification webhooks; **must poll** (no `api.events`) → a 6th consumer of that seam | POSTs to arbitrary operator-supplied URLs — build with corsproxy/-grade SSRF gates and a careful review, in a session focused on it |
48+
| `webmention/` | IndieWeb Webmention receiver — store incoming mentions in the pod | spec requires fetching the (arbitrary) source URL to verify the link — same outbound-fetch caution as webhooks |
4649

50+
If you build them, copy notifications/ (fs.watch) + rss/sparql (walk) for
51+
webhooks, activitypub/ inbox + corsproxy/'s SSRF gates for webmention.
4752
Dispatch each as its own worker into its own directory (they don't conflict
4853
— separate subdirs, shared files touched only by you at integration time).
4954
Verify each with `node --test --test-concurrency=1 <name>/test.js`, then:
@@ -91,12 +96,15 @@ each. Current top four (keep this current as you add consumers):
9196

9297
1. **`api.authorize(request, path, mode)`** — 3 consumers; the top
9398
*blocking* seam (authority the requester doesn't drive).
94-
2. **`api.events.onResourceChange`** — 4 consumers; matrix `/sync` needs
99+
2. **`api.events.onResourceChange`** — 5 consumers (backup/ made it 5:
100+
incremental/scheduled backup is unbuildable); matrix `/sync` needs
95101
live push. Every "react to writes" plugin (webhooks, WebSub, indexing)
96-
will want it — webhooks/ (wave 7) makes it 5.
102+
will want it — webhooks/ would make it 6.
97103
3. **`api.reservePath`** — every API-shim owns fixed roots outside its one
98104
prefix and can't self-exempt; didweb needs a *parameterized* form.
99-
4. **`api.serverInfo`** — broadest (~10 plugins hand-roll their origin).
105+
(micropub/ is the counter-witness: client-discovered endpoints need no
106+
reservation — the seam is about protocol-fixed paths.)
107+
4. **`api.serverInfo`** — broadest (~12 plugins hand-roll their origin).
100108

101109
Plus: the unconsumed-body-**stream** primitive (#583), `api.mcp.registerTool`
102110
(blocks the MCP-tool issues #495/#496/#500/#501), can't-set-server-options,
@@ -105,12 +113,12 @@ empirically in NOTES: **route-owning → plugin, pipeline-modifying → core**.
105113

106114
## Meta-work worth doing (beyond more plugins)
107115

108-
- **A summary write-up** the maintainer can act on: "the plugin api reaches
109-
N of ~40 tagged issues; here are the M seams to add next, ranked, each
110-
with consumers." Half of this exists in `ISSUES.md`/`NOTES.md` already.
116+
- **The summary write-up is DONE — `REPORT.md`.** Keep it current (counts,
117+
consumers) whenever a wave lands; it's written so each seam could be
118+
filed nearly verbatim.
111119
- **Filing the seams as upstream issues** — but only if the maintainer asks
112-
(core-repo interaction is currently off; see the hard rule). Draft them
113-
here first if so.
120+
(core-repo interaction is currently off; see the hard rule). REPORT.md
121+
is the draft.
114122

115123
## Footguns (every multi-boot test suite rediscovered these)
116124

NOTES.md

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,38 @@ ports reached for it without coordinating.
3838
#382), or a capability exercising the *issuer's* authority
3939
(capability #506). This is the most-requested seam and the one that
4040
moves the most backlog issues from "plugin-approximation" to "faithful".
41-
2. **`api.events.onResourceChange(cb)`****four consumers now:
42-
notifications/, sparql/, search/, matrix/**, in rising sharpness:
43-
notifications (a miss is a *late* notification), sparql (a miss is a
44-
*wrong* query result), search (a miss is *stale results*, the property
45-
users most expect to be fresh — the most user-visible instance), and
46-
matrix (its `/sync?since=` long-poll needs server-side sync cursors +
47-
**live push on writes** — an event hook feeding `api.ws.route` — so a
48-
stateless bridge can only do full-state `/sync` at all). Core already has
49-
the emitter internally (`src/notifications/events.js`); this is the seam
41+
2. **`api.events.onResourceChange(cb)`****five consumers now:
42+
notifications/, sparql/, search/, matrix/, backup/**, in rising
43+
sharpness: notifications (a miss is a *late* notification), sparql (a
44+
miss is a *wrong* query result), search (a miss is *stale results*, the
45+
property users most expect to be fresh — the most user-visible
46+
instance), matrix (its `/sync?since=` long-poll needs server-side sync
47+
cursors + **live push on writes** — an event hook feeding `api.ws.route`
48+
— so a stateless bridge can only do full-state `/sync` at all), and
49+
backup (with no change hook *and* no plugin-owned read authority,
50+
incremental and scheduled/server-initiated backup are both unbuildable —
51+
every backup is a caller-driven full crawl). Core already has the
52+
emitter internally (`src/notifications/events.js`); this is the seam
5053
every "react to pod writes" app (webhooks, indexing, sync, search, live
5154
chat) will want, and it's now clearly the #2 most-demanded after
5255
`api.authorize`.
53-
3. **`api.serverInfo` (`{ baseUrl, port }` at listen)****eight+
56+
3. **`api.serverInfo` (`{ baseUrl, port }` at listen)****a dozen+
5457
consumers: notifications/, webdav/, carddav/, caldav/, sparql/, rss/,
55-
nip05/, webfinger/, mastodon/, bluesky/, activitypub/** — essentially
58+
nip05/, webfinger/, mastodon/, bluesky/, activitypub/, micropub/,
59+
backup/** — essentially
5660
every plugin that mints absolute URLs or reaches the host over loopback.
5761
All repeat the origin in config today; a wrong value fails quietly (nip05
5862
serves an empty identity map). The single most *broadly* needed seam
5963
(vs. api.authorize being the most *blocking*); trivially cheap to provide.
60-
4. **The unconsumed-body-**stream** primitive (#583)** — consumer:
61-
gitscratch/ sharpened it. tunnel/ needed the raw *buffer*; git needs the
62-
raw *stream* piped to a subprocess gzip-and-all. Whatever `api.mountApp`
63-
/ raw-body mode ships must hand back the un-drained stream, not just a
64-
buffered body. (This is exactly what the merged loader's scoped
65-
pass-through parser does — the finding is to keep it that way.)
64+
4. **The unconsumed-body-**stream** primitive (#583)** — consumers:
65+
gitscratch/ sharpened it; micropub/ adds a blocked one. tunnel/ needed
66+
the raw *buffer*; git needs the raw *stream* piped to a subprocess
67+
gzip-and-all; micropub's **media endpoint** (multipart file upload) is
68+
simply not implemented until a plugin can pipe an un-drained body.
69+
Whatever `api.mountApp` / raw-body mode ships must hand back the
70+
un-drained stream, not just a buffered body. (This is exactly what the
71+
merged loader's scoped pass-through parser does — the finding is to
72+
keep it that way.)
6673
5. **Routes/WAC-exemption outside the single prefix****the most-hit
6774
finding: seven+ consumers.** A plugin can *register* absolute/exact
6875
routes outside its prefix (the loader doesn't confine `api.fastify`), but
@@ -98,7 +105,11 @@ ports reached for it without coordinating.
98105
(or `paths: [...]` in the entry) — the loader exempts *and* claims each
99106
deliberately and reports collisions. The seam **every API-shim plugin**
100107
structurally requires; third most-demanded after `api.authorize` and
101-
`api.events`.
108+
`api.events`. **Counter-witness that sharpens it:** micropub/ is a
109+
protocol shim that needed *no* reservation at all, because Micropub
110+
endpoints are client-discovered rather than protocol-fixed — the seam
111+
is specifically about protocols that pin absolute paths, not API shims
112+
per se.
102113
6. **Can't set fastify server options** — consumer: capability/ hit
103114
`maxParamLength` (100) silently 404ing long tokens in named params;
104115
workaround is a wildcard route. A plugin has no way to raise per-route
@@ -107,10 +118,13 @@ ports reached for it without coordinating.
107118
(`src/nostr/event.js` NIP-01 verify), pay/ (`src/mrc20.js`). Pure crypto.
108119
Export like auth.js (`javascript-solid-server/nostr.js`) or bless
109120
vendoring.
110-
8. **Response-header injection on core routes** — consumer: notifications/
111-
(`Updates-Via` discovery). A plugin can't add headers to routes it
112-
doesn't own. NOT a default-on hook (bigger grant than route ownership);
113-
gate behind `capabilities: ['hooks']` if ever.
121+
8. **Response-header injection on core routes****two consumers:
122+
notifications/** (`Updates-Via` discovery) and **micropub/** (clients
123+
find the endpoint via `<link rel="micropub">` on the user's homepage —
124+
a core-owned resource the plugin can't decorate; the operator must
125+
advertise it by hand). A plugin can't add headers to routes it doesn't
126+
own. NOT a default-on hook (bigger grant than route ownership); gate
127+
behind `capabilities: ['hooks']` if ever.
114128

115129
## Test-harness footguns (host quirks, not plugin api)
116130

@@ -192,3 +206,14 @@ this repo is its proof.
192206
- Test harness dance: a plugin whose config references the server's own
193207
origin forces the port-probe-then-boot pattern (helpers.js `port` option)
194208
— same finding as api.serverInfo, visible in test setup.
209+
- **No snapshot semantics** (backup/): a multi-resource read is never
210+
atomic — the api offers no lock, snapshot, or conditional multi-read, so
211+
an archive of a busy pod is an honest but mixed-state export. Related:
212+
a streaming response can't report post-walk results (skip counts) in
213+
headers — they're committed before the walk starts and there are no
214+
usable trailers — so backup/ writes an in-band `MANIFEST.json` as the
215+
last archive entry.
216+
- **Permissions don't round-trip** (backup/): `.acl`/`.meta` could be
217+
exported (loopback enforces Control for free) but absolute-URL ACLs
218+
don't restore elsewhere — policy portability is a spec-level gap, not a
219+
plugin-api gap.

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,15 @@ README and NOTES.md, ship the closest honest approximation.
8282
| `search/` || full-text search over pod resources (TF-IDF) |
8383
| `didweb/` || `did:web` DID-document resolver for pods |
8484
| `s3/` || S3-compatible object-storage gateway (SigV4) |
85+
| `micropub/` || IndieWeb Micropub endpoint — posts land in the author's pod |
86+
| `backup/` || pod container → `.tar.gz` download (data portability) |
8587

86-
**~270 tests, all green** (`npm test`), including `compose.test.js` — all
87-
twenty-four plugins on one server from pure config, pods + WAC intact
88+
**264 tests, all green** (`npm test`), including `compose.test.js` — all
89+
twenty-six plugins on one server from pure config, pods + WAC intact
8890
beside them. Findings consolidated in [NOTES.md](./NOTES.md); the full
8991
plugin-tagged backlog triaged in [ISSUES.md](./ISSUES.md); the build guide
90-
is [AGENT.md](./AGENT.md).
92+
is [AGENT.md](./AGENT.md); the maintainer-facing summary — what to add
93+
to the api next, ranked, with evidence — is [REPORT.md](./REPORT.md).
9194

9295
```
9396
<name>/plugin.js the port <name>/test.js real-JSS tests <name>/README.md findings

0 commit comments

Comments
 (0)