@aeptus/aexeo-emdash 0.8.17 → 0.8.19

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 CHANGED
@@ -4,8 +4,137 @@ All notable changes to `@aeptus/aexeo-emdash` are listed here.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/),
5
5
  and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.8.19] - 2026-09-29
8
+
9
+ Updates the EmDash integration to the current plugin and content APIs, and
10
+ closes three security findings. Everything since 0.8.18 ships in this one
11
+ version, since 0.8.19 had never been published.
12
+
13
+ **Compatibility target:** EmDash `>=0.41.0 <0.42.0`.
14
+
15
+ ### Security
16
+
17
+ - **EVAL_TOKEN is stored through the host's encrypted plugin settings.**
18
+ The sidecar bearer token was written as plaintext into the plugin's KV
19
+ namespace. EmDash provides `PluginContext.settings` with host-side
20
+ encryption for fields declared `type: "secret"` in a plugin's
21
+ `settingsSchema`, which is exactly this case; the descriptor now declares
22
+ that schema and the token is written there. Installs that predate the
23
+ change have their plaintext KV copy deleted on the next write, so the
24
+ credential is not sitting in the database twice. Hosts without
25
+ `EMDASH_ENCRYPTION_KEY` configured fall back to the previous KV path with
26
+ a warning that names the missing environment variable and never the
27
+ token, so the Setup page keeps working instead of failing closed.
28
+ - **Sidecar URL validation is allowlist-backed and HTTPS-only.**
29
+ `handleSetupSubmit` accepted `http:`, which sent the bearer token in
30
+ cleartext, and rejected only `localhost` and `127.*` — leaving
31
+ `0.0.0.0`, `[::1]`, RFC1918, CGNAT, and the `169.254.169.254` cloud
32
+ metadata address reachable. It now requires HTTPS, rejects the private,
33
+ loopback, link-local, multicast, and IPv4-mapped-IPv6 ranges, and
34
+ compares the submitted host against the `allowedHosts` list the plugin
35
+ already computes at build time, so the form cannot store a URL the
36
+ plugin's own allowlist would reject later.
37
+ - **Unauthenticated crawl-artifact routes removed from
38
+ `packages/aexeo-crawl-worker`.** `GET /findings/latest` returned the full
39
+ body of the customer's `aexeo-cli crawl` artifact and
40
+ `GET /findings/list` returned R2 key names, timestamps, and sizes.
41
+ Neither was behind the bearer-token check that guarded `/evaluate`, and
42
+ neither had any caller in this repository. CORS is not access control, so
43
+ anyone who learned the worker hostname could read the whole crawl
44
+ artifact. Both routes, the R2 binding, and the `examples/github-actions.yml`
45
+ R2 upload workflow that existed only to feed them are gone.
46
+
47
+ ### Removed
48
+
49
+ - **`src/mcp.ts` and `src/indexnow.ts`.** Both were unreachable. The host
50
+ reads `PluginDefinition.mcp` as a `Record` of tool definitions keyed by
51
+ name; the code attached an `mcpTools` array with a different container and
52
+ a different per-entry shape, and `configured.ts` never attached it at
53
+ all. No MCP tool this package offered could ever have been invoked.
54
+
55
+ ### Fixed
56
+
57
+ - **Suppressions ignored by the Block Kit Refresh button.** In configured
58
+ mode the /findings "Refresh" button called the sweep without the
59
+ compiled `suppressionFilter`, so it re-persisted findings the editor
60
+ had explicitly silenced. The React `/refresh` route always applied
61
+ them, so the two surfaces disagreed and the documented guarantee
62
+ ("suppressed findings never reach the dashboard, /findings, or the
63
+ per-document panel") did not hold on the Block Kit path.
64
+ Root cause: `configured.ts` and `sandbox-entry.ts` each carried their
65
+ own near-verbatim copy of the interaction normalizer, the findings
66
+ page, the score widget, the document panel, and the refresh handler.
67
+ The copies had already drifted — this missing argument was one of the
68
+ drifts. Fix: the whole block now lives once in `src/findings-view.ts`,
69
+ and `RefreshOptions.suppressionFilter` is a **required** property, so a
70
+ caller cannot silently omit it again.
71
+ - **`/evaluate` no longer 500s when `EVAL_TOKEN` is unset.** The check read
72
+ `env.EVAL_TOKEN.length` without a guard, so a worker deployed without the
73
+ secret threw a `TypeError` on every request instead of rejecting it.
74
+ - **Sandbox access declaration:** replace obsolete capability strings with
75
+ `content:read` and conditional `network:request`, scoped to the configured
76
+ evaluator host. Remove unused schema, artifact, KV, and IndexNow host grants.
77
+ - **Typed `blocks` content:** distinguish EmDash block compositions from
78
+ Portable Text and adapt their editorial strings into the evaluator's text
79
+ format instead of discarding them.
80
+ - **Routes and translations:** use EmDash's `getPublicUrl()` and
81
+ `getTranslations()` APIs for collection patterns, locale paths, trailing
82
+ slash policy, and hreflang alternates. Keep draft and unroutable entries
83
+ under stable collection/ID keys, and link only to URLs the host resolves.
84
+ - **Sandbox save evaluation:** `content:afterSave` now persists and evaluates
85
+ the saved item through the configured sidecar, matching configured mode.
86
+ - **Route migrations:** remove obsolete document and finding entries when a
87
+ document's resolved route changes or a successful refresh no longer finds
88
+ an entry in the selected collections.
89
+
90
+ ### Changed
91
+
92
+ - Extract the shared Block Kit view layer (`normalizeInteraction`,
93
+ `BlockInteraction`, `BlockResponse`, the findings page, the score
94
+ widget, the document panel, and the refresh sweep) out of
95
+ `configured.ts` and `sandbox-entry.ts` into `src/findings-view.ts`.
96
+ Behaviour is unchanged apart from the suppression fix above; the two
97
+ entrypoints keep their own dispatch and their own copy of the three
98
+ strings that legitimately differ (empty-state copy, score-widget
99
+ empty copy, hard-refresh banner variant), passed in through
100
+ `AdminViewTuning`. `BlockInteraction` and `BlockResponse` are
101
+ re-exported from `sandbox-entry.ts`, so the `./sandbox` entry's
102
+ public type surface is unchanged.
103
+ - Bump the EmDash peer and development ranges to `0.41.x`; update the README
104
+ and install guide to name the supported range and sandbox save behavior.
105
+ - Add regression tests for `normalizeInteraction` — the ten edge cases
106
+ the 0.8.17 changelog claimed to have verified but never did — plus
107
+ entrypoint parity tests that drive both admin routes with the same
108
+ malformed bodies, and a test that the configured Block Kit refresh and
109
+ the React `/refresh` route agree on suppressed findings. Vitest coverage
110
+ now runs against EmDash's official plugin runtime test host for sandbox
111
+ hooks, blocks extraction, resolved URLs, and translation alternates.
112
+ - Add `tsconfig.test.json` and a `typecheck:tests` script so `tests/`
113
+ and `vitest.config.ts` are typechecked. `tsconfig.json`'s `include`
114
+ covered `src/**` only, which meant the suite guarding the 0.8.17
115
+ production hotfix and the emdash test-host fixture plugin were both
116
+ unchecked. `npm run typecheck` now runs `typecheck:src` and
117
+ `typecheck:tests`.
118
+ - `packages/aexeo-crawl-worker` no longer commits its build artifacts. The
119
+ checked-in `aexeo_emdash_bridge_bg.wasm` and its glue were several
120
+ releases behind the bridge the plugin actually calls — missing
121
+ `generateFactsPrompt`, `validateFactsManifest`, the manifest argument on
122
+ `scoreIntelligence`, and the `__wbg_*` bindings — and nothing in the
123
+ repository could regenerate them. The hand-written `.d.ts` files had been
124
+ updated to match the stale binary, which is why it went unnoticed. The
125
+ binary is now produced by `npm run build:wasm`, chained into `dev` and
126
+ `deploy`, and only the two declaration files remain tracked. The package
127
+ also gains a README, which it did not have.
128
+
7
129
  ## [Unreleased]
8
130
 
131
+ ## [0.8.18] - 2026-09-28
132
+
133
+ ### Added
134
+
135
+ - Include deterministic editorial-policy rules EDT001–EDT003 in the bundled Rust/WASM evaluator. Configured answer summaries, target questions, and evidence-marked claims can now be checked through the serialized Aexeo config.
136
+
137
+
9
138
  ## [0.8.17] - 2026-05-29
10
139
 
11
140
  Hotfix release. Closes the production admin-route regression
package/INSTALL.md CHANGED
@@ -31,7 +31,8 @@ when it's actually needed.
31
31
  (`@astrojs/cloudflare` + `@emdash-cms/cloudflare`'s `sandbox()`
32
32
  runner is fine — sandboxed plugins use Worker Loader, configured
33
33
  plugins don't, but both adapters import cleanly).
34
- - emdash 0.7.0 or later as a peer dependency.
34
+ - EmDash `>=0.41.0 <0.42.0` as a peer dependency. The integration
35
+ coverage targets `0.41.0` with EmDash's official plugin test host.
35
36
 
36
37
  ### Install
37
38
 
@@ -106,9 +107,8 @@ no sidecar to deploy.
106
107
  — toast says "Refreshed N routes (M findings across K documents)"
107
108
  and the table populates.
108
109
 
109
- 4. Save a document: the `content:afterSave` hook auto-evaluates that
110
- one document; refresh the findings page and that route's findings
111
- are updated. (The dashboard widget also picks up the new score.)
110
+ 4. Save a document: `content:afterSave` evaluates that document. Reload
111
+ the findings page to see the updated row and dashboard score.
112
112
 
113
113
  ### Production deploy
114
114
 
@@ -171,8 +171,8 @@ Pre-flight before bumping:
171
171
 
172
172
  1. Look up the plugin version's tested emdash range in our
173
173
  [CHANGELOG](./CHANGELOG.md). The latest plugin version's
174
- `Compatibility` line says e.g. "tested against emdash 0.7.0
175
- through 0.8.0." If the emdash version you want is **inside that
174
+ `Compatibility` line names the supported EmDash range and test-host
175
+ target. If the EmDash version you want is **inside that
176
176
  range**, the bump is safe. If it's **above the range**, treat it
177
177
  as untested.
178
178
  2. If untested, scan the emdash release notes for breaking changes
@@ -181,7 +181,7 @@ Pre-flight before bumping:
181
181
  - Hook signatures (`content:afterSave`, etc.)
182
182
  - Block Kit element schemas (banner variants, button styles,
183
183
  table column formats)
184
- - Capability validation strings (`read:content`, `network:fetch`)
184
+ - Capability names (`content:read`, `network:request`)
185
185
  - The `definePlugin` API
186
186
  - The `createPlugin` / configured-plugin descriptor contract
187
187
  If any of those changed, expect to bump the plugin too.
@@ -240,9 +240,8 @@ Trade-offs vs. configured mode:
240
240
  prevents the plugin from ever reading host state directly.
241
241
  - **Con**: separate sidecar Worker to deploy and maintain; an
242
242
  EVAL_TOKEN to rotate; a Setup admin page to configure. The
243
- `content:afterSave` hook is also non-functional in emdash 0.7.x's
244
- sandbox (post-response bridge invalidation — known upstream issue);
245
- evaluation is manual-Refresh-only.
243
+ `content:afterSave` hook evaluates through the sidecar after you save
244
+ its URL and token on the Setup page. Refresh still runs a full-site sweep.
246
245
 
247
246
  ```js
248
247
  // astro.config.mjs (sandboxed mode)
@@ -267,16 +266,25 @@ in this repo. Per-site deploy:
267
266
 
268
267
  ```bash
269
268
  cd aexeo-crawl-worker
270
- # Edit wrangler.toml: name, R2 bucket name, SITE_URL
269
+ npm ci
270
+ # Edit wrangler.toml: name, SITE_URL
271
271
  npx wrangler login
272
- npx wrangler r2 bucket create <bucket-name>
273
- npx wrangler deploy
272
+ npm run deploy # builds the WASM bridge, then deploys
274
273
  echo "$(openssl rand -hex 32)" | npx wrangler secret put EVAL_TOKEN
275
274
  ```
276
275
 
276
+ The Worker's WASM is generated, not committed — `npm run deploy` runs
277
+ `npm run build:wasm` first, which needs the Rust toolchain and
278
+ `wasm-bindgen`. See the template's README.
279
+
277
280
  Then in the admin UI, visit
278
281
  `/admin/plugins/aexeo-emdash/setup` and paste the deployed URL +
279
- the same token.
282
+ the same token. The URL must be `https`, must not point at a
283
+ loopback, private, or link-local address, and must be the host in
284
+ `evaluatorHost` above — the emdash sandbox rejects any other outbound
285
+ host, so the form checks it before storing. The token is written
286
+ through the host's encrypted plugin settings (`admin.settingsSchema`
287
+ declares it `secret`), not to plugin KV.
280
288
 
281
289
  ## Alternative install sources
282
290
 
package/README.md CHANGED
@@ -47,12 +47,15 @@ troubleshooting list, see [INSTALL.md](./INSTALL.md).
47
47
  answer-pack, external-trust dimensions). Banner appears below 60.
48
48
  - **Refresh button**: re-evaluate every document in the configured
49
49
  collections (`posts`, `pages` by default).
50
- - **Auto-evaluate on save**: emdash's `content:afterSave` hook fires,
51
- re-evaluates the saved document, updates the findings table.
50
+ - **Auto-evaluate on save**: EmDash's `content:afterSave` hook evaluates
51
+ the saved document and updates its findings. Sandboxed installs need a
52
+ configured evaluator sidecar; configured installs run evaluation in-process.
52
53
 
53
54
  ## Compatibility
54
55
 
55
- - emdash `>= 0.7.0` (peer dep)
56
+ - EmDash `>= 0.41.0 < 0.42.0` (peer dependency; current supported range)
57
+ - Runtime integration coverage targets EmDash `0.41.0` via the official
58
+ `@emdash-cms/plugin-test` host.
56
59
  - Cloudflare Workers via `@astrojs/cloudflare`. The plugin uses
57
60
  Cloudflare-specific APIs and silently no-ops on the Node adapter.
58
61
  - WASM eval ships with the package; the consumer's Vite/Wrangler
package/dist/adapter.d.ts CHANGED
@@ -5,6 +5,7 @@ export interface EmdashContentItem {
5
5
  slug: string | null;
6
6
  status: string;
7
7
  locale: string | null;
8
+ translationGroup?: string | null;
8
9
  data: Record<string, unknown>;
9
10
  seo?: {
10
11
  title?: string | null;
@@ -18,11 +19,41 @@ export interface EmdashContentMeta {
18
19
  slug: string | null;
19
20
  status: string;
20
21
  title: string;
22
+ /** Exact URL returned by EmDash, or null when this entry has no public route. */
23
+ publicUrl: string | null;
24
+ }
25
+ export interface ContentUrlApi {
26
+ getPublicUrl(collection: string, id: string): Promise<string | null>;
27
+ getTranslations(collection: string, id: string): Promise<{
28
+ translationGroup: string;
29
+ translations: Array<{
30
+ id: string;
31
+ locale: string | null;
32
+ slug: string | null;
33
+ status: string;
34
+ updatedAt: string;
35
+ }>;
36
+ }>;
21
37
  }
22
38
  export interface AdaptedContent {
23
39
  document: EmdashDocument;
24
40
  meta: EmdashContentMeta;
25
41
  }
42
+ /**
43
+ * Adapt a stored content row using EmDash's route resolver. The host API
44
+ * accounts for collection URL patterns, locale prefixes, and slash policy;
45
+ * slug-derived paths are not equivalent to the site's actual public URLs.
46
+ */
47
+ export declare function adaptContentItemWithContext(content: EmdashContentItem, contentApi: ContentUrlApi, defaultLocale?: string): Promise<AdaptedContent>;
48
+ /**
49
+ * Synchronous adapter for callers that do not have a host content API.
50
+ * It deliberately uses a stable identity route; callers should use
51
+ * adaptContentItemWithContext whenever EmDash can resolve the public URL.
52
+ */
26
53
  export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
27
- export declare function adaptContentItem(content: EmdashContentItem): AdaptedContent;
54
+ export declare function adaptContentItem(content: EmdashContentItem, options?: {
55
+ route?: string;
56
+ publicUrl?: string | null;
57
+ defaultLocale?: string;
58
+ }): AdaptedContent;
28
59
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAOD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IAIf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAkC3E"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrE,eAAe,CACb,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,KAAK,CAAC;YAClB,EAAE,EAAE,MAAM,CAAC;YACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;YACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,aAAa,EACzB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CA2CzB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,GACL,cAAc,CAgChB"}
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0HrD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA4H3E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AA0HxE,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA2H3E;AAID,eAAe,YAAY,CAAC"}