@@ -24,26 +24,72 @@ 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
27- ## Candidate seams (in value order, consumers attached)
28-
29- 1 . ** ` api.events.onResourceChange(cb) ` ** — consumer: notifications/.
30- Core has the emitter internally (` src/notifications/events.js ` ); today a
31- plugin must fs.watch a config-supplied path, which drifts and misses
32- non-fs backends. This is also the seam any future "react to pod writes"
33- app (webhooks, indexing, sync) wants — likely the most demanded seam of
34- the next wave of real apps.
35- 2 . ** ` api.serverInfo ` (` { baseUrl, port } ` resolved at listen)** — consumers:
36- notifications/ (pub URLs, origin checks, loopback), any plugin minting
37- absolute URLs. Today the operator repeats the origin in every plugin's
38- config.
39- 3 . ** Internal utility modules plugins re-vendor** — consumers: relay/
40- (` src/nostr/event.js ` NIP-01 verify) and potentially pay/ (` src/mrc20.js ` ).
41- Both are pure, dependency-light crypto. Candidate: export like auth.js
42- (` javascript-solid-server/nostr.js ` ), or bless vendoring as the answer.
43- 4 . ** Response-header injection on core routes** — consumer: notifications/
44- (` Updates-Via ` discovery). Explicitly NOT proposing a default-on hook:
45- a plugin rewriting every response is a bigger grant than route ownership.
46- If it ships, gate it (` capabilities: ['hooks'] ` ).
27+ ## Candidate seams (ranked by how many independent plugins demanded them)
28+
29+ Twelve plugins in, the ranking is now empirical — a seam's rank is how many
30+ ports reached for it without coordinating.
31+
32+ 1 . ** ` api.authorize(request, path, mode) ` ** — "would the host's WAC allow
33+ this?" ** Three independent consumers: notifications/, corsproxy/,
34+ capability/.** The loopback trick (below) covers the case where the
35+ * requester's own* credentials should decide (notifications, webdav,
36+ sparql all use it), but it can't cover authorization the requester
37+ doesn't drive: a proxy governed by a * pod owner's* ` .acl ` (corsproxy
38+ #382 ), or a capability exercising the * issuer's* authority
39+ (capability #506 ). This is the most-requested seam and the one that
40+ moves the most backlog issues from "plugin-approximation" to "faithful".
41+ 2 . ** ` api.events.onResourceChange(cb) ` ** — ** two consumers: notifications/,
42+ sparql/** , and sparql is the * stronger* one: without a write hook a
43+ plugin index returns ** wrong** query results, not merely late
44+ notifications, and ` pluginDir ` caching is uninvalidatable. Core already
45+ has the emitter internally (` src/notifications/events.js ` ). The seam
46+ every "react to pod writes" app (webhooks, indexing, sync, full-text)
47+ will want.
48+ 3 . ** ` api.serverInfo ` (` { baseUrl, port } ` at listen)** — ** consumers:
49+ notifications/, webdav/, sparql/, nip05/** (subdomain per-host
50+ filtering). Every plugin that mints absolute URLs or reaches the host
51+ over loopback repeats the origin in config today; a wrong value fails
52+ quietly (nip05 serves an empty identity map). Cheap to provide.
53+ 4 . ** The unconsumed-body-** stream** primitive (#583 )** — consumer:
54+ gitscratch/ sharpened it. tunnel/ needed the raw * buffer* ; git needs the
55+ raw * stream* piped to a subprocess gzip-and-all. Whatever ` api.mountApp `
56+ / raw-body mode ships must hand back the un-drained stream, not just a
57+ buffered body. (This is exactly what the merged loader's scoped
58+ pass-through parser does — the finding is to keep it that way.)
59+ 5 . ** Reserved-path declaration** — consumers: nip05/ (`/.well-known/
60+ nostr.json` ), gitscratch/ (coexistence with core ` git: true`). A plugin
61+ can register absolute/exact paths outside its prefix, but only WAC-exempt
62+ * by luck* (core blanket-exempts ` /.well-known/* ` ) and with no conflict
63+ detection — a future core route at the same path throws
64+ ` FST_ERR_DUPLICATED_ROUTE ` at boot. Let entries declare reserved paths so
65+ the loader claims them deliberately and reports collisions.
66+ 6 . ** Can't set fastify server options** — consumer: capability/ hit
67+ ` maxParamLength ` (100) silently 404ing long tokens in named params;
68+ workaround is a wildcard route. A plugin has no way to raise per-route
69+ limits. Minor, but sharp when it bites.
70+ 7 . ** Internal utility modules plugins re-vendor** — consumer: relay/
71+ (` src/nostr/event.js ` NIP-01 verify), pay/ (` src/mrc20.js ` ). Pure crypto.
72+ Export like auth.js (` javascript-solid-server/nostr.js ` ) or bless
73+ vendoring.
74+ 8 . ** Response-header injection on core routes** — consumer: notifications/
75+ (` Updates-Via ` discovery). A plugin can't add headers to routes it
76+ doesn't own. NOT a default-on hook (bigger grant than route ownership);
77+ gate behind ` capabilities: ['hooks'] ` if ever.
78+
79+ ## Test-harness footguns (host quirks, not plugin api)
80+
81+ Every multi-boot suite independently rediscovered these; worth a line in
82+ the plugin-author docs.
83+
84+ - ** Module-global ` DATA_ROOT ` ** : JSS keeps the storage root (and IdP key
85+ resolution) in a process-global env var that * every* ` createServer `
86+ repoints — a second boot in one process, ** even a deliberately-failing
87+ one** , poisons the first. Order validation-failure tests * before* the
88+ long-lived boot. (notifications/, webdav/, sparql/ all hit this.)
89+ - ** Ambient ` ~/.gitconfig ` ** : git-shelling plugins inherit the operator's
90+ config — ` init.defaultBranch = gh-pages ` leaked dangling HEADs into
91+ server-created bare repos (empty clones). Spawn git with
92+ ` GIT_CONFIG_NOSYSTEM=1 ` and no ` HOME ` . (gitscratch/.)
4793
4894## The core/plugin line — answering #564 empirically
4995
0 commit comments