theseus/docs/DESIGN-one-extension-catalogue.md

88 lines
4.6 KiB
Markdown
Raw Normal View History

# One catalogue, one set of rules
Status: proposed, 2026-09-22. Not implemented.
Theseus currently runs two extension systems that happen to produce the same
kind of thing.
| | Bundled add-ons | Community extensions |
|----------------|-------------------------------------|---------------------------------------|
| How it arrives | compiled into the build, seeded into the profile on first run | installed from the gateway catalogue |
| Who signs | the Silent Mode operator's Ed25519 key, pubkeys compiled into the app | the owner of a BNS name, checked against the chain |
| Update channel | `theseus.x/extensions/<id>/updates.json` | `theseus.x/extensions/community/<id>/updates.json` |
| In Settings | a section headed "Built into Theseus" | a separate section headed "Community" |
| How to publish | `scripts/sign-addon-update.mjs` + `sia-upload` | the publish page, or `PUT /api/ext/…` |
Two catalogues in the UI, two trust rules, two publishing paths — for a
difference that is really only "who wrote it" and "is it there on day one".
## What this should be instead
**One catalogue.** Every extension is listed in the same place, whoever wrote
it. Silent Mode publishes under a Silent Mode name the same way anyone else
publishes under theirs. Being first-party is a fact about the publisher, not
a separate distribution system, and the UI should say so with a line of text
rather than a second list.
**One rule for trust.** An extension is installable because a signature
checks out against the current owner of the publisher's name. That is
already what `verifyPublisherEntry` does; the operator Ed25519 key becomes
either one more accepted signer or nothing at all. `pickChannelEntry`
already accepts both, so this is mostly deletion.
**Defaults are configuration.** Which extensions a fresh Theseus starts with
is a list of ids in the app's config, not a folder that gets copied. Changing
the default set should be editing that list, not moving directories around
and rebuilding.
## The one thing that needs care
If defaults are fetched from the catalogue on first run, a fresh install with
no network — or with the gateway down — has no editor, no wallet and no
screenshot tool. That is a real regression from today, where the build
carries them.
So keep shipping the code, but stop treating it as a different kind of thing:
- The build continues to include the default extensions' folders. They are a
**pre-seeded cache**, not a separate class of add-on.
- On first run they are installed from that cache, then verified and updated
against the catalogue exactly like anything the user installed by hand.
- If a seeded copy fails verification, it is quarantined rather than run —
a cache is only a cache.
That keeps offline first-run working while leaving one set of rules
everywhere else.
## Sketch of the work
- **Config**: `defaultExtensions: [{ id, publisher }]` in the app's settings,
read at first run and on profile migration. Ids the user has removed are
remembered and not re-seeded — uninstalling a default must stick.
- **`main.js`**: seeding installs *through* the normal install path instead
of copying a folder and calling it built-in. One code path, one place where
a signature is checked.
- **`settings.html`**: one list. "Installed by default" becomes a badge on a
row, next to the publisher's name. The separate Community section and its
Install button fold into the single catalogue view.
- **`addon-updater.js`**: drop the distinction between the two channel
shapes; an extension has a publisher and an updates URL.
- **Gateway**: the catalogue gains first-party entries. Nothing changes
structurally — `/api/ext` already stores whatever a name owner signs.
- **Publishing**: `scripts/sign-addon-update.mjs` and the operator key stop
being the first-party route. Silent Mode's own extensions go through
`PUT /api/ext/<our-name>/<id>/<version>` like everyone else's, which means
the release process needs that name's wallet rather than a key file.
## Open questions
- **Which name publishes Silent Mode's extensions?** Whoever goes first owns
each id permanently. Worth deciding once, deliberately.
- **Pinning.** A default extension is installed without anyone choosing it,
so a compromised or coerced catalogue entry reaches every fresh profile.
Pinning the default set to a version and a publisher in the app's config
bounds that.
- **Does the operator key survive at all?** Keeping it as an accepted signer
for the default set is a hedge against the name-owner key being lost;
keeping it forever means the two-rules problem never fully goes away.