@aeptus/aexeo-emdash 0.8.0 → 0.8.3

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
@@ -6,6 +6,115 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.3] - 2026-05-04
10
+
11
+ Picks up the upstream aexeo-core 0.0.9 improvements that address
12
+ the two open quality items Aeptus flagged after the 0.8.1 + CLI
13
+ 0.0.8 rollout.
14
+
15
+ ### Changed (engine via bridge)
16
+
17
+ - **Truth-manifest generator no longer picks listing-page or
18
+ 404 titles as the organization/product name.** Aeptus's
19
+ facts.json was being generated with `organization.name = "Blog"`
20
+ and `product.name = "Page not found"` because the listing page's
21
+ title outvoted the homepage and the 404 page's title leaked into
22
+ the candidate corpus. Three engine fixes ship in this release:
23
+ (a) the generic-label blocklist now covers blog, posts,
24
+ articles, authors, tags, categories, search, archive, page not
25
+ found, 404, error, oops, and pure-numeric labels;
26
+ (b) 404 / error pages are detected by title/h1 patterns and
27
+ excluded from the candidate corpus entirely;
28
+ (c) the homepage signal wins over any subpage signal regardless
29
+ of count when present.
30
+
31
+ This is a generation-quality improvement, not a wire-format
32
+ change — existing stored manifests are unaffected. Hosts who
33
+ re-run `intelligence facts generate` will get a cleaner draft.
34
+
35
+ - **GEO009 ("page facts misalign across title/H1/OG/schema")
36
+ now uses the same identity-extraction logic as the
37
+ `intelligence identity` diagnostic.** Previously the rule read
38
+ every JSON-LD `name` field (including BreadcrumbList items and
39
+ ItemList children), so it fired on listing/author/localized
40
+ routes where breadcrumb crumbs leaked in. The diagnostic
41
+ already restricted to top-level identity-bearing schema; the
42
+ rule and the diagnostic now share that filter. Aeptus's
43
+ ~44 GEO009 warnings on listing/author/localized routes should
44
+ drop after re-running the audit.
45
+
46
+ ### Notes for hosts upgrading from 0.8.x
47
+
48
+ - No code changes required — pure engine-side improvements.
49
+ - Re-run `intelligence facts generate` if you generated your
50
+ manifest with v0.0.7 or earlier of the CLI; the new generator
51
+ output is materially better.
52
+ - Re-run the audit; expect GEO009 counts to drop on listing
53
+ pages without any host-side change.
54
+
55
+ ## [0.8.2] - 2026-05-04
56
+
57
+ Fix for the residual entity-presence trim() crash that 0.8.1
58
+ didn't solve.
59
+
60
+ ### Fixed
61
+
62
+ - **`/facts` and `/presence` routes were double-wrapping their
63
+ return values.** Both handlers returned `{ data: ... }` payloads,
64
+ but emdash's route registry already wraps a handler's return as
65
+ `{ data: <result> }` at the wire boundary. The result was
66
+ `{ data: { data: ... } }` on the wire. The React caller's
67
+ single-unwrap (`body.data`) surfaced `{ data: ... }` to the
68
+ consumer where the actual payload was expected — so
69
+ `(res as ManifestData).manifest` came back as `undefined`,
70
+ `JSON.stringify(undefined)` returned the literal `undefined`,
71
+ `setDraft(undefined)` set the textarea draft to undefined, and
72
+ the next render crashed on `draft.trim()`.
73
+
74
+ Fixed by changing both route handlers to return raw payloads
75
+ (matching the `/data` and `/refresh` route convention that has
76
+ always worked correctly). Errors are now signaled by throwing
77
+ `PluginRouteError` from the `emdash` package — the registry
78
+ treats thrown PluginRouteErrors as structured failures and
79
+ routes them through the host's standard error path. Returned
80
+ `{ error: ... }` objects (the previous pattern) flowed through
81
+ the success path and silently masqueraded as data.
82
+
83
+ ### Notes for hosts upgrading from 0.8.x
84
+
85
+ - No code changes required.
86
+ - The wire format of the `/facts` and `/presence` route responses
87
+ changes: where they previously returned
88
+ `{ data: { data: <payload> } }`, they now return
89
+ `{ data: <payload> }`. Anyone calling these routes directly
90
+ (outside the bundled React admin) needs to drop one unwrap.
91
+ The bundled admin is updated in lockstep so editors see no
92
+ behavioral difference beyond the bug being fixed.
93
+ - Error responses for these routes now come back through the
94
+ standard `PluginRouteError` envelope (HTTP 4xx with
95
+ `{ error: { code, message } }`) instead of being silently
96
+ embedded in success responses.
97
+
98
+ ## [0.8.1] - 2026-05-03
99
+
100
+ Fix for the entity-presence diagnostic shipped in 0.8.0.
101
+
102
+ ### Fixed
103
+
104
+ - `EntityPresence` admin component's fetch to `/presence` was
105
+ missing the `X-EmDash-Request: 1` header that emdash's catch-all
106
+ plugin-route handler requires for state-changing methods on
107
+ private routes. Without it the host returns 403 CSRF_REJECTED
108
+ before the plugin handler runs, and the host's plugin-registry
109
+ bundle then crashes on the rejected response shape with a
110
+ `Cannot read properties of undefined (reading 'trim')` error
111
+ during admin-page render. With the header added, both the 403
112
+ and the downstream trim crash clear.
113
+
114
+ The `/data`, `/refresh`, and `/facts` routes were already sending
115
+ the header — `/presence` was the new addition and the omission
116
+ shipped in 0.8.0.
117
+
9
118
  ## [0.8.0] - 2026-05-03
10
119
 
11
120
  Layer-4 entity-presence diagnostic. The `/entity-legitimacy`
@@ -499,7 +608,10 @@ First public release.
499
608
  `astro.config.mjs` for the WASM import to resolve to a
500
609
  precompiled `WebAssembly.Module`. Not optional.
501
610
 
502
- [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.8.0...HEAD
611
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.8.3...HEAD
612
+ [0.8.3]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.8.2...aexeo-emdash-v0.8.3
613
+ [0.8.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.8.1...aexeo-emdash-v0.8.2
614
+ [0.8.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.8.0...aexeo-emdash-v0.8.1
503
615
  [0.8.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.7.0...aexeo-emdash-v0.8.0
504
616
  [0.7.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.6.0...aexeo-emdash-v0.7.0
505
617
  [0.6.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.5.0...aexeo-emdash-v0.6.0
package/dist/admin.js CHANGED
@@ -476,7 +476,15 @@ function formatAge(minutes) {
476
476
  async function callPresenceRoute(kind) {
477
477
  const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/presence`, {
478
478
  method: "POST",
479
- headers: { "Content-Type": "application/json" },
479
+ headers: {
480
+ "Content-Type": "application/json",
481
+ // Required by emdash's catch-all plugin-route handler for any
482
+ // POST/PUT/PATCH/DELETE on a private (default) plugin route.
483
+ // Without it the host returns 403 CSRF_REJECTED before the
484
+ // plugin handler even runs. Same convention as the /facts and
485
+ // /data routes the other admin components use.
486
+ "X-EmDash-Request": "1"
487
+ },
480
488
  body: JSON.stringify({ kind })
481
489
  });
482
490
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -469,8 +469,8 @@ async function handleDataRoute(ctx, options) {
469
469
  });
470
470
  await persistManifestStateFindings(ctx);
471
471
  }
472
- const payload = await buildFindingsPayload(ctx);
473
- return { payload, summary };
472
+ const payload2 = await buildFindingsPayload(ctx);
473
+ return { payload: payload2, summary };
474
474
  }
475
475
  async function buildFindingsPayload(ctx) {
476
476
  const stored = await readAllStoredDocuments(ctx.kv);
@@ -649,6 +649,7 @@ function buildPublishedUrl(meta, route, siteUrl) {
649
649
  }
650
650
 
651
651
  // src/facts-route.ts
652
+ import { PluginRouteError } from "emdash";
652
653
  async function handleFactsRoute(ctx) {
653
654
  const body = ctx.input ?? {};
654
655
  switch (body.kind) {
@@ -661,47 +662,34 @@ async function handleFactsRoute(ctx) {
661
662
  case "save":
662
663
  return await handleSave(ctx, body);
663
664
  default:
664
- return {
665
- error: {
666
- code: "unknown_kind",
667
- message: `unknown facts route kind: ${String(body.kind)}`
668
- }
669
- };
665
+ throw PluginRouteError.badRequest(
666
+ `unknown facts route kind: ${String(body.kind)}`
667
+ );
670
668
  }
671
669
  }
672
670
  async function handleData(ctx) {
673
671
  const manifest = await readStoredFacts(ctx.kv);
674
672
  return {
675
- data: {
676
- manifest,
677
- // The presence flag lets the React component branch on
678
- // "show authoring CTA" vs "show 'manifest stored' state" without
679
- // having to introspect the manifest shape.
680
- present: manifest !== null
681
- }
673
+ manifest,
674
+ // The presence flag lets the React component branch on
675
+ // "show authoring CTA" vs "show 'manifest stored' state" without
676
+ // having to introspect the manifest shape.
677
+ present: manifest !== null
682
678
  };
683
679
  }
684
680
  async function handlePrompt(ctx) {
685
681
  const documents = await readAllDocuments(ctx.kv);
686
682
  if (documents.length === 0) {
687
- return {
688
- error: {
689
- code: "no_documents",
690
- message: "no documents indexed yet \u2014 click Refresh on the Findings page first, then come back to author the manifest"
691
- }
692
- };
683
+ throw PluginRouteError.badRequest(
684
+ "no documents indexed yet \u2014 click Refresh on the Findings page first, then come back to author the manifest"
685
+ );
693
686
  }
694
687
  const prompt = await generateFactsPrompt2(JSON.stringify(documents));
695
- return { data: { prompt } };
688
+ return { prompt };
696
689
  }
697
690
  async function handleValidate(ctx, body) {
698
691
  if (typeof body.manifest_json !== "string" || body.manifest_json.length === 0) {
699
- return {
700
- error: {
701
- code: "missing_manifest",
702
- message: "manifest_json field is required"
703
- }
704
- };
692
+ throw PluginRouteError.badRequest("manifest_json field is required");
705
693
  }
706
694
  const documents = await readAllDocuments(ctx.kv);
707
695
  try {
@@ -709,24 +697,16 @@ async function handleValidate(ctx, body) {
709
697
  body.manifest_json,
710
698
  JSON.stringify(documents)
711
699
  );
712
- return { data: JSON.parse(raw) };
700
+ return JSON.parse(raw);
713
701
  } catch (error) {
714
- return {
715
- error: {
716
- code: "validate_failed",
717
- message: error instanceof Error ? error.message : String(error)
718
- }
719
- };
702
+ throw PluginRouteError.badRequest(
703
+ error instanceof Error ? error.message : String(error)
704
+ );
720
705
  }
721
706
  }
722
707
  async function handleSave(ctx, body) {
723
708
  if (typeof body.manifest_json !== "string" || body.manifest_json.length === 0) {
724
- return {
725
- error: {
726
- code: "missing_manifest",
727
- message: "manifest_json field is required"
728
- }
729
- };
709
+ throw PluginRouteError.badRequest("manifest_json field is required");
730
710
  }
731
711
  const documents = await readAllDocuments(ctx.kv);
732
712
  let validation;
@@ -738,37 +718,31 @@ async function handleSave(ctx, body) {
738
718
  const parsed2 = JSON.parse(raw);
739
719
  validation = parsed2.validation;
740
720
  } catch (error) {
741
- return {
742
- error: {
743
- code: "validate_failed",
744
- message: error instanceof Error ? error.message : String(error)
745
- }
746
- };
721
+ throw PluginRouteError.badRequest(
722
+ error instanceof Error ? error.message : String(error)
723
+ );
747
724
  }
748
725
  if (!validation.valid || validation.errors.length > 0) {
749
- return {
750
- error: {
751
- code: "validation_failed",
752
- message: `manifest does not pass shape validation: ${validation.errors.slice(0, 3).join("; ")}`
753
- }
754
- };
726
+ throw PluginRouteError.badRequest(
727
+ `manifest does not pass shape validation: ${validation.errors.slice(0, 3).join("; ")}`
728
+ );
755
729
  }
756
730
  let parsed;
757
731
  try {
758
732
  parsed = JSON.parse(body.manifest_json);
759
733
  } catch (error) {
760
- return {
761
- error: {
762
- code: "parse_failed",
763
- message: error instanceof Error ? error.message : String(error)
764
- }
765
- };
734
+ throw PluginRouteError.badRequest(
735
+ error instanceof Error ? error.message : String(error)
736
+ );
766
737
  }
767
738
  await ctx.kv.set(FACTS_KEY, parsed);
768
739
  await persistManifestStateFindings(ctx);
769
- return { data: { saved: true } };
740
+ return { saved: true };
770
741
  }
771
742
 
743
+ // src/presence-route.ts
744
+ import { PluginRouteError as PluginRouteError2 } from "emdash";
745
+
772
746
  // src/presence-check.ts
773
747
  var FETCH_TIMEOUT_MS = 5e3;
774
748
  var USER_AGENT = "aexeo-emdash-plugin/0.8 (+https://github.com/schiste/Aexeo; entity-presence diagnostic)";
@@ -1073,58 +1047,53 @@ async function handlePresenceRoute(ctx) {
1073
1047
  case "refresh":
1074
1048
  return await handleRefresh(ctx);
1075
1049
  default:
1076
- return {
1077
- error: {
1078
- code: "unknown_kind",
1079
- message: `unknown presence route kind: ${String(body.kind)}`
1080
- }
1081
- };
1050
+ throw PluginRouteError2.badRequest(
1051
+ `unknown presence route kind: ${String(body.kind)}`
1052
+ );
1082
1053
  }
1083
1054
  }
1084
1055
  async function handleData2(ctx) {
1085
1056
  const manifest = await readStoredFacts(ctx.kv);
1086
1057
  if (manifest === null) {
1087
- return wireResponse("no_manifest", [], null, null);
1058
+ return payload("no_manifest", [], null, null);
1088
1059
  }
1089
1060
  const entity = entityFromManifest(manifest);
1090
1061
  if (entity === null) {
1091
- return wireResponse("no_organization", [], null, null);
1062
+ return payload("no_organization", [], null, null);
1092
1063
  }
1093
1064
  const cached = await ctx.kv.get(PRESENCE_KEY);
1094
1065
  if (!cached) {
1095
- return wireResponse("empty", [], null, entity.name);
1066
+ return payload("empty", [], null, entity.name);
1096
1067
  }
1097
1068
  if (cached.entityName !== entity.name) {
1098
- return wireResponse("empty", [], null, entity.name);
1069
+ return payload("empty", [], null, entity.name);
1099
1070
  }
1100
1071
  const ageMs = ageMillis(cached.generatedAt);
1101
1072
  const state = ageMs !== null && ageMs > CACHE_TTL_MS ? "stale" : "fresh";
1102
- return wireResponse(state, cached.results, cached.generatedAt, entity.name);
1073
+ return payload(state, cached.results, cached.generatedAt, entity.name);
1103
1074
  }
1104
1075
  async function handleRefresh(ctx) {
1105
1076
  const manifest = await readStoredFacts(ctx.kv);
1106
1077
  if (manifest === null) {
1107
- return wireResponse("no_manifest", [], null, null);
1078
+ return payload("no_manifest", [], null, null);
1108
1079
  }
1109
1080
  const entity = entityFromManifest(manifest);
1110
1081
  if (entity === null) {
1111
- return wireResponse("no_organization", [], null, null);
1082
+ return payload("no_organization", [], null, null);
1112
1083
  }
1113
1084
  const results = await checkAllSources(entity);
1114
1085
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1115
- const payload = {
1086
+ const cachePayload = {
1116
1087
  results,
1117
1088
  generatedAt,
1118
1089
  entityName: entity.name
1119
1090
  };
1120
- await ctx.kv.set(PRESENCE_KEY, payload);
1121
- return wireResponse("fresh", results, generatedAt, entity.name);
1091
+ await ctx.kv.set(PRESENCE_KEY, cachePayload);
1092
+ return payload("fresh", results, generatedAt, entity.name);
1122
1093
  }
1123
- function wireResponse(state, results, generatedAt, entityName) {
1094
+ function payload(state, results, generatedAt, entityName) {
1124
1095
  const ageMinutes = generatedAt === null ? null : Math.max(0, Math.floor((ageMillis(generatedAt) ?? 0) / 6e4));
1125
- return {
1126
- data: { state, results, generatedAt, entityName, ageMinutes }
1127
- };
1096
+ return { state, results, generatedAt, entityName, ageMinutes };
1128
1097
  }
1129
1098
  function ageMillis(iso) {
1130
1099
  const t = Date.parse(iso);
@@ -1191,7 +1160,7 @@ function globToMatcher(pattern) {
1191
1160
  }
1192
1161
 
1193
1162
  // src/version.ts
1194
- var PACKAGE_VERSION = "0.8.0";
1163
+ var PACKAGE_VERSION = "0.8.3";
1195
1164
 
1196
1165
  // src/configured.ts
1197
1166
  import { definePlugin } from "emdash";
@@ -1 +1 @@
1
- {"version":3,"file":"facts-route.d.ts","sourceRoot":"","sources":["../src/facts-route.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAa9C,UAAU,YAAa,SAAQ,UAAU;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB1E"}
1
+ {"version":3,"file":"facts-route.d.ts","sourceRoot":"","sources":["../src/facts-route.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAa9C,UAAU,YAAa,SAAQ,UAAU;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAcD,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB1E"}
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ function buildAllowedHosts(evaluatorHost) {
23
23
  }
24
24
 
25
25
  // src/version.ts
26
- var PACKAGE_VERSION = "0.8.0";
26
+ var PACKAGE_VERSION = "0.8.3";
27
27
 
28
28
  // src/sandbox.ts
29
29
  function aexeoPluginSandboxed(options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"presence-route.d.ts","sourceRoot":"","sources":["../src/presence-route.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuC9C,UAAU,YAAa,SAAQ,UAAU;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAelB"}
1
+ {"version":3,"file":"presence-route.d.ts","sourceRoot":"","sources":["../src/presence-route.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA0C9C,UAAU,YAAa,SAAQ,UAAU;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAYlB"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "0.8.0";
1
+ export declare const PACKAGE_VERSION = "0.8.3";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeptus/aexeo-emdash",
3
- "version": "0.8.0",
3
+ "version": "0.8.3",
4
4
  "description": "Aexeo SEO/GEO content evaluator as an emdash plugin",
5
5
  "license": "MIT",
6
6
  "author": "Aeptus",
Binary file