docs(34): capture phase context

This commit is contained in:
Jakub Zych
2026-10-29 12:16:38 +01:00
parent 4449066fa6
commit c48301ef7e
2 changed files with 205 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
# Phase 34: Plugin marketplace - Context
**Gathered:** 2026-10-29
**Status:** Ready for planning
<domain>
## Phase Boundary
People browse the public Golem15 plugins at summercms.io/marketplace and add one to an application with `summer plugin:install <id>`; the same command installs private plugins from a git URL. Each public `sm-*-plugin` repo carries a generated `plugin.yaml`; summercms.io ingests manifests, READMEs and tags into a registry with a public read-only API; the admin SPA gains a read-only Plugins screen (Installed + Marketplace tabs). v1 has no paid plugins, no developer registration, no themes and no installs from the admin panel (all settled in `.planning/notes/2026-10-29-plugin-marketplace.md`).
Discussion added one capability to the roadmap text: `summer plugin:update`, so the admin's "update available" badge has a matching command (D-10).
</domain>
<decisions>
## Implementation Decisions
### Manifest: plugin.yaml is generated, never hand-edited
- **D-01:** `plugin.yaml` is generated by a new command `summer plugin:manifest` run in a plugin repo. `plugin.go` stays as it is (no change to `party.Plugin`, no breaking change to core plugins).
- **D-02:** Every field is derived from the repo: `id` and `requires` from `plugin.go` (AST: the literals returned by `ID()` / `Requires()`), module path from `go.mod`, minimum framework version and minimum required-plugin versions from `go.mod`'s `require` lines, `name` and `description` from the README's H1 and its one-sentence summary (the standard module README structure). A plugin whose `ID()`/`Requires()` are not literal returns is an error that names the function.
- **D-03:** Optional `--name="…"` / `--description="…"` override the README-derived text. Overrides persist in `plugin.yaml` and survive regeneration until changed or cleared (exact storage is planner's choice, e.g. an `overrides:` block).
- **D-04:** `summer plugin:manifest --check` fails when the committed `plugin.yaml` is stale. It runs in each plugin repo's tests (a pact/test helper or `go test`-runnable check) and in the tag/changelog step (`scripts/changelog-post.py publish … --repo`). `plugin.yaml` is committed. — **Reversibility:** costly — the format is read by the registry ingester, `plugin:install` for git URLs, and every public plugin repo; changing it means regenerating all repos and migrating the ingester.
- **D-05:** Version constraints are Go-style minimums: `framework: v0.4.0`, `requires: {golem15.translate: v0.2.0}`. No semver ranges (Go's minimal version selection; a major bump changes the module path).
### Runtime metadata for the Installed tab
- **D-06:** `summer build` reads each plugin's `plugin.yaml` and bakes name and description into `plugins.gen.go`. Plugins without a `plugin.yaml` show only their ID.
- **D-07:** Version: module installs use the version from `go.mod`/build info; submodule or local-`replace` plugins get `git describe --tags` run in the plugin directory at build time (e.g. `v0.3.1-4-gabc123`), also baked into `plugins.gen.go`.
### Install resolution
- **D-08:** `summer plugin:install golem15.journal` picks the newest tag whose minimum framework version the app's `go.mod` already satisfies; `golem15.journal@v0.3.0` pins. If only tags needing a newer framework exist, it fails and names the framework version to move to (it does not bump the framework silently).
- **D-09:** Missing required plugins are resolved transitively, the full list of additions is printed, and the user confirms; `--yes` skips the prompt and non-interactive runs require `--yes`. Dependencies install in the same mode as the requested plugin (module, or submodule under `--source`).
- **D-10:** `summer plugin:update [id]` is in scope: module installs move to the newest compatible tag (`go get`), submodule installs are checked out at the newest compatible tag. Without an ID it updates all plugins it can resolve.
- **D-11:** Git-URL installs are submodule-only in v1; no `--module`. Close the open question in `.planning/research/questions.md` as "not in v1". — **Reversibility:** reversible — adding `--module` later is additive.
- **D-12:** A git-URL plugin's own repo is never sent to summercms.io, but its required plugin IDs (e.g. `golem15.translate`) are resolved through the registry like any ID install. `--offline` disables registry calls and then requires the dependencies to be installed already.
- Both modes finish with the same registration `plugin:add` does (`summer.yaml`, `go.mod`, `go.work` where applicable), and module-installed plugins must work with the build steps that currently inspect plugin directories (`internal/build/leaf.go`, `internal/build/registry.go`) — resolve their directory from the module cache.
### Registry ingestion and API (summercms.io, `golem15.summercms` site plugin)
- **D-13:** Pull ingester: given a repo URL it clones anonymously, reads tags, each tag's `plugin.yaml`, and the README. It runs as a River periodic job (around hourly) and on an authenticated "ingest now" endpoint that `changelog-post.py publish --repo` calls. No forge webhooks.
- **D-14:** The listing is admin-curated: a summercms.io admin adds a repo URL; the ingester fills the facts. A plugin is published only once it has a tag with a valid `plugin.yaml`; tags without one are skipped.
- **D-15:** Every `vX.Y.Z` tag is stored as a version row with that tag's framework and plugin minimums (needed by D-08). README is stored for the latest tag only.
- **D-16:** Public read-only JSON API, no auth, open CORS, cache headers: `GET /api/registry/v1/plugins?q=&category=` (list/search), `GET /api/registry/v1/plugins/{id}` (detail incl. README as markdown and rendered HTML), `GET /api/registry/v1/plugins/{id}/versions`. Consumed by the Nuxt marketplace pages, the CLI and cabana. — **Reversibility:** one-way — once released `summer` binaries call `/api/registry/v1`, its shape cannot change without a v2 path.
- Presentation fields (category, featured, screenshots, icon, sort order) are edited in the summercms.io admin (from the decision note).
### Admin Plugins screen
- **D-17:** The SPA talks only to its own backend: cabana gets swag-annotated admin endpoints that call the registry with a timeout and short cache, and compute "update available" in Go against installed versions. No CSP/CORS change in the admin.
- **D-18:** Config key `plugins.registry_url` (default `https://summercms.io`); empty disables registry calls, which hides the Marketplace tab and update badges while Installed still works. The CLI reads the same setting (allows a staging or self-hosted registry). Exact key name is planner's choice if it clashes with existing config.
- **D-19:** New permission (a `system.plugins.view`-style key in cabana's permission set), granted to superusers by default.
### Claude's Discretion
- Marketplace page layout and visuals on `vue-summercmsio-app` (list, search, category, detail); `/msd-ui-phase 34` can produce a UI contract if wanted.
- Storage of D-03 overrides inside `plugin.yaml`.
- Registry DB schema, ingester error handling, cache durations, periodic interval.
- Whether older tags (before `plugin.yaml` existed) get backfilled; default per D-14 is to skip them.
- Exact CLI output/prompt wording and the semantics of `plugin:update` with no ID when some plugins can't be resolved.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Marketplace decisions
- `.planning/notes/2026-10-29-plugin-marketplace.md` — v1 scope, listing data split, install-mode table, read-only admin screen, out-of-scope list
- `.planning/research/questions.md` — "Is `--module` for git-URL installs worth supporting?" (answered by D-11: not in v1)
- `.planning/ROADMAP.md` § Phase 34 — goal, repos, success criteria
### Plugins and repos
- `.planning/notes/core-plugins-own-repos.md` — `sm-*-plugin` repos, module path = repo path, submodules at `plugins/<vendor>/<name>`, commit plugin first then pointer bump
- `.planning/notes/why-go-not-scala.md` and `.planning/research/FEATURES.md` (anti-features) — no runtime plugin loading
- `.planning/research/STACK.md` — decided dependency set (YAML via `goccy/go-yaml`; any new dependency, e.g. a markdown renderer or git library, needs a note)
### Release flow
- `../sm-summercmsio-app/DEPLOY.md` § "Posting to the changelog" and `../sm-summercmsio-app/scripts/changelog-post.py` — plugin tag step that D-04 and D-13 hook into
- `.planning/notes/2026-10-07-embedded-admin-release-gate.md` — rebuild and commit `modules/boardwalk/dist` when `admin/` changes
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `cmd/summer/main.go` `addPluginCommand()` and `internal/build.AddPlugin` — the registration step both install modes end with.
- `internal/build/manifest.go` — `Manifest`, `LoadManifest`/`SaveManifest`, `ValidatePluginID`, `validateModulePath`: reuse for `summer.yaml` edits and ID validation in `plugin.yaml`.
- `internal/build/build.go` `generatePluginsGen` — where D-06/D-07 metadata gets baked in.
- `modules/party` `Activated` — the installed list cabana's Installed tab reads.
- `conga` (River periodic jobs) — the ingester schedule on summercms.io.
- `scripts/check-admin-openapi.sh` — swag → `admin/openapi/admin.json` → `admin/src/api/schema.d.ts` for the new cabana endpoints.
### Established Patterns
- `party.Plugin` is only `ID()`, `Requires()`, `Register`, `Boot`; plugin metadata does not exist at runtime today, hence D-06.
- `plugin:add` assumes a local directory + `replace`; module installs (no `replace`) are new and must satisfy `leaf.go`/`registry.go` directory inspection.
- Commands are `bonfire.Command` with Long text, Examples and Args/Flags; docs checker (`summer docs:build --check`) verifies command names in docs.
- New admin screens follow the cabana admin API + boardwalk SPA split; the dist is committed.
### Integration Points
- `../sm-summercmsio-app/plugins/golem15/summercms` — currently routes/static only (docs, `/new`, `/update`, site tree); gains models, migrations, admin (presentation fields, repo list), registry API routes and the ingester job.
- `../sm-summercmsio-app/vue-summercmsio-app` — marketplace pages consuming D-16.
- Every public `../sm-*-plugin` repo — commit generated `plugin.yaml` plus the `--check` test (plugin commits first, app pointer bumps separately).
</code_context>
<specifics>
## Specific Ideas
- The user wanted hand-editable text despite generation, hence `--name` / `--description` overrides on `plugin:manifest`.
- Modelled on octobercms.com/marketplace for browsing; install is CLI only.
</specifics>
<deferred>
## Deferred Ideas
- `--module` for git-URL (private) installs. Revisit if submodule-only proves limiting.
- Already out of scope per the decision note: paid plugins, licensing, developer accounts, third-party submissions, themes, installing from the admin panel.
### Reviewed Todos (not folded)
- 17 todos matched on keywords only (e.g. gate scripts naming apps, OIDC parity, MFA findings, menu plugin port). None concern the marketplace; none folded.
</deferred>
---
*Phase: 34-plugin-marketplace*

View File

@@ -0,0 +1,81 @@
# Phase 34: Plugin marketplace - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-10-29
**Phase:** 34-plugin-marketplace
**Areas discussed:** plugin.yaml vs plugin.go, Install resolution, Registry ingestion & API, Admin ↔ registry link
---
## plugin.yaml vs plugin.go
| Option | Description | Selected |
|--------|-------------|----------|
| Both stay, build checks | plugin.go unchanged; build/validator fail on mismatch | |
| plugin.go reads plugin.yaml | embed and derive ID()/Requires(); edits every plugin.go | |
| Generate plugin.yaml | command writes it from plugin.go plus extra fields | ✓ |
**Field sources:** derive everything from the repo (plugin.go AST, go.mod, README H1 + summary). **Notes:** user asked for optional `--name` / `--description` overrides for hand-edited text.
**When generated:** `summer plugin:manifest` + `--check` before tagging (selected) vs generated only at ingestion.
| Runtime meta | Selected |
|---|---|
| Baked into plugins.gen.go by summer build | ✓ |
| Optional pact Details() capability | |
| Local version | Selected |
|---|---|
| git describe at build | ✓ |
| Show "local" | |
| version field in plugin.yaml | |
| Constraint format | Selected |
|---|---|
| Go-style minimum | ✓ |
| Semver ranges | |
---
## Install resolution
| Question | Options | Selected |
|---|---|---|
| Version pick | Newest compatible + @ pins / Always latest tag | Newest compatible + @ pins |
| Missing deps | Install transitively after listing / Fail with command | Install transitively after listing |
| Update command | plugin:update in scope / Defer | plugin:update in scope |
| `--module` for git URLs | Let research decide / No, submodule only / Yes with diagnostics | No, submodule only |
| Git-URL plugin's public deps | Registry for IDs only / Never contact, fail / From go.mod alone | Registry for IDs only |
---
## Registry ingestion & API
| Question | Options | Selected |
|---|---|---|
| Trigger | Pull job + changelog step / Forge webhook / changelog-post.py push only | Pull job + changelog step |
| Listing | Admin-curated repo list / Auto-discover sm-*-plugin | Admin-curated repo list |
| Versions | Every tag + its plugin.yaml / Latest only | Every tag + its plugin.yaml |
| API shape | Small read-only JSON / Static index.json | Small read-only JSON |
---
## Admin ↔ registry link
| Question | Options | Selected |
|---|---|---|
| Path | Server-side via cabana / Browser calls summercms.io | Server-side via cabana |
| Opt-out | Config key, on by default / Always on | Config key, on by default |
| Permission | New permission, superuser default / Any admin | New permission, superuser default |
---
## Claude's Discretion
- Marketplace page layout; override storage in plugin.yaml; registry schema, cache durations, ingest interval; backfilling older tags; CLI wording.
## Deferred Ideas
- `--module` for git-URL installs.