domma-cms 1.6.60 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4344 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# Marketplace Step 2 - Consolidation, Extraction and Withdrawal
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Put every plugin in one repo, extract `invoice` and `surveys` from the free tier without
|
|
6
|
+
disturbing a live site, stop the scaffolder seeding paid plugins, withdraw `seo-pro`, and get the
|
|
7
|
+
five unversioned site plugins into git.
|
|
8
|
+
|
|
9
|
+
**Architecture:** A new private monorepo `dcms-marketplace` becomes the canonical home for every
|
|
10
|
+
marketplace plugin, free and paid. `domma-cms/plugins/` keeps a vendored copy of the free tier only
|
|
11
|
+
(so `npm install domma-cms` stays self-contained), synced from the monorepo. The manager's
|
|
12
|
+
`paid-plugins/` becomes a checkout rather than a source. Every manifest gains `tier`, and the
|
|
13
|
+
scaffolder's hand-written `SKIP_PLUGINS` set is replaced by a read of that field.
|
|
14
|
+
|
|
15
|
+
**Spec:** `docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md` (sequencing step 2)
|
|
16
|
+
|
|
17
|
+
**Tech Stack:** Fastify 5 (ESM), Domma SPA admin, `node:test` runner, JSON file storage, git.
|
|
18
|
+
|
|
19
|
+
**Critical project rules** (from `CLAUDE.md` + memory):
|
|
20
|
+
|
|
21
|
+
- Server on port **4096** is **always running** - never start or stop it. Test via `curl`.
|
|
22
|
+
- **Never stop a fleet site by hand.** Use `processManager` through the Site Manager API or
|
|
23
|
+
`entitlements.rescindPlugin()`. A site stopped outside `processManager` after a pm2 manager restart
|
|
24
|
+
becomes an orphan and does not respawn.
|
|
25
|
+
- Fleet sites run `NODE_ENV=production`, so cached HTML ignores hand-edited data until the site
|
|
26
|
+
restarts. Plugin file changes need a restart too.
|
|
27
|
+
- `domma-cms` is worked on concurrently by another agent. **Commit early, add named paths only,
|
|
28
|
+
never stash.**
|
|
29
|
+
- `npm test` is serial on purpose (`--test-concurrency=1`). Restore any config file a test pollutes.
|
|
30
|
+
- Do **not** publish to npm as part of this plan. Roll locally: build to `_publish/` and use
|
|
31
|
+
`SOURCE=` that directory.
|
|
32
|
+
|
|
33
|
+
**Ordering constraints that must not be reordered:**
|
|
34
|
+
|
|
35
|
+
1. **Grandfather before extract.** Licences for `invoice` and `surveys` are issued *before* either is
|
|
36
|
+
removed from `domma-cms/plugins/`, so no site ever holds a paid plugin with no licence record.
|
|
37
|
+
2. **`proBundle` before revoke.** `seo-pro` leaves the Pro bundle *before* its licences are revoked,
|
|
38
|
+
or the next Pro enable re-issues one.
|
|
39
|
+
3. **Verify provenance before importing.** Several plugins exist in three places and only one copy is
|
|
40
|
+
the source; importing a minified derivative is silent and permanent.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Phase 0 - Provenance verification (no writes)
|
|
45
|
+
|
|
46
|
+
The monorepo must be seeded from source, never from a build artifact. Two derivations are already
|
|
47
|
+
known: the manager's copies of plugins that also live in `domma-cms` are built (their
|
|
48
|
+
`admin/css/index.css` is a single line against 87 to 487 lines in `domma-cms`), and every site copy
|
|
49
|
+
has been through `pluginDistributor.minifyAdminCode()` at `activate()`.
|
|
50
|
+
|
|
51
|
+
- [x] For each slug, record the canonical copy in a provenance table committed to the monorepo as
|
|
52
|
+
`PROVENANCE.md`. **Done 2026-09-22** - findings below; the file is drafted and lands with Phase 1:
|
|
53
|
+
- **Free set + `invoice` + `surveys`** → `domma-cms/plugins/<slug>/` (unminified, carries CLAUDE.md)
|
|
54
|
+
- **Paid set** (`analytics-pro`, `data-transfer`, `mongodb-adapter`, `job-board`, `theme-roller`) →
|
|
55
|
+
`domma-cms-manager/plugins/site-manager/paid-plugins/<slug>/`
|
|
56
|
+
- **Manager-only** (`calendar`, `docs`, `messages`, `garage`) → `domma-cms-manager/plugins/<slug>/`
|
|
57
|
+
- [x] **`email-pro` resolved.** Its two copies (`domma-cms-manager/plugins/email-pro` and
|
|
58
|
+
`paid-plugins/email-pro`) are byte-identical, so there is nothing to choose. Take `paid-plugins/`
|
|
59
|
+
as canonical, since that is the distribution source. Paid, sold under licence.
|
|
60
|
+
- [x] Confirm no site copy is newer than its claimed source. **Done** - every file in every
|
|
61
|
+
candidate copy hashed, excluding `data/`, `backups/` and `node_modules/`.
|
|
62
|
+
|
|
63
|
+
**Exit: met.** `PROVENANCE.md` names one source per slug and every difference is accounted for.
|
|
64
|
+
|
|
65
|
+
### Phase 0 findings
|
|
66
|
+
|
|
67
|
+
**1. `paid-plugins/surveys` is a stale fork and must not be imported.** This is what the phase was
|
|
68
|
+
for. Three copies existed and the one that looked authoritative was three months behind:
|
|
69
|
+
|
|
70
|
+
| Copy | `plugin.js` | mtime |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `domma-cms/plugins/surveys` | 9,368 bytes | 2026-08-21 |
|
|
73
|
+
| `domma-cms-manager/plugins/surveys` | 9,368 bytes | 2026-09-22 (build copy of the above) |
|
|
74
|
+
| `paid-plugins/surveys` | **8,985 bytes** | **2026-06-29** |
|
|
75
|
+
|
|
76
|
+
`domma-cms` against `paid-plugins`: **16 files differ**, including `plugin.js`, `lib/ledger.js`,
|
|
77
|
+
`lib/sending.js`, `lib/submit.js` and two admin templates. Importing the wrong one would have rolled
|
|
78
|
+
`surveys` back silently, with no error anywhere. Canonical source is **`domma-cms/plugins/surveys`**.
|
|
79
|
+
No site runs the stale copy: every site receives `surveys` from the scaffolder, which copies the
|
|
80
|
+
manager's build of the `domma-cms` source, and it is enabled on zero sites.
|
|
81
|
+
|
|
82
|
+
**2. Nothing has ever been hand-edited on a live site.** All 18 scaffolder-distributed plugins are
|
|
83
|
+
byte-identical across all 8 sites and match their manager source exactly. `email-pro` is the only
|
|
84
|
+
exception and only in 8 minified `admin/**/*.js` files, which is `minifyAdminCode()` behaving as
|
|
85
|
+
designed. **There are no live fixes to rescue**, which removes the risk that made Phase 5's deletions
|
|
86
|
+
dangerous.
|
|
87
|
+
|
|
88
|
+
**3. `minifyAdminCode()` has only ever run for `email-pro`.** `analytics-pro`, `data-transfer`,
|
|
89
|
+
`mongodb-adapter` and `seo-pro` are all `closedSource: true`, yet their `admin/**/*.js` is byte
|
|
90
|
+
identical to source on every site they are installed on - so their admin code ships readable. Either
|
|
91
|
+
they were granted before that step existed, or esbuild was unavailable at grant time. Not a security
|
|
92
|
+
matter, since it never was one, but re-granting them would apply it. Worth deciding whether to
|
|
93
|
+
re-grant during Phase 5, when those sites are being touched anyway.
|
|
94
|
+
|
|
95
|
+
**4. `email-pro`'s two copies are byte-identical**, so there was nothing to choose. `paid-plugins/` is
|
|
96
|
+
canonical as the distribution source.
|
|
97
|
+
|
|
98
|
+
**5. The monorepo needs `.gitignore` entries for `data/` and `backups/`.** `data-transfer` writes
|
|
99
|
+
timestamped backup trees into `backups/` at runtime; `oneill-leisure` holds 28 such files.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Phase 1 - Create the monorepo
|
|
104
|
+
|
|
105
|
+
**Done 2026-09-22** at `/home/dommajs/domains/dcms-marketplace`, 22 commits, no remote yet.
|
|
106
|
+
|
|
107
|
+
- [x] Repo created with the spec's layout, plus `README.md` and `scripts/verify.js`.
|
|
108
|
+
- [x] All **20** plugins imported from their Phase 0 sources, one verbatim commit each naming the
|
|
109
|
+
absolute source path. Re-verified afterwards: every one is byte-identical to its source, manifest
|
|
110
|
+
excepted.
|
|
111
|
+
- [x] `_lib` and `_template` not imported - they stay in `domma-cms/plugins/` so the builder works
|
|
112
|
+
from `npm install` alone.
|
|
113
|
+
- [x] `site-manager`, `customer-registration`, `site-tweaks` and the four site-local plugins not
|
|
114
|
+
imported.
|
|
115
|
+
- [x] `tier` on every manifest: **8 free, 12 paid**.
|
|
116
|
+
- [x] `closedSource: true` added - **to six plugins, not two**. See the deviation below.
|
|
117
|
+
|
|
118
|
+
**Exit: met.** `make verify` passes: 20 plugins, 8 free, 12 paid, 4 reserved slugs, required manifest
|
|
119
|
+
fields present, no committed runtime state.
|
|
120
|
+
|
|
121
|
+
### Deviation: closedSource on six plugins, not two
|
|
122
|
+
|
|
123
|
+
This phase called for `closedSource: true` on `invoice` and `surveys` only. It was also applied to
|
|
124
|
+
`calendar`, `docs`, `messages` and `garage`, which are open in their manager source but are becoming
|
|
125
|
+
paid. `closedSource` is what refuses the source editor (`pluginFiles.isClosedSource`) and what makes
|
|
126
|
+
path C refuse to export a plugin, so a paid plugin without it is readable and re-exportable by the
|
|
127
|
+
customer who bought it - which would contradict the design's own export refusal. Deployed copies are
|
|
128
|
+
unaffected until they are re-distributed.
|
|
129
|
+
|
|
130
|
+
`scripts/verify.js` now enforces this as an invariant: a `tier: "paid"` plugin without
|
|
131
|
+
`closedSource: true` fails the check.
|
|
132
|
+
|
|
133
|
+
### What the import commits are for
|
|
134
|
+
|
|
135
|
+
Each import is a pristine snapshot; `tier` and `closedSource` were applied afterwards in one separate
|
|
136
|
+
commit. So `git show <import-commit>` remains exactly what the source looked like on 2026-09-22,
|
|
137
|
+
which is what makes the Phase 0 provenance claims checkable later rather than merely asserted.
|
|
138
|
+
|
|
139
|
+
### Remote
|
|
140
|
+
|
|
141
|
+
Not configured - the user is setting it up. After `git remote add origin …`, push `main`. Nothing in
|
|
142
|
+
the repo assumes a remote.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Phase 2 - Grandfather the live installs
|
|
147
|
+
|
|
148
|
+
**This phase runs before Phase 3. It issues licences for plugins that are about to become paid, while
|
|
149
|
+
the files are already in place, so nothing changes on any site.**
|
|
150
|
+
|
|
151
|
+
**Done 2026-09-22.** Exactly one file changed across the whole estate: the licence ledger.
|
|
152
|
+
|
|
153
|
+
- [x] `invoice` licensed on `atlas-learning`, `oneill-leisure`, `jamie-alcock` and `pinpointzero` -
|
|
154
|
+
the four sites where it is **enabled**. 365-day term, 7-day grace, matching the existing
|
|
155
|
+
`proLicenceDays` / `gracePeriodDays` defaults. **Not** `grantPlugin()` - see the trap below.
|
|
156
|
+
- [x] `surveys`' two existing licences (`pinpointzero`, `domma-cms-site`) both point at sites that
|
|
157
|
+
still exist. Left active.
|
|
158
|
+
- [x] Verified: `invoice` still `enabled: true` on all four; no site file written (the only site
|
|
159
|
+
writes in the window were three `analytics-pro/data/hits.jsonl`, which is the visitor tracker
|
|
160
|
+
logging live traffic).
|
|
161
|
+
|
|
162
|
+
**Exit: met.** Ledger now holds 24 records, 4 of them `invoice`, all active.
|
|
163
|
+
|
|
164
|
+
**Rollback:** revoke the four licence ids, or restore
|
|
165
|
+
`data/licences.json.bak-pre-invoice-grandfather-20260922T094837`. Nothing else was touched.
|
|
166
|
+
|
|
167
|
+
### Trap: `grantPlugin()` would have disabled invoice on four live sites
|
|
168
|
+
|
|
169
|
+
The plan said to use `entitlements.grantPlugin()`. **Do not**, for a plugin that is not resident in
|
|
170
|
+
`paid-plugins/`. `grantPlugin()` issues the licence and then calls
|
|
171
|
+
`pluginDistributor.activate()`, which throws `Plugin "invoice" not found in paid-plugins directory`
|
|
172
|
+
- and its error path calls `pluginDistributor.deactivate()`, writing `enabled: false` into the
|
|
173
|
+
child's `config/plugins.json` before rethrowing. The site keeps serving until its next restart and
|
|
174
|
+
then comes back **without invoicing**, with the cause recorded nowhere near the symptom.
|
|
175
|
+
|
|
176
|
+
Grandfathering was done with `licenceManager.activate()` instead: it writes the ledger record and
|
|
177
|
+
nothing else. No files, no config, no restart. That is the correct tool whenever the files are
|
|
178
|
+
already in place and only the licence is missing.
|
|
179
|
+
|
|
180
|
+
`grantPlugin()` becomes the right call for `invoice` once `paid-plugins/` is a checkout of the
|
|
181
|
+
monorepo - but note that at that point `activate()` will also overwrite each site's copy and minify
|
|
182
|
+
its admin JS, now that `invoice` is `closedSource`. That is a deliberate re-distribution, not part of
|
|
183
|
+
grandfathering.
|
|
184
|
+
|
|
185
|
+
### Correction to this phase's original exit test
|
|
186
|
+
|
|
187
|
+
It read "every site holding `invoice` **files** also holds an active licence". That is too strict:
|
|
188
|
+
`invoice` files sit on all eight sites because the scaffolder put them there, but only four have it
|
|
189
|
+
enabled. The other four are strays and **Phase 5 deletes them**; licensing a site that does not use
|
|
190
|
+
the plugin would have been wrong. The test is: every site with `invoice` *enabled* holds an active
|
|
191
|
+
licence.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Phase 3 - Extract from the free tier
|
|
196
|
+
|
|
197
|
+
- [x] **`invoice` removed** from `domma-cms/plugins/` (35 files, named-path `git rm`).
|
|
198
|
+
- [x] **`surveys` removed**, with its 11 test files, once option 1 was chosen for the test question.
|
|
199
|
+
- [x] `package.json` had no `files` entry for either. A fresh `npm run build` confirms
|
|
200
|
+
`_publish/plugins/` no longer contains `invoice`.
|
|
201
|
+
- [x] `tier: "paid"` + `closedSource: true` on **both** manager copies. Committed for `invoice`;
|
|
202
|
+
applied on disk for `surveys` but **not committed, because `plugins/surveys/` is gitignored in the
|
|
203
|
+
manager** - see the finding below. The tag still takes effect, since `siteScaffolder` walks the
|
|
204
|
+
filesystem rather than git.
|
|
205
|
+
- [x] History not rewritten; the reasoning is in the commit message.
|
|
206
|
+
- [x] `CHANGELOG.md` entry added under Unreleased.
|
|
207
|
+
|
|
208
|
+
**Exit: met.** `_publish/plugins/` contains neither plugin. The four invoice sites are unaffected and
|
|
209
|
+
still licensed, `surveys` was enabled on no site and its two licences are untouched, and the suite
|
|
210
|
+
passes: **1,584 before, 1,550 after, with the missing 34 now passing in the marketplace repo.**
|
|
211
|
+
|
|
212
|
+
### Resolved: the tests moved with the plugin (option 1)
|
|
213
|
+
|
|
214
|
+
`tests/surveys/` is **11 files, 527 lines**, and they import from three places at once:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
../../plugins/surveys/lib/{ledger,tokens,campaigns,submit}.js the plugin
|
|
218
|
+
../../server/services/{collections,forms}.js the CMS core
|
|
219
|
+
fastify, @fastify/jwt domma-cms node_modules
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Delete the plugin and `npm test` breaks. Move the tests to the monorepo and they need a CMS checkout
|
|
223
|
+
*and* its `node_modules` to run at all. This is not a `surveys` problem - it is the general question
|
|
224
|
+
of **how a paid plugin's tests run once the plugin no longer lives inside the CMS**, and it will
|
|
225
|
+
recur for every paid plugin that has tests.
|
|
226
|
+
|
|
227
|
+
Three options, none of them free:
|
|
228
|
+
|
|
229
|
+
1. **Tests move with the plugin, monorepo gains a host-CMS dependency.** Honest about where the code
|
|
230
|
+
lives; costs a `package.json`, an `npm install`, and a documented path to a sibling `domma-cms`
|
|
231
|
+
checkout. Test imports of `../../server/services/…` become the awkward part.
|
|
232
|
+
2. **Tests stay in `domma-cms`, importing the plugin from the sibling monorepo checkout.** Cheapest
|
|
233
|
+
to do, but `domma-cms`'s suite then fails for anyone without the private repo - including CI.
|
|
234
|
+
3. **Ship `surveys` to the monorepo without its tests.** Fastest, and the worst: 527 lines of
|
|
235
|
+
coverage for a plugin about to be sold, discarded to avoid a packaging decision.
|
|
236
|
+
|
|
237
|
+
**Chosen: (1).** Tests live at `plugins/<slug>/tests/` in the monorepo and run with `make test`.
|
|
238
|
+
|
|
239
|
+
Implementing it turned up something the options above had not anticipated: **the plugin's own source
|
|
240
|
+
addresses the CMS by relative path too**, not just its tests. `plugins/surveys/plugin.js` imports
|
|
241
|
+
`../../server/services/collections.js`, and `lib/submit.js` requires
|
|
242
|
+
`../../../public/js/form-logic-engine.js` - correct in production, where the plugin is installed at
|
|
243
|
+
`plugins/<slug>/` inside a CMS tree, and broken in a repo whose root has no `server/`.
|
|
244
|
+
|
|
245
|
+
Rewriting those imports would have been the obvious move and the wrong one: it would break every
|
|
246
|
+
plugin where it actually runs. Instead `make link-cms` gives the repo just enough of the CMS's shape,
|
|
247
|
+
as local symlinks into a sibling checkout, none committed:
|
|
248
|
+
|
|
249
|
+
| Link | Points at | Why |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `cms` | `../domma-cms` | the host checkout |
|
|
252
|
+
| `node_modules` | `cms/node_modules` | the CMS's own fastify, not a separately pinned one |
|
|
253
|
+
| `server` | `cms/server` | plugin source imports `../../server/services/…` |
|
|
254
|
+
| `public` | `cms/public` | shared browser code, e.g. `public/js/form-logic-engine.js` |
|
|
255
|
+
| `plugins/_lib` | `cms/plugins/_lib` | the shared helper library, imported as `../_lib/…` |
|
|
256
|
+
|
|
257
|
+
Tests also run with cwd set to the CMS root, because the core reads `config/server.json` relative to
|
|
258
|
+
cwd when imported. This harness is generic - it is what every paid plugin's tests will use.
|
|
259
|
+
|
|
260
|
+
The manager's untracked `plugins/surveys/` was deleted at the same time, per the finding below. The
|
|
261
|
+
manager had it disabled, so nothing there changed.
|
|
262
|
+
|
|
263
|
+
### Finding: `plugins/surveys/` is gitignored in the manager, deliberately
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
# ── Pro plugins are distributed via paid-plugins/, not tracked in the engine tree ──
|
|
267
|
+
# The 0.32.x domma-cms engine bundles plugins/surveys/; keep it Pro-isolated.
|
|
268
|
+
plugins/surveys/
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A past decision had already treated `surveys` as Pro and kept the engine's bundled copy out of the
|
|
272
|
+
manager repo. That is why `paid-plugins/surveys` exists as a separate fork, and why it went stale:
|
|
273
|
+
the engine copy kept being updated in `domma-cms` while the Pro copy sat untouched from 2026-06-29.
|
|
274
|
+
Every other plugin in `domma-cms-manager/plugins/` is tracked normally.
|
|
275
|
+
|
|
276
|
+
Consequence for later phases: once `surveys` does leave `domma-cms`, the manager's untracked
|
|
277
|
+
`plugins/surveys/` should be **deleted**, not kept - it is the engine's bundled copy of a plugin the
|
|
278
|
+
engine no longer ships. `surveys` then lives in `paid-plugins/` only, refreshed from the monorepo.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Finding for step 5: the manager runs paid plugins itself - RESOLVED
|
|
283
|
+
|
|
284
|
+
`domma-cms-manager` is a CMS, and its own `config/plugins.json` has `docs`, `email-pro`, `garage` and
|
|
285
|
+
`invoice` **enabled** - four `closedSource` plugins, with no licence records, because it is ours.
|
|
286
|
+
`cms.dommajs.org` is in the same position with `docs`, `invoice` and `messages`.
|
|
287
|
+
|
|
288
|
+
Once entitlement verification lands (spec step 5), both would refuse to load them at boot - and on the
|
|
289
|
+
manager that means the fleet's management plane losing half its admin, including the screens used to
|
|
290
|
+
issue the licences that would fix it.
|
|
291
|
+
|
|
292
|
+
**Resolved 2026-09-22: a wildcard entitlement.** The manager is not merely "ours", it is the testbed
|
|
293
|
+
where plugins are written, so per-plugin self-issued licences would mean licensing a plugin before
|
|
294
|
+
you can run your own new code. Full reasoning in the spec's
|
|
295
|
+
[Owner instances](../2026-09-22-dcms-marketplace-design.md#owner-instances).
|
|
296
|
+
|
|
297
|
+
## Phase 4 - Stop seeding paid plugins
|
|
298
|
+
|
|
299
|
+
**Done 2026-09-22.**
|
|
300
|
+
|
|
301
|
+
- [x] `SKIP_PLUGINS` replaced with `if (manifest.tier === 'paid') continue;`, keeping the
|
|
302
|
+
manager-only exclusions and the `_`-prefix skip.
|
|
303
|
+
- [x] Manifest now read from the **source** before the copy, so a paid plugin is never written to the
|
|
304
|
+
site at all.
|
|
305
|
+
- [x] **No readable manifest now means skip** - an addition, not in the original task.
|
|
306
|
+
`discoverPlugins` would not load such a directory either, and this is what keeps `form-builder`
|
|
307
|
+
residue off new sites.
|
|
308
|
+
- [x] `tier: "paid"` added to the manager's `calendar`, `docs`, `email-pro`, `garage`, `messages`.
|
|
309
|
+
- [x] Throwaway site scaffolded: **9 free plugins seeded, 0 paid**, and no `form-builder`.
|
|
310
|
+
- [x] `data-transfer` granted to it and appeared correctly, enabled in its `config/plugins.json`.
|
|
311
|
+
- [x] Throwaway deleted. `scaffoldSite()` writes only into `sites/<slug>` - it never touches
|
|
312
|
+
`sites.json`, `ports.json`, Apache or `processManager` - so nothing was registered or started and
|
|
313
|
+
deleting the directory orphaned nothing. Ledger still 24 records, sites still 9.
|
|
314
|
+
- [x] 67 site-manager service tests pass.
|
|
315
|
+
|
|
316
|
+
**Exit: met.**
|
|
317
|
+
|
|
318
|
+
### Why only the paid manifests were tagged
|
|
319
|
+
|
|
320
|
+
Absent `tier` means free, matching the CMS loader, so only paid plugins strictly need tagging. Tagging
|
|
321
|
+
the free ones in the manager would also be pointless: the engine updater refreshes those copies from
|
|
322
|
+
`domma-cms`, whose manifests carry no `tier` until `sync-free-tier` lands in step 3, so the tag would
|
|
323
|
+
be overwritten. The paid ones are manager-owned and the updater leaves them alone.
|
|
324
|
+
|
|
325
|
+
### Rule established: closedSource lives on distribution copies, not local installs
|
|
326
|
+
|
|
327
|
+
`closedSource` was **removed** from the manager's `invoice` copy, reversing part of Phase 3.
|
|
328
|
+
`domma-cms-manager/plugins/` holds the manager's *own installed* plugins; `paid-plugins/` is the
|
|
329
|
+
distribution source. On a local install `closedSource` achieves nothing except stopping us editing
|
|
330
|
+
our own code in the admin editor. The copies that reach customers - monorepo and `paid-plugins/` -
|
|
331
|
+
carry it, which is where it does its job.
|
|
332
|
+
|
|
333
|
+
### Aside: the manager's `npm test` runs nothing
|
|
334
|
+
|
|
335
|
+
`package.json` globs `tests/**/*.test.js`, but the manager's tests live at
|
|
336
|
+
`plugins/site-manager/services/*.test.js`. `npm test` reports "tests 0" and exits 0, which is worse
|
|
337
|
+
than failing. Run directly, all 67 pass. Not fixed here - it is unrelated to this plan - but it means
|
|
338
|
+
any verification step that says "run the manager's tests" has been passing vacuously.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Phase 5 - Remove the strays
|
|
343
|
+
|
|
344
|
+
Six paid slugs currently sit unlicensed on existing sites, seeded by the drifted skip-list. Left
|
|
345
|
+
alone they will report `"unlicensed"` in the customer's admin once entitlement verification lands.
|
|
346
|
+
|
|
347
|
+
**Done 2026-09-22.** Note it is **nine** sites, not eight: `domma-cms-site` lives under the manager
|
|
348
|
+
rather than in `dcms/`, and earlier audits in this plan had missed it.
|
|
349
|
+
|
|
350
|
+
- [x] Audited all nine sites against the licence ledger.
|
|
351
|
+
- [x] **43 directories removed**, every one disabled and unlicensed, with their `config/plugins.json`
|
|
352
|
+
entries dropped. A last-second guard in the removal script re-checked `enabled` per directory and
|
|
353
|
+
would have refused any that were.
|
|
354
|
+
- [x] `form-builder` gone from all nine; `plugins/site-search/` gone from `domma-cms` (it was
|
|
355
|
+
untracked - git does not track empty directories - so there is nothing to commit for it).
|
|
356
|
+
- [x] `reserved.json` already carried `form-builder` and `site-search` from Phase 1.
|
|
357
|
+
- [x] **No restarts needed.** Everything removed was disabled, so no running process had it loaded.
|
|
358
|
+
All nine domains verified returning 200 afterwards.
|
|
359
|
+
|
|
360
|
+
**Exit: met, with five exceptions held back deliberately** - see below.
|
|
361
|
+
|
|
362
|
+
### Everything was archived first
|
|
363
|
+
|
|
364
|
+
`domma-cms-manager/var/phase5-removed-plugins-20260922T103005.tar.gz` - 43 directories, 1,280 files,
|
|
365
|
+
947K, symlinks dereferenced so the real content is inside. Removal is fully reversible from it.
|
|
366
|
+
|
|
367
|
+
This was not in the original task and should have been: `docs` and `form-builder` carry `data/` on
|
|
368
|
+
every site. Most of it turned out to be seeded defaults - the same document UUIDs and identical file
|
|
369
|
+
sizes repeated across sites - but `oneill-leisure/form-builder` held five form definitions that exist
|
|
370
|
+
nowhere else, neither in `content/forms/` nor anywhere code can reach, since the plugin has no code
|
|
371
|
+
left. Dead data, but a customer's dead data, and not something to delete on a judgement call.
|
|
372
|
+
|
|
373
|
+
### STOP: five plugins are enabled and unlicensed
|
|
374
|
+
|
|
375
|
+
Not removed, per this phase's own rule. These are plugins someone is using:
|
|
376
|
+
|
|
377
|
+
| Site | Plugin | Status |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| `ginga-ninja` | `messages` | **Licensed 2026-09-22** - was a customer running a paid plugin nobody had billed for |
|
|
380
|
+
| `oneill-leisure` | `messages` | **Licensed 2026-09-22** - as above |
|
|
381
|
+
| `domma-cms-site` | `docs`, `invoice`, `messages` | **Still open** - our own site, the self-licensing question |
|
|
382
|
+
|
|
383
|
+
The two customer cases got the `invoice` treatment: `licenceManager.activate()`, 365-day term, 7-day
|
|
384
|
+
grace, files untouched and no restart, so nothing changed for either customer except that what they
|
|
385
|
+
are using is now on the books. `messages` is not in `paid-plugins/` either, so `grantPlugin()` would
|
|
386
|
+
have hit the same trap it would have hit for `invoice`.
|
|
387
|
+
|
|
388
|
+
Ledger is now **26 records**. The only unlicensed paid plugins left anywhere are the three on
|
|
389
|
+
`domma-cms-site`, which is the same question as the manager running its own paid plugins (see the
|
|
390
|
+
step 5 finding above) - exempt, or self-issue. It is ours, so nothing is at risk while it waits.
|
|
391
|
+
|
|
392
|
+
### Left uncommitted deliberately
|
|
393
|
+
|
|
394
|
+
The removals show as `D` deletions in each site's own git repo, and for `domma-cms-site` inside the
|
|
395
|
+
manager repo. They are **not committed**: those working trees already carry 70 to 220 files of
|
|
396
|
+
unrelated drift that is not mine to commit, and `config/plugins.json` in particular may hold someone
|
|
397
|
+
else's pending change. Worth committing per site, but as a deliberate act rather than swept up here.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Phase 6 - Dispose of the site-local plugins
|
|
402
|
+
|
|
403
|
+
Four of the five are already tracked in their own sites' git repos, so this phase is disposition
|
|
404
|
+
rather than rescue. None go in the monorepo.
|
|
405
|
+
|
|
406
|
+
**Done 2026-09-22.** A third correction to the version-control claim first: **`shopping-cart` is
|
|
407
|
+
tracked too.** `domma-cms-site` has no `.git` of its own, but the manager repo tracks all 619 of its
|
|
408
|
+
files, those 5 included. **Every plugin on the estate is versioned somewhere.** Each time this was
|
|
409
|
+
got wrong the mistake was the same - looking for a `.git` or a specific home, and not checking
|
|
410
|
+
whether an enclosing repo tracked it.
|
|
411
|
+
|
|
412
|
+
- [x] `reactive-showcase` - **no action required.** 21/21 files tracked in the `reactive` repo. Log a
|
|
413
|
+
separate, optional task to stop vendoring `public/domma-reactive.esm.js` (7,999 of its 9,587 lines)
|
|
414
|
+
and take the library from npm instead.
|
|
415
|
+
- [ ] `retro64-games` - **no action required.** Tracked in `retro-64`. It is structurally a proper
|
|
416
|
+
plugin (four routes, admin view, per-game JSON store) and stays one; it is simply never a
|
|
417
|
+
marketplace plugin.
|
|
418
|
+
- [x] `retro64-cors` - **core feature landed**: `cors.publicPrefixes` in `config/server.json`, with
|
|
419
|
+
`server/services/publicCors.js`, 5 tests, and the hook wired in `server.js`. `publicPrefixes` is
|
|
420
|
+
stripped before the `@fastify/cors` register call rather than passed as an unknown option, and the
|
|
421
|
+
`skip-override` trick is not needed in core because the hook attaches to the root instance.
|
|
422
|
+
**The plugin is NOT deleted yet**: the feature has to reach `retro-64` in a CMS release first, or
|
|
423
|
+
there would be a window with neither. Deleting it, and setting
|
|
424
|
+
`cors.publicPrefixes: ["/public/retro64/"]` on that site, belongs to that release.
|
|
425
|
+
- [ ] `cache-autoflush` - promote into core. **Own task, not this plan**, unchanged (see the spec's
|
|
426
|
+
[Promoting a plugin into core](../2026-09-22-dcms-marketplace-design.md#promoting-a-plugin-into-core)):
|
|
427
|
+
it needs a `config/cache.json` block, a startup hook in `server.js`, and a retirement migration
|
|
428
|
+
modelled on `search-migration.js`. Until then it stays where it is and keeps working.
|
|
429
|
+
- [x] `shopping-cart` - **untouched scaffolder output**: every route commented out, `onEnable` empty.
|
|
430
|
+
Reserved in `reserved.json`, nothing imported. Free is the right tier once something exists.
|
|
431
|
+
- [x] `domma-cms-site` needs nothing - it is tracked in the manager repo, as above.
|
|
432
|
+
|
|
433
|
+
**Exit: met**, except `retro64-cors`'s plugin deletion, which waits on a release.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Phase 7 - Withdraw seo-pro
|
|
438
|
+
|
|
439
|
+
**Order is load-bearing: the bundle first, then the licences.**
|
|
440
|
+
|
|
441
|
+
**Done 2026-09-22**, in the stated order.
|
|
442
|
+
|
|
443
|
+
- [x] `seo-pro` removed from `proBundle` **first**. Confirmed no `settings.proBundle` override exists
|
|
444
|
+
in the manager's `config/plugins.json`, so the default is the whole answer.
|
|
445
|
+
- [x] Three licences revoked. `licenceManager.list({pluginName: 'seo-pro'})` returns three `revoked`
|
|
446
|
+
records and nothing else. Ledger still 26 records.
|
|
447
|
+
- [x] Files deleted from `pinpointzero`, `retro-64`, `atlas-learning` and from `paid-plugins/`,
|
|
448
|
+
archived first to `var/phase7-seo-pro-20260922T104908.tar.gz`.
|
|
449
|
+
- [x] `seo-pro` pruned from `proPlugins` on all three sites, so the PRO badge stays honest.
|
|
450
|
+
- [x] Already reserved in `reserved.json`, and never imported into the monorepo.
|
|
451
|
+
|
|
452
|
+
**Exit: met.** All four affected domains verified 200 afterwards.
|
|
453
|
+
|
|
454
|
+
### `rescindPlugin()` was not used, and no restart was forced
|
|
455
|
+
|
|
456
|
+
`rescindPlugin()` restarts the child through `processManager`. **That only works from inside the
|
|
457
|
+
manager's own process**, which forks and supervises each site: a `processManager.start()` called from
|
|
458
|
+
a standalone script would parent the site to that script and orphan it on exit. Restarting a fleet
|
|
459
|
+
site has to go through the Site Manager API, not a node one-liner.
|
|
460
|
+
|
|
461
|
+
It turned out not to be needed. Nothing on any site referenced `/api/plugins/seo-pro/meta`, and
|
|
462
|
+
removing a plugin's files does not unregister its route from a process that already loaded it - so the
|
|
463
|
+
plugin simply stops existing at each site's next restart, whenever that happens. `registerPlugins`
|
|
464
|
+
iterates discovered manifests, so a stale `enabled` entry for a missing plugin is ignored rather than
|
|
465
|
+
an error; the config entries were dropped anyway.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Phase 8 - Retire the hand-maintained lists
|
|
470
|
+
|
|
471
|
+
**Done 2026-09-22.**
|
|
472
|
+
|
|
473
|
+
- [x] `KNOWN_HUES` deleted. Every hue is derived from the plugin name, which is what the fallback did
|
|
474
|
+
anyway. `_template` stays colourless.
|
|
475
|
+
- [x] `README.md`'s Bundled Plugins table regenerated from the manifests actually present - the eight
|
|
476
|
+
free-tier plugins - with a pointer to the marketplace for the rest.
|
|
477
|
+
- [x] Already recorded in the spec that `proBundle` moves into `plugin-terms.json` at step 5.
|
|
478
|
+
|
|
479
|
+
**Exit: met.** The only plugin list left in `domma-cms` is `CORE_PLUGINS`, which is a mechanism rather
|
|
480
|
+
than an inventory. Suite at **1,555 passing**.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Verification
|
|
485
|
+
|
|
486
|
+
Run after every phase, not only at the end. The fleet is live throughout.
|
|
487
|
+
|
|
488
|
+
- [ ] `npm test` in `domma-cms` passes, serially. Restore any polluted config file.
|
|
489
|
+
- [ ] All nine customer domains respond. Apache proxies every one through the manager, so a manager
|
|
490
|
+
fault takes them all out at once.
|
|
491
|
+
- [ ] Each site's admin Plugins screen loads and reports no plugin in a failed-load state
|
|
492
|
+
(`getPluginLoadFailures()`).
|
|
493
|
+
- [ ] `getEntitlements()` reports no orphan state on any site: nothing installed-but-unlicensed,
|
|
494
|
+
nothing licensed-but-missing.
|
|
495
|
+
- [ ] A fresh `npm run build` succeeds and `_publish/plugins/` contains exactly the free tier plus
|
|
496
|
+
`_lib` and `_template`.
|
|
497
|
+
- [ ] `git clone` of the monorepo, then a diff of each plugin against its live deployed copy,
|
|
498
|
+
accounts for every difference as minification.
|
|
499
|
+
|
|
500
|
+
## Out of scope
|
|
501
|
+
|
|
502
|
+
Everything in spec steps 3 to 6: the Ed25519 key and catalogue generation, the envelope format,
|
|
503
|
+
export and import, entitlement verification, the terms store and the Plugin Terms view, and wiring
|
|
504
|
+
the marketplace client to the manager. This plan only moves code, licences and lists into the shape
|
|
505
|
+
those steps assume.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Marketplace Step 3 - The Signing Key and the Catalogue
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Generate the Ed25519 key `marketplace-1` offline, retire `manager-2026` from the CMS,
|
|
6
|
+
and turn the monorepo's 20 plugins into a signed, publishable catalogue.
|
|
7
|
+
|
|
8
|
+
**Architecture:** `make catalogue` walks `plugins/*/plugin.json` and emits `catalogue.json` plus one
|
|
9
|
+
tarball and detached signature per plugin, all signed with a key that never touches the manager host.
|
|
10
|
+
`terms.json` is a separate file on a separate lifecycle, so a price change is a save rather than a
|
|
11
|
+
release. The CMS gains the new public key and loses the old one.
|
|
12
|
+
|
|
13
|
+
**Spec:** `docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md` (sequencing step 3, and
|
|
14
|
+
[Signing keys](../2026-09-22-dcms-marketplace-design.md#signing-keys))
|
|
15
|
+
|
|
16
|
+
**Prerequisite, already done:** `scripts/entitlement.js` and `scripts/issue-entitlement.js` exist in
|
|
17
|
+
`dcms-marketplace` and are tested. They need only a key to become useful.
|
|
18
|
+
|
|
19
|
+
**Critical project rules** (from `CLAUDE.md` + memory):
|
|
20
|
+
|
|
21
|
+
- The private key is generated on a machine that is **not** the manager host and is never copied onto
|
|
22
|
+
it. This server is the manager host.
|
|
23
|
+
- Server on port **4096** is always running; never start or stop it.
|
|
24
|
+
- `npm test` is serial. `domma-cms` is worked on concurrently - commit early, named paths only.
|
|
25
|
+
- Do not publish to npm as part of this plan.
|
|
26
|
+
|
|
27
|
+
**Ordering constraint:** the key exists before anything is signed, and `manager-2026` leaves
|
|
28
|
+
`TRUSTED_KEYS` in the same release that adds `marketplace-1`. Shipping the new key while keeping the
|
|
29
|
+
old one would leave a window where a bundle signed by a lost key is still accepted.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Phase 1 - Generate and place the key - DONE (on this host, deliberately)
|
|
34
|
+
|
|
35
|
+
**Generated 2026-09-22 on the manager host**, which is a deliberate deviation from this phase's own
|
|
36
|
+
instruction - see the spec's Custody section, which records it rather than pretending otherwise.
|
|
37
|
+
|
|
38
|
+
- [x] Pair generated:
|
|
39
|
+
```sh
|
|
40
|
+
openssl genpkey -algorithm ed25519 -out marketplace-1.pem
|
|
41
|
+
openssl pkey -in marketplace-1.pem -pubout -out marketplace-1.pub
|
|
42
|
+
```
|
|
43
|
+
- [x] Private half at `~/.domma-keys/marketplace-1.pem` (dir 0700, key 0600), outside every
|
|
44
|
+
repository.
|
|
45
|
+
- [x] **Second copy held off this disk**, confirmed by the owner 2026-09-22. Recorded as reported
|
|
46
|
+
rather than verified: this host has a single filesystem, no removable media and no network mounts,
|
|
47
|
+
so a backup here is by definition somewhere I cannot see.
|
|
48
|
+
|
|
49
|
+
This was the failure being recovered from - `plugin-signing-2026.pem` was named in the code and
|
|
50
|
+
existed nowhere. The key has now signed a catalogue, so losing it would mean re-signing everything
|
|
51
|
+
under a new keyId and shipping a CMS release before anyone trusted it.
|
|
52
|
+
- [x] `marketplace-1.pub` committed to `dcms-marketplace/keys/`; `*.pem` is gitignored there, and
|
|
53
|
+
`git check-ignore` confirms the private half cannot land by accident.
|
|
54
|
+
- [ ] Record in `PROVENANCE.md` where the private key lives - the location, not the key.
|
|
55
|
+
|
|
56
|
+
**Exit:** `keys/marketplace-1.pub` is committed, the private half is in two safe places, and
|
|
57
|
+
`git log -p` shows no `.pem` anywhere.
|
|
58
|
+
|
|
59
|
+
## Phase 2 - Swap the key in the CMS - DONE
|
|
60
|
+
|
|
61
|
+
- [x] `marketplace-1` added, `manager-2026` deleted, verifier switched to `crypto.verify(null, …)`.
|
|
62
|
+
- [ ] Delete the `PLACEHOLDER` guard if it no longer applies, or keep it if a placeholder is still
|
|
63
|
+
conceivable. It exists so a stub key can never verify anything.
|
|
64
|
+
- [x] Tests added via a new `trustedKeyIds()` export, so the contract is checkable without key
|
|
65
|
+
material: exactly one key trusted, `manager-2026` gone, the trusted key is **Ed25519** (an RSA
|
|
66
|
+
paste would verify fine and only fail in the field), and `manager-2026` is refused by name before
|
|
67
|
+
any signature check.
|
|
68
|
+
- [ ] Still to do: the signed-bundle install round trip, which needs the catalogue build of Phase 3
|
|
69
|
+
to produce a real bundle.
|
|
70
|
+
- [ ] `npm test` passes.
|
|
71
|
+
|
|
72
|
+
**Exit:** the CMS trusts exactly one key, and the retired one is refused by name.
|
|
73
|
+
|
|
74
|
+
## Phase 3 - Catalogue generation - DONE
|
|
75
|
+
|
|
76
|
+
- [x] `scripts/build-catalogue.js`, wired to `make catalogue KEY=...`, emitting:
|
|
77
|
+
```
|
|
78
|
+
dist/catalogue.json signed index
|
|
79
|
+
dist/catalogue.json.sig
|
|
80
|
+
dist/bundles/<slug>-<version>.tar.gz
|
|
81
|
+
dist/bundles/<slug>-<version>.tar.gz.sig
|
|
82
|
+
```
|
|
83
|
+
- [x] Entries carry **artifact facts only**: `slug`, `version`, `displayName`, `description`, `tier`,
|
|
84
|
+
`icon`, `supersedes`, `requires`, `minCmsVersion`. No prices - those live in `terms.json`.
|
|
85
|
+
- [x] **Refuses any slug in `reserved.json`**, and aborts with `dist/` untouched. `seo-pro`, `shopping-cart`, `form-builder` and
|
|
86
|
+
`site-search` must not be publishable by someone adding a directory and forgetting why the name was
|
|
87
|
+
held. `scripts/verify.js` already fails a reserved slug; the catalogue build must too.
|
|
88
|
+
- [x] Excludes `data/`, `backups/`, `node_modules/` and `CLAUDE.md` from every tarball. `data-transfer` writes backup trees at
|
|
89
|
+
runtime and one site had 28 of them.
|
|
90
|
+
- [x] Tarball format **proven**, not assumed, through `pluginInstaller.extractTar()`: which strips the leading
|
|
91
|
+
path component and ignores symlinks. **Generate a tarball and install it on a scratch CMS** rather
|
|
92
|
+
than assuming the formats agree.
|
|
93
|
+
- [x] Reuses `canonicalise()` for the catalogue signature, so there is one
|
|
94
|
+
canonical-JSON implementation in the repo rather than two that can drift.
|
|
95
|
+
|
|
96
|
+
**Exit: met.** 14 plugins, 2 free and 12 paid. A generated `mongodb-adapter` bundle installed on the
|
|
97
|
+
dev CMS through the real installer and its files landed correctly; a copy with one byte flipped was
|
|
98
|
+
refused with "Plugin signature invalid". The catalogue signature verifies and fails on tampering.
|
|
99
|
+
|
|
100
|
+
### Found while building: validation order
|
|
101
|
+
|
|
102
|
+
The first version wiped `dist/` and then validated as it went, so **one bad manifest destroyed the
|
|
103
|
+
last good catalogue on its way to failing**. Surfaced by testing the reserved-slug refusal, which is
|
|
104
|
+
precisely the case that would hit it in practice. Validation now runs first and a failed build leaves
|
|
105
|
+
the previous output exactly where it was.
|
|
106
|
+
|
|
107
|
+
Builds are also reproducible - `--sort=name`, fixed mtime, `gzip -n` - so unchanged code produces
|
|
108
|
+
identical signatures. Without it every release would churn every signature and customers would
|
|
109
|
+
re-download code that had not changed.
|
|
110
|
+
|
|
111
|
+
## Phase 4 - The free-tier sync
|
|
112
|
+
|
|
113
|
+
- [ ] `scripts/sync-free-tier.js`, wired to `make sync-free-tier`: copy every `tier: "free"` plugin
|
|
114
|
+
into `domma-cms/plugins/`, so `npm install domma-cms` stays a complete offline plugin-development
|
|
115
|
+
environment.
|
|
116
|
+
- [ ] It must **not** touch `_lib` or `_template`, which live in `domma-cms` and are its own.
|
|
117
|
+
- [ ] Decide the direction of truth first. Today `domma-cms` is still the working copy for free
|
|
118
|
+
plugins, and this script would make the monorepo authoritative. Whichever is chosen, only one of
|
|
119
|
+
them can be edited afterwards, and the other is generated. **This is open question 5 in the spec
|
|
120
|
+
and should be settled before the script exists**, because a two-way sync is how both copies end up
|
|
121
|
+
wrong.
|
|
122
|
+
|
|
123
|
+
**Exit:** the free subset in `domma-cms` is reproducible from the monorepo by one command, and it is
|
|
124
|
+
documented which side is authoritative.
|
|
125
|
+
|
|
126
|
+
## Phase 5 - Publish
|
|
127
|
+
|
|
128
|
+
- [ ] Put `dist/` on object storage plus CDN. Static files only: no application server on the public
|
|
129
|
+
path, which is the whole point of signing offline.
|
|
130
|
+
- [ ] `terms.json` publishes alongside, rewritten whenever a price changes. It does **not** require a
|
|
131
|
+
catalogue rebuild.
|
|
132
|
+
- [ ] Verify a plain `curl` of the catalogue and one bundle from an unauthenticated client, since
|
|
133
|
+
that is exactly what a CMS does.
|
|
134
|
+
|
|
135
|
+
**Exit:** a CMS with no credentials can fetch and verify the catalogue and a free bundle.
|
|
136
|
+
|
|
137
|
+
## Verification
|
|
138
|
+
|
|
139
|
+
- [ ] `npm test` in `domma-cms` passes, serially.
|
|
140
|
+
- [ ] `make verify` and `make test` pass in `dcms-marketplace`.
|
|
141
|
+
- [ ] All nine customer domains respond; the fleet is untouched by any of this.
|
|
142
|
+
- [ ] A bundle signed with a throwaway key is **refused** by a CMS trusting only `marketplace-1`.
|
|
143
|
+
- [ ] `git log -p` across both repos contains no private key material.
|
|
144
|
+
|
|
145
|
+
## Out of scope
|
|
146
|
+
|
|
147
|
+
Step 4's envelope and export/import, step 5's entitlement verification and terms store, and step 6's
|
|
148
|
+
wiring of the marketplace client. Issuing the owner wildcards is step 5 work and cannot happen until
|
|
149
|
+
the verifier exists - though once the key from Phase 1 exists, the command itself is ready:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
node scripts/issue-entitlement.js --key <private.pem> --slug '*' --model perpetual --no-support \
|
|
153
|
+
--purchaser-name "Domma (owner instance: dcm.dommajs.org)" --out entitlement.json
|
|
154
|
+
```
|