@bongos/core 1.21.182 → 1.22.2

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.
Files changed (247) hide show
  1. package/.bongos-core.json +273 -248
  2. package/docs/copy-inventory.md +165 -165
  3. package/docs/copy-registry.json +184 -184
  4. package/docs/module-api-changelog.md +12 -0
  5. package/docs/page-inventory.json +35 -26
  6. package/docs/page-readings.json +389 -388
  7. package/modules/agents/lib/answer-hold.js +1 -1
  8. package/modules/agents/lib/fire-budget.js +1 -1
  9. package/modules/autonomy/cadence.js +3 -3
  10. package/modules/autonomy/config-idle.js +1 -1
  11. package/modules/autonomy/fence.js +1 -1
  12. package/modules/autonomy/gauge.js +2 -2
  13. package/modules/autonomy/governor-docket.js +0 -1
  14. package/modules/autonomy/server-schedule.js +1 -1
  15. package/modules/builder-settings/builder-needs.js +1 -3
  16. package/modules/builder-settings/event-sound-prefs.js +0 -1
  17. package/modules/builder-settings/law-locks.js +0 -1
  18. package/modules/builder-settings/model-alloc-prefs.js +0 -5
  19. package/modules/builder-settings/profile-nudge-prefs.js +0 -1
  20. package/modules/builder-settings/render-prefs.js +0 -1
  21. package/modules/builder-settings/skill-prefs.js +0 -2
  22. package/modules/builder-settings/sound-prefs.js +0 -3
  23. package/modules/builder-settings/wandering-prefs.js +0 -2
  24. package/modules/copy-desk/artist-stats.js +1 -1
  25. package/modules/copy-desk/flags.js +1 -1
  26. package/modules/copy-desk/guarded-lines.js +1 -1
  27. package/modules/copy-desk/page-status.js +0 -5
  28. package/modules/copy-desk/proposals.js +0 -2
  29. package/modules/copy-desk/registry.js +1 -1
  30. package/modules/copy-desk/stuck-tweaks.js +1 -1
  31. package/modules/economy/credits-by-craft.js +0 -2
  32. package/modules/economy/streak.js +0 -3
  33. package/modules/government/asks-db.js +1 -1
  34. package/modules/government/asks.js +0 -1
  35. package/modules/government/decision-board.js +0 -1
  36. package/modules/government/docket.js +0 -6
  37. package/modules/government/leads.js +0 -1
  38. package/modules/government/positions.js +1 -1
  39. package/modules/government/recommendations.js +0 -3
  40. package/modules/government/routing.js +1 -1
  41. package/modules/government/treasury-db.js +1 -1
  42. package/modules/government/treasury.js +2 -2
  43. package/modules/grading/grade-engine.js +0 -1
  44. package/modules/hall-ui/public/approval-queue.js +3 -3
  45. package/modules/hall-ui/public/atlas.html +2 -1
  46. package/modules/hall-ui/public/atlas.js +1 -1
  47. package/modules/hall-ui/public/blockers.html +3 -4
  48. package/modules/hall-ui/public/board-room.html +2 -2
  49. package/modules/hall-ui/public/brand-holes.js +3 -3
  50. package/modules/hall-ui/public/city.html +2 -1
  51. package/modules/hall-ui/public/collab.html +2 -2
  52. package/modules/hall-ui/public/commands.html +2 -2
  53. package/modules/hall-ui/public/commands.js +1 -2
  54. package/modules/hall-ui/public/copy-desk.html +2 -2
  55. package/modules/hall-ui/public/core-move-notice.js +1 -5
  56. package/modules/hall-ui/public/craft-mode.js +1 -5
  57. package/modules/hall-ui/public/criterion-uat-ui.js +3 -3
  58. package/modules/hall-ui/public/deploy.html +2 -2
  59. package/modules/hall-ui/public/diagrams.html +2 -1
  60. package/modules/hall-ui/public/dom-utils.js +15 -6
  61. package/modules/hall-ui/public/drachmae.html +2 -1
  62. package/modules/hall-ui/public/fleet-costs.js +3 -2
  63. package/modules/hall-ui/public/fleet.html +2 -2
  64. package/modules/hall-ui/public/founding-mark.js +1 -6
  65. package/modules/hall-ui/public/gate.html +2 -2
  66. package/modules/hall-ui/public/goal-map.html +2 -1
  67. package/modules/hall-ui/public/goals.html +2 -2
  68. package/modules/hall-ui/public/government.html +2 -2
  69. package/modules/hall-ui/public/hall-ask.js +3 -3
  70. package/modules/hall-ui/public/hall-kit.js +3 -5
  71. package/modules/hall-ui/public/hall-visual.js +3 -5
  72. package/modules/hall-ui/public/idea.html +2 -2
  73. package/modules/hall-ui/public/ideas.html +2 -2
  74. package/modules/hall-ui/public/index.html +2 -2
  75. package/modules/hall-ui/public/lightbulb.js +1 -5
  76. package/modules/hall-ui/public/md-reader.js +1 -8
  77. package/modules/hall-ui/public/modules.html +2 -2
  78. package/modules/hall-ui/public/my-agents.html +2 -2
  79. package/modules/hall-ui/public/my-agents.js +3 -1
  80. package/modules/hall-ui/public/page-notes.js +1 -5
  81. package/modules/hall-ui/public/paintbrush.js +1 -5
  82. package/modules/hall-ui/public/palette.js +1 -5
  83. package/modules/hall-ui/public/primer.html +2 -1
  84. package/modules/hall-ui/public/primer.js +1 -1
  85. package/modules/hall-ui/public/profile-crafts.js +3 -5
  86. package/modules/hall-ui/public/profile.html +2 -3
  87. package/modules/hall-ui/public/project-settings.html +2 -2
  88. package/modules/hall-ui/public/ranks.html +2 -3
  89. package/modules/hall-ui/public/roadmap.html +2 -2
  90. package/modules/hall-ui/public/roster.html +3 -4
  91. package/modules/hall-ui/public/sessions.html +2 -3
  92. package/modules/hall-ui/public/settings.html +2 -3
  93. package/modules/hall-ui/public/shell.js +1 -5
  94. package/modules/hall-ui/public/sky-data.js +1 -1
  95. package/modules/hall-ui/public/street.html +2 -1
  96. package/modules/hall-ui/public/studio.html +2 -2
  97. package/modules/hall-ui/public/task-detail.js +1 -6
  98. package/modules/hall-ui/public/task-lib.js +3 -8
  99. package/modules/hall-ui/public/task.html +2 -3
  100. package/modules/hall-ui/public/thinking.html +2 -1
  101. package/modules/hall-ui/public/tweak-editor.html +2 -2
  102. package/modules/hall-ui/public/vote-control.js +1 -1
  103. package/modules/hall-ui/public/watch.html +2 -3
  104. package/modules/hall-ui/public/welcome-boot.js +3 -5
  105. package/modules/hall-ui/public/work.html +2 -3
  106. package/modules/ideas/completeness.js +1 -1
  107. package/modules/ideas/descent.js +0 -1
  108. package/modules/ideas/governor-docket.js +0 -2
  109. package/modules/ideas/idea-state.js +0 -1
  110. package/modules/ideas/ideator-stats.js +1 -1
  111. package/modules/ideas/votes.js +1 -1
  112. package/modules/lifecycle/autopickup-files.js +0 -3
  113. package/modules/lifecycle/conflict-resolve.js +0 -1
  114. package/modules/lifecycle/criterion-uat-db.js +0 -4
  115. package/modules/lifecycle/dead-deps.js +2 -2
  116. package/modules/lifecycle/est-advisory.js +1 -1
  117. package/modules/lifecycle/goal-category.js +0 -3
  118. package/modules/lifecycle/goal-spec-task.js +1 -1
  119. package/modules/lifecycle/governor-docket.js +0 -1
  120. package/modules/lifecycle/kickoff-seed.js +1 -1
  121. package/modules/lifecycle/mingle.js +3 -3
  122. package/modules/lifecycle/page-tweak-claim.js +1 -1
  123. package/modules/lifecycle/rebase-gate.js +1 -1
  124. package/modules/lifecycle/removal-derivation.js +0 -1
  125. package/modules/lifecycle/routes/abandon-handler.js +1 -1
  126. package/modules/lifecycle/routes/version-route-authz.js +0 -1
  127. package/modules/lifecycle/ship-card.js +2 -5
  128. package/modules/lifecycle/ship-preflight.js +0 -1
  129. package/modules/lifecycle/sky.js +1 -1
  130. package/modules/lifecycle/task-classifier.js +0 -1
  131. package/modules/lifecycle/task-visuals.js +0 -2
  132. package/modules/lifecycle/version-close.js +0 -1
  133. package/modules/memory/memory.js +0 -1
  134. package/modules/memory/search-chunker.js +1 -1
  135. package/modules/memory/search.js +0 -3
  136. package/modules/npm-release/preview/divert.js +1 -1
  137. package/modules/npm-release/preview/proxy.js +2 -3
  138. package/modules/npm-release/preview/runtime.js +1 -1
  139. package/modules/npm-release/public/task-widget.js +1 -1
  140. package/modules/onboarding/founding-builders.js +1 -1
  141. package/modules/onboarding/founding-mode.js +1 -1
  142. package/modules/onboarding/howto-guide.js +1 -1
  143. package/modules/onboarding/howto-state.js +1 -1
  144. package/modules/onboarding/onboarding-state.js +0 -4
  145. package/modules/onboarding/onboarding.js +0 -6
  146. package/modules/page-notes/notes.js +0 -1
  147. package/modules/platform-identity/community-leaderboard.js +0 -1
  148. package/modules/platform-identity/hub-keys.js +0 -2
  149. package/modules/platform-identity/instance-feedback.js +0 -1
  150. package/modules/platform-identity/read-rate-limit.js +1 -1
  151. package/modules/platform-identity/routes/sso.js +1 -3
  152. package/modules/platform-identity/visibility-pull.js +0 -9
  153. package/modules/provisioning/app-status.js +1 -1
  154. package/modules/provisioning/catalog-bridge.js +0 -1
  155. package/modules/provisioning/core-upgrade.js +1 -1
  156. package/modules/provisioning/credential-preflight.js +1 -1
  157. package/modules/provisioning/demo.js +2 -2
  158. package/modules/provisioning/look.js +1 -1
  159. package/modules/provisioning/routes/callback-page.js +3 -7
  160. package/modules/public-landing/public/projects.html +3 -5
  161. package/modules/raise/raise.js +2 -2
  162. package/modules/render-deploy/deploys.js +1 -1
  163. package/modules/security/artifact-scan/aggregate.js +0 -4
  164. package/modules/security/artifact-scan/detectors/capabilities.js +1 -1
  165. package/modules/security/artifact-scan/detectors/config-obfuscation.js +1 -1
  166. package/modules/security/artifact-scan/detectors/credentials.js +1 -1
  167. package/modules/security/artifact-scan/detectors/dependencies.js +1 -1
  168. package/modules/security/artifact-scan/detectors/injection.js +1 -1
  169. package/modules/security/artifact-scan/detectors/network.js +1 -1
  170. package/modules/security/artifact-scan/detectors/npm-lifecycle.js +1 -1
  171. package/modules/security/artifact-scan/detectors/permissions.js +1 -1
  172. package/modules/security/artifact-scan/detectors/shared.js +0 -3
  173. package/modules/security/artifact-scan/floor.js +0 -3
  174. package/modules/security/artifact-scan/mirror.js +1 -1
  175. package/modules/security/artifact-scan/quarantine.js +0 -1
  176. package/modules/security/artifact-scan/rebuild-spec.js +0 -2
  177. package/modules/security/artifact-scan/report.js +1 -1
  178. package/modules/security/artifact-scan/sanitize.js +0 -2
  179. package/modules/security/secret-scrub.js +0 -4
  180. package/modules/security/security.js +0 -2
  181. package/modules/sessions/accessibility.js +0 -2
  182. package/modules/sessions/bfg.js +0 -4
  183. package/modules/sessions/upload-health.js +0 -3
  184. package/modules/specialities/db.js +1 -1
  185. package/modules/specialities/specialities.js +1 -1
  186. package/modules/ui-design/kit/lib.js +2 -2
  187. package/package-lock.json +2 -2
  188. package/package.json +1 -1
  189. package/release-notes.json +26 -12
  190. package/scripts/bongos/escape-helper-guard.js +147 -0
  191. package/scripts/bongos/fitness.js +3 -2
  192. package/scripts/bongos/key-card.js +1 -4
  193. package/scripts/bongos/review-sheet.js +1 -3
  194. package/scripts/bongos/start.js +1 -1
  195. package/scripts/bongos/status.js +1 -1
  196. package/scripts/gds/escape-helper-guard.js +6 -0
  197. package/scripts/status-mirror-sync.js +2 -3
  198. package/src/bongos/module-scope-map.js +1 -0
  199. package/src/bongos/platform-visibility-gate.js +2 -3
  200. package/src/bongos/prelaunch-gate.js +2 -3
  201. package/src/bongos/routes/auth.js +1 -5
  202. package/src/bongos/serve-internal.js +2 -6
  203. package/src/html-escape.js +26 -0
  204. package/src/module-api.js +9 -1
  205. package/tests/blockers_kit_pager.mjs +2 -2
  206. package/tests/blockers_page_ui.mjs +2 -2
  207. package/tests/core_move_notice.mjs +3 -1
  208. package/tests/core_update_banner.mjs +2 -0
  209. package/tests/core_upgrade_owner_door.mjs +2 -0
  210. package/tests/craft_mode.mjs +2 -0
  211. package/tests/craft_mode_nav.mjs +3 -0
  212. package/tests/craft_review_mode.mjs +3 -2
  213. package/tests/criterion_ref_status.mjs +7 -2
  214. package/tests/curve_kind_overlay.mjs +2 -1
  215. package/tests/founding_mode.mjs +10 -2
  216. package/tests/github_install_nudge.mjs +4 -1
  217. package/tests/goal_map_page.mjs +2 -2
  218. package/tests/hall_approval_queue.mjs +2 -2
  219. package/tests/hall_city.mjs +1 -1
  220. package/tests/hall_fullscreen.mjs +2 -0
  221. package/tests/hall_gov_real_site.mjs +2 -0
  222. package/tests/hall_kit_charts.mjs +2 -1
  223. package/tests/hall_my_agents.mjs +3 -2
  224. package/tests/hall_nav.mjs +2 -0
  225. package/tests/hall_palette.mjs +2 -2
  226. package/tests/hall_reading_world.mjs +3 -1
  227. package/tests/hall_reference_world.mjs +2 -1
  228. package/tests/hall_signout.mjs +2 -0
  229. package/tests/hall_street.mjs +1 -1
  230. package/tests/hall_studio_world.mjs +3 -1
  231. package/tests/hall_tweak_editor.mjs +2 -1
  232. package/tests/helpers.mjs +17 -2
  233. package/tests/html_escape.mjs +149 -0
  234. package/tests/lightbulb.mjs +2 -0
  235. package/tests/module_api.mjs +1 -0
  236. package/tests/module_store_howto.mjs +2 -1
  237. package/tests/modules_external_export_contract.mjs +48 -0
  238. package/tests/nav_permission_atoms.mjs +1 -1
  239. package/tests/npm_release_where.mjs +2 -1
  240. package/tests/page_notes_panel.mjs +4 -3
  241. package/tests/paintbrush.mjs +2 -1
  242. package/tests/profile_connections_ui.mjs +2 -1
  243. package/tests/profile_role_headline.mjs +2 -2
  244. package/tests/profile_role_tabs.mjs +2 -2
  245. package/tests/projects_hub_repo_private.mjs +4 -0
  246. package/tests/terms_acceptance.mjs +1 -0
  247. package/tests/welcome_boot_browser.mjs +1 -1
@@ -161,7 +161,6 @@ async function healInstanceCatalog(db, row) {
161
161
  }
162
162
 
163
163
  module.exports = {
164
- CATALOG_HIDDEN_STATES,
165
164
  catalogEntryForInstance,
166
165
  // the disconnect runner leg forgets exactly what a publish wrote (task 1003249)
167
166
  catalogOriginForInstance, ownerGithubIdForInstance,
@@ -551,5 +551,5 @@ function makeUpdateChannelHandler({ provisioning, pool, parseId, validateOrRespo
551
551
  };
552
552
  }
553
553
 
554
- module.exports = { loadPreview, UPDATE_CHANNELS, DEFAULT_UPDATE_CHANNEL, updateChannelOf, selfUpdateChannel, makeUpdateChannelHandler, CORE_UPGRADE_FROM, lastCoreMove, lastMoveSuperseded, parseRepoNote, preflightForOffer, UPGRADE_SHAPES, VERSION_RE, isCoreVersion, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
554
+ module.exports = { loadPreview, UPDATE_CHANNELS, DEFAULT_UPDATE_CHANNEL, updateChannelOf, selfUpdateChannel, makeUpdateChannelHandler, lastCoreMove, lastMoveSuperseded, parseRepoNote, UPGRADE_SHAPES, VERSION_RE, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
555
555
 
@@ -167,4 +167,4 @@ async function adoptCredentialFault({ builderId, onboardMode, targetRef }, deps)
167
167
  return null;
168
168
  }
169
169
 
170
- module.exports = { adoptCredentialFault, parseTargetRef, readOwnerRepoToken, fetchRepoVisibility };
170
+ module.exports = { adoptCredentialFault, parseTargetRef };
@@ -171,6 +171,6 @@ async function sweepDemos(db, { enqueueTeardown, recordEvent, now = new Date() }
171
171
  }
172
172
 
173
173
  module.exports = {
174
- DEMO_LIMITS, DEMO_OFFLINE_DAYS, DEMO_WARN_DAYS, DEMO_DETAIL, DEMO_SETTINGS, OUTCOMES,
175
- parseDemo, fitCheck, withDemoDetail, demoRecord, demoView, startDemo, startDemoProject, settleDemo, sweepDemos,
174
+ DEMO_LIMITS, DEMO_SETTINGS, OUTCOMES,
175
+ parseDemo, fitCheck, withDemoDetail, demoRecord, demoView, startDemoProject, settleDemo, sweepDemos,
176
176
  };
@@ -165,5 +165,5 @@ module.exports = {
165
165
  upsertLogo, deleteLogo, readLogo,
166
166
  LIBRARY_LOOKS, LOOK_SETTINGS_VOCAB, LOOK_DEFAULTS, LOOK_COMPANION_ENV,
167
167
  lookChoice, lookAdjustAccent, lookAccentOf, logoUrlOf,
168
- LOGO_MAX_BYTES, LOGO_MIME, screenLogo, sniffLogo, logoFileName, LOGO_FILE,
168
+ LOGO_MAX_BYTES, LOGO_MIME, screenLogo, logoFileName, LOGO_FILE,
169
169
  };
@@ -20,12 +20,8 @@
20
20
 
21
21
  const api = require('../../../src/module-api');
22
22
 
23
- // Minimal HTML escaper for the callback landing page (no template engine here).
24
- function esc(s) {
25
- return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
26
- { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]
27
- ));
28
- }
23
+ // The one server escaper, through the doorway (task 1004876).
24
+ function esc(s) { return api.escapeHtml(s); }
29
25
 
30
26
  // The way back to the project (task 1004650): the hub's return route, keyed on the attempt's
31
27
  // one-time state. The SERVER resolves that state to the project it was minted for and lands
@@ -194,4 +190,4 @@ function callbackPage({ ok, title, body, state, domain, next }) {
194
190
  </body></html>`;
195
191
  }
196
192
 
197
- module.exports = { esc, callbackPage, returnHref, RETURN_PATH };
193
+ module.exports = { esc, callbackPage, returnHref };
@@ -2173,6 +2173,7 @@ summary{min-height:24px;padding:3px 0;}
2173
2173
 
2174
2174
  <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on <span data-brand-product-inline>Cloud&nbsp;Bongos</span>. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/#about">About</a><a href="/docs">Docs</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><!--apex-only:start--><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a><!--apex-only:end--></nav></footer>
2175
2175
 
2176
+ <script src="/modules/hall-ui/public/dom-utils.js"></script>
2176
2177
  <script>
2177
2178
  /* Brand-pack fill (R57): the server injects window.__BRANDING__ ahead of this
2178
2179
  script; baked markup above is the standalone fallback. */
@@ -2199,11 +2200,8 @@ summary{min-height:24px;padding:3px 0;}
2199
2200
 
2200
2201
  function $(id) { return document.getElementById(id); }
2201
2202
 
2202
- function esc(s) {
2203
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
2204
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
2205
- });
2206
- }
2203
+ /* the hall's shared escaper (dom-utils.js, loaded just above; task 1004876) */
2204
+ function esc(s) { return window.OTB.escapeHtml(s); }
2207
2205
 
2208
2206
  /* ── the app's setup on Render (task 1004353) ──
2209
2207
  GET /provisioning/instances/:id/render is the one source, on both the wizard's done
@@ -55,7 +55,7 @@ function isEditableKey(key) {
55
55
  return !!m && Number(m[1]) >= 1 && Number(m[1]) <= (DEFAULT_SETTINGS.founders || []).length;
56
56
  }
57
57
 
58
- const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
58
+ const { escapeHtml: esc } = require('../../src/module-api'); // the one server escaper (task 1004876)
59
59
  const usd = (n) => '$' + Math.round(n).toLocaleString('en-US');
60
60
  const TBA = '<span class="tba">TBA</span>';
61
61
 
@@ -170,5 +170,5 @@ module.exports = {
170
170
  TOKENS, SETTING_KEYS, MAX_BLOCK, EDIT_MIN_RANK, TBA,
171
171
  defaultCopy, defaultSettings, ndaBody, ndaVersion, isEditableKey,
172
172
  esc, usd, kindOf, normalizeBlock, renderInline, renderBlock, renderContext, fill, cleanSetting,
173
- hashPassword, passwordOk, validLinkedin, normalizeSignin, newSessionToken, hashToken, plainForDiscord,
173
+ hashPassword, passwordOk, normalizeSignin, newSessionToken, hashToken, plainForDiscord,
174
174
  };
@@ -197,7 +197,7 @@ async function saveApp(pool, { ownerId, serviceId, serviceName, builderId }) {
197
197
  }
198
198
 
199
199
  module.exports = {
200
- KEY_RE, OWNER_RE, SERVICE_RE, DEPLOY_RE, HISTORY_LIMIT,
200
+ KEY_RE, OWNER_RE, SERVICE_RE, DEPLOY_RE,
201
201
  statusWords, taskIdsIn, previewOf, costOf, shapeHistory, readHistory, getApp, saveApp,
202
202
  ROLLBACK_STATUSES,
203
203
  };
@@ -33,7 +33,6 @@ const TIER_CLAMP = Object.freeze({
33
33
  // A lens that did not finish cleanly contributes caution, never silence: an
34
34
  // errored/timed-out/budget-exhausted lens means its surface went UNREVIEWED.
35
35
  const LENS_OK = 'ok';
36
- const LENS_PROBLEM_STATUSES = Object.freeze(['error', 'timeout', 'budget_exhausted']);
37
36
 
38
37
  const rank = (v) => VERDICT_RANK[v] ?? VERDICT_RANK.dangerous; // unknown → worst
39
38
  const maxVerdict = (a, b) => (rank(a) >= rank(b) ? a : b);
@@ -123,11 +122,8 @@ function reduceVerdict({ floorReport = {}, lenses = [], tier = 'low', declaredDe
123
122
 
124
123
  module.exports = {
125
124
  VERDICTS,
126
- TIER_CLAMP,
127
125
  LENS_OK,
128
- LENS_PROBLEM_STATUSES,
129
126
  panelOutcome,
130
- lensHealth,
131
127
  coverageCap,
132
128
  reduceVerdict,
133
129
  };
@@ -50,4 +50,4 @@ function inventory(corpus) {
50
50
 
51
51
  // Constants exported as the white-box surface: the reducer/report stages and
52
52
  // tests read them to stay in lockstep with what the inventory actually covers.
53
- module.exports = { inventory, CAPABILITY_PATTERNS, SCRIPT_EXTENSIONS };
53
+ module.exports = { inventory };
@@ -38,4 +38,4 @@ function detect(corpus) {
38
38
  return findings;
39
39
  }
40
40
 
41
- module.exports = { detect, isJsonConfig };
41
+ module.exports = { detect };
@@ -114,4 +114,4 @@ function detect(corpus) {
114
114
  return findings;
115
115
  }
116
116
 
117
- module.exports = { detect, perFileHits, CREDENTIAL_PATTERNS };
117
+ module.exports = { detect, perFileHits };
@@ -112,4 +112,4 @@ function detect(corpus) {
112
112
  return findings;
113
113
  }
114
114
 
115
- module.exports = { detect, specSeverity, DEP_SECTIONS };
115
+ module.exports = { detect };
@@ -115,4 +115,4 @@ function detect(corpus) {
115
115
  return findings;
116
116
  }
117
117
 
118
- module.exports = { detect, OVERRIDE_PHRASES, SCANNER_PHRASES, codepointClasses };
118
+ module.exports = { detect };
@@ -64,4 +64,4 @@ function detect(corpus) {
64
64
  return findings;
65
65
  }
66
66
 
67
- module.exports = { detect, perFileHits, NETWORK_PATTERNS };
67
+ module.exports = { detect, perFileHits };
@@ -78,4 +78,4 @@ function detect(corpus) {
78
78
  return findings;
79
79
  }
80
80
 
81
- module.exports = { detect, AUTO_RUN_SCRIPTS };
81
+ module.exports = { detect };
@@ -88,4 +88,4 @@ function detect(corpus) {
88
88
  return findings;
89
89
  }
90
90
 
91
- module.exports = { detect, BYPASS_MARKERS };
91
+ module.exports = { detect };
@@ -202,14 +202,11 @@ function escapedConfigKeyCount(text) {
202
202
  }
203
203
 
204
204
  module.exports = {
205
- EXCERPT_MAX_CHARS,
206
205
  SEVERITIES,
207
- SENSITIVE_BASENAMES,
208
206
  isSensitivePath,
209
207
  basename,
210
208
  finding,
211
209
  excerpt,
212
- sanitizeDetail,
213
210
  lineAt,
214
211
  parseJsonStrict,
215
212
  rawKeyCounts,
@@ -359,12 +359,9 @@ async function runFloor({ pristineDir, quarantineRoot, entries, sha = null, cont
359
359
 
360
360
  module.exports = {
361
361
  FLOOR_VERSION,
362
- REPORT_BASENAME,
363
362
  VERDICT_RANK,
364
363
  DETECTORS,
365
364
  buildCorpus,
366
- computeFloorVerdict,
367
- countBySeverity,
368
365
  coOccurrence,
369
366
  coverageFindings,
370
367
  dedupeFindings,
@@ -155,4 +155,4 @@ async function buildMirror({ pristineDir, mirrorDir, quarantineRoot, entries, ca
155
155
  return { mirrored, stubs, unreviewedBytes, fenceDelim, indexPath };
156
156
  }
157
157
 
158
- module.exports = { buildMirror, MIRROR_CAPS, INDEX_BASENAME };
158
+ module.exports = { buildMirror, MIRROR_CAPS };
@@ -254,7 +254,6 @@ function createQuarantine({ tmpRoot = os.tmpdir(), forbiddenRoots } = {}) {
254
254
  module.exports = {
255
255
  canonical,
256
256
  isInsideLexical,
257
- canonicalUncached,
258
257
  clearCanonicalCache,
259
258
  CAPS,
260
259
  REPO_ROOT,
@@ -112,8 +112,6 @@ function assembleRebuildSpec({
112
112
  }
113
113
 
114
114
  module.exports = {
115
- SPEC_LIMITS,
116
- GROUND_RULES,
117
115
  isDocPath,
118
116
  selectDocFiles,
119
117
  assembleRebuildSpec,
@@ -152,4 +152,4 @@ function assembleReport({
152
152
  return out.join('\n');
153
153
  }
154
154
 
155
- module.exports = { assembleReport, findingLine };
155
+ module.exports = { assembleReport };
@@ -313,8 +313,6 @@ async function buildSanitizedCopy({ pristineDir, quarantineRoot, entries, remova
313
313
  }
314
314
 
315
315
  module.exports = {
316
- SANITIZED_DIRNAME,
317
- DIFF_BASENAME,
318
316
  hasMaliceIndicator,
319
317
  planSanitize,
320
318
  buildSanitizedCopy,
@@ -255,15 +255,11 @@ function scrubSecrets(text, extraSecrets = [], opts = undefined) {
255
255
  module.exports = {
256
256
  REDACTED,
257
257
  SECRET_PATTERNS,
258
- SECRET_ASSIGNMENT_RE,
259
258
  shannonEntropy,
260
- charClasses,
261
259
  looksHighEntropySecret,
262
- ENTROPY_CANDIDATE_RE,
263
260
  redactHighEntropy,
264
261
  ENV_SECRET_KEY_RE,
265
262
  ENV_SECRET_PREFIX_RE,
266
- ENV_BENIGN_KEY_RE,
267
263
  collectEnvSecrets,
268
264
  scrubSecrets,
269
265
  };
@@ -283,8 +283,6 @@ async function linkVulnerabilityReportFix(id, fixTaskId) {
283
283
  module.exports = {
284
284
  SEVERITIES,
285
285
  VULN_STATUSES,
286
- DEFAULT_BOUNTY,
287
- SEVERITY_BLURB,
288
286
  envName,
289
287
  bountyTable,
290
288
  bountyTableDetailed,
@@ -356,7 +356,6 @@ module.exports = {
356
356
  scoreAccessibility,
357
357
  // Exported so the tests can pin the contracts (waivers, leniency, each
358
358
  // detector) without reconstructing a whole digest for every case.
359
- resolveProfile,
360
359
  waiversFor,
361
360
  leniencyFor,
362
361
  assistantProse,
@@ -366,7 +365,6 @@ module.exports = {
366
365
  bandFor,
367
366
  SIGNAL_SPEC,
368
367
  JARGON_TERMS,
369
- UI_BACKED_SUBJECTS,
370
368
  LOW_CONFIDENCE_CHARS,
371
369
  PROFILE_DEFAULTS,
372
370
  PROFILE_VALUES,
@@ -346,12 +346,8 @@ module.exports = {
346
346
  buildAdoptionSuggestion,
347
347
  // 6C.2 correction builder
348
348
  buildCorrection,
349
- TRANSFERABLE_TYPES,
350
- PII_PATTERNS,
351
349
  // exported for tests + reuse
352
350
  parseFrontmatter,
353
- stripFrontmatter,
354
351
  clamp01,
355
352
  QUALITY_WEIGHTS,
356
- ALLOWED_MEMORY_TYPES,
357
353
  };
@@ -187,11 +187,8 @@ function uploadHealthVerdict(rows, opts = {}) {
187
187
  module.exports = {
188
188
  DEFAULT_WINDOW_HOURS,
189
189
  DEFAULT_LOOKBACK_DAYS,
190
- DEFAULT_MIN_SHIPS,
191
- DEFAULT_MAX_UNCOVERED_SHARE,
192
190
  clampLookbackDays,
193
191
  clampWindowHours,
194
192
  clampSampleLimit,
195
- normalizeBuilderRows,
196
193
  uploadHealthVerdict,
197
194
  };
@@ -278,5 +278,5 @@ module.exports = {
278
278
  createSpeciality, getSpeciality, listVisibleTo, updateSpeciality,
279
279
  addDocument, listDocuments, deleteDocument,
280
280
  linkLearning, countTraining,
281
- adopt, abandon, deactivateCraft, activeForCraft, listAdoptions, setEnabledSkills,
281
+ adopt, abandon, activeForCraft, listAdoptions, setEnabledSkills,
282
282
  };
@@ -303,4 +303,4 @@ function offeredTo(specialities, viewer, adoptions = []) {
303
303
  });
304
304
  }
305
305
 
306
- module.exports = { VISIBILITIES, LIMITS, SKILL_NAME_RE, slugify, validateDraft, skillListErrors, enabledSkillsErrors, canAdopt, describeForApi, offeredTo };
306
+ module.exports = { VISIBILITIES, LIMITS, SKILL_NAME_RE, slugify, validateDraft, enabledSkillsErrors, canAdopt, describeForApi, offeredTo };
@@ -524,9 +524,9 @@ function packColors(pack) {
524
524
  }
525
525
 
526
526
  module.exports = {
527
- REPO_ROOT, KIT_DIR, STYLES_DIR, EXIT, FLOORS, SIZES, DEFAULT_SIZES, MODES, CHANNELS, SETTLE_MS, ACTIONS, EXPECT_KEYS,
527
+ REPO_ROOT, KIT_DIR, STYLES_DIR, EXIT, FLOORS, SIZES, DEFAULT_SIZES, MODES, CHANNELS, SETTLE_MS, ACTIONS,
528
528
  argOf, hasFlag, listArg, pageName, normalizePageArg, resolveSurfaceDir, sidecarPath, readJson,
529
- validateActions, runActions, validateExpect, factsInPage, expectProblems, auditFloors, reducedMotionProblems, craftTells,
529
+ runActions, factsInPage, expectProblems, auditFloors, reducedMotionProblems, craftTells,
530
530
  validateStatesFile, validateProbesFile, NoBrowserError, launchBrowser, attachCollectors, withMode,
531
531
  freePort, startStub, parseColor, composite, luminance, contrastRatio, instancePack, packColors,
532
532
  listStyles, resolveStyle, styleBrandingFile,
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.182",
3
+ "version": "1.22.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.182",
9
+ "version": "1.22.2",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.182",
3
+ "version": "1.22.2",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -10331,18 +10331,6 @@
10331
10331
  {
10332
10332
  "id": "1004889",
10333
10333
  "text": "An automatic security check on search results works again, and it now really catches a private note leaking to the wrong person instead of passing by luck."
10334
- },
10335
- {
10336
- "id": "1004886",
10337
- "text": "Core releases can no longer be labelled as containing code that never actually went out."
10338
- },
10339
- {
10340
- "id": "1004859",
10341
- "text": "The Gate page is now called Autobongos. It keeps the runner controls and drops the list of held pull requests, which was always empty because the check it waited on never ran. Old links to /gate still work. The two permissions"
10342
- },
10343
- {
10344
- "id": "1004863",
10345
- "text": "Eight database tests that had quietly stopped working are repaired and now run on every change, including the one that proves an idea's author is never paid twice and the one that proves search never shows someone a page above"
10346
10334
  }
10347
10335
  ],
10348
10336
  "1.21.179": [
@@ -10368,5 +10356,31 @@
10368
10356
  "id": "1004890",
10369
10357
  "text": "Removed a leftover permission-list checker that nothing in the product used; the real protections and their tests are unchanged."
10370
10358
  }
10359
+ ],
10360
+ "1.22.0": [
10361
+ {
10362
+ "id": "1004886",
10363
+ "text": "Core releases can no longer be labelled as containing code that never actually went out."
10364
+ },
10365
+ {
10366
+ "id": "1004859",
10367
+ "text": "The Gate page is now called Autobongos. It keeps the runner controls and drops the list of held pull requests, which was always empty because the check it waited on never ran. Old links to /gate still work. The two permissions"
10368
+ },
10369
+ {
10370
+ "id": "1004863",
10371
+ "text": "Eight database tests that had quietly stopped working are repaired and now run on every change, including the one that proves an idea's author is never paid twice and the one that proves search never shows someone a page above"
10372
+ }
10373
+ ],
10374
+ "1.22.1": [
10375
+ {
10376
+ "id": "1004876",
10377
+ "text": "Builder-written text now goes through one safety filter in the browser and one on the server instead of 42 hand-made copies, six of which let a stray quote through — and a check stops a new copy from creeping back."
10378
+ }
10379
+ ],
10380
+ "1.22.2": [
10381
+ {
10382
+ "id": "1004891",
10383
+ "text": "Cleaned up 220 internal code hooks nothing else used, cutting the project's dead-code count from 439 to 213."
10384
+ }
10371
10385
  ]
10372
10386
  }
@@ -0,0 +1,147 @@
1
+ // scripts/bongos/escape-helper-guard.js — fitness check: ONE HTML escaper per side
2
+ // (task 1004876, goal 1000135).
3
+ //
4
+ // WHY. The HTML escaper is what keeps a builder's own text (a task title, an idea,
5
+ // a goal name) from turning into live markup on someone else's screen. The
6
+ // 2026-10-25 audit found ~45 private copies of it, and six of them escaped fewer
7
+ // characters than the rest (no single quote) or printed "null" for a missing
8
+ // value — each one a place a cross-site-scripting hole could hide. They were
9
+ // collapsed onto exactly two definitions:
10
+ //
11
+ // browser modules/hall-ui/public/dom-utils.js → window.OTB.escapeHtml
12
+ // server src/html-escape.js → escapeHtml (modules reach it
13
+ // through src/module-api.js)
14
+ //
15
+ // This check keeps it that way. It does not look for a NAME (a copy called
16
+ // `hesc` or `sesc` hid from a name search) — it looks for the escape TABLE
17
+ // itself: a quoted &lt; string literal is what every hand-rolled escaper has
18
+ // to contain, and nothing else in the code needs one. A file that contains one
19
+ // outside the allow-list below is a new private escaper: delete it and call the
20
+ // shared one (a thin `function esc(s) { return window.OTB.escapeHtml(s); }`
21
+ // delegate is fine — it holds no table).
22
+ //
23
+ // A delegate must really delegate, though. primer.js used to fall back to
24
+ // `(s) => String(s)` — an "escaper" with no table that escaped nothing — so a
25
+ // local HTML-escaper NAME (esc / escapeHtml / escAttr / escHtml / hesc / sesc)
26
+ // must point at a shared escaper on the line that defines it. In browser code
27
+ // (modules/*/public/) every such name is checked; on the server `esc` alone is
28
+ // exempt, because scripts there use it for regex and markdown escaping too.
29
+ //
30
+ // Second half: a hall page must load dom-utils.js as its FIRST hall script and
31
+ // without `defer`/`async`, because shell.js, palette.js and craft-mode.js run
32
+ // while the page parses and now escape through window.OTB.
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('node:fs');
37
+ const path = require('node:path');
38
+
39
+ const { REPO_ROOT } = require('./fitness-lib.js');
40
+
41
+ // The two shared escapers, plus the one file whose escaper is NOT an HTML one.
42
+ const ALLOWED = new Map([
43
+ ['modules/hall-ui/public/dom-utils.js', 'the shared BROWSER escaper (window.OTB.escapeHtml)'],
44
+ ['src/html-escape.js', 'the shared SERVER escaper (module-api escapeHtml)'],
45
+ ['modules/copy-desk/docx.js', 'an XML escaper for .docx files (&apos;, drops control characters) — a different contract'],
46
+ ]);
47
+
48
+ // Where code lives. Tests, docs and vendored bundles are out of scope: a test may
49
+ // carry its own decoder/encoder to CHECK the shared one against.
50
+ const SCAN_ROOTS = ['src', 'scripts', 'modules', 'server.js', 'bin'];
51
+ const SCAN_EXT = /\.(c|m)?js$|\.html$/;
52
+ const SKIP_DIR = new Set(['node_modules', '.git', 'tests', 'fixtures']);
53
+
54
+ // A quoted &lt; literal — the entity standing alone as a string in any quote style.
55
+ const ESCAPE_TABLE_RE = /(['"`])&lt;\1/;
56
+
57
+ function walk(abs, out) {
58
+ let st;
59
+ try { st = fs.statSync(abs); } catch { return out; }
60
+ if (st.isFile()) { if (SCAN_EXT.test(abs)) out.push(abs); return out; }
61
+ for (const ent of fs.readdirSync(abs, { withFileTypes: true })) {
62
+ if (SKIP_DIR.has(ent.name)) continue;
63
+ walk(path.join(abs, ent.name), out);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // findEscapeTables(text) → 1-based line numbers holding a hand-rolled escape table.
69
+ function findEscapeTables(text) {
70
+ const lines = String(text).split('\n');
71
+ const hits = [];
72
+ for (let i = 0; i < lines.length; i++) if (ESCAPE_TABLE_RE.test(lines[i])) hits.push(i + 1);
73
+ return hits;
74
+ }
75
+
76
+ // findFakeDelegates(text, { browser }) → 1-based lines that define a local HTML
77
+ // escaper name WITHOUT pointing it at a shared escaper (window.OTB.escapeHtml, a
78
+ // kit/reader alias of it, api.escapeHtml, or src/html-escape.js).
79
+ const BROWSER_NAMES = /(?:function\s+|(?:const|let|var)\s+)(esc|escapeHtml|escAttr|escHtml|hesc|sesc)\s*(?:\(|=)/;
80
+ const SERVER_NAMES = /(?:function\s+|(?:const|let|var)\s+)(escapeHtml|escAttr|escHtml|hesc|sesc)\s*(?:\(|=)/;
81
+ const POINTS_AT_SHARED = /\.escapeHtml\b|\.esc\b|\bescapeHtml;|require\([^)]*html-escape/;
82
+ function findFakeDelegates(text, { browser = false } = {}) {
83
+ const lines = String(text).split('\n');
84
+ const hits = [];
85
+ for (let i = 0; i < lines.length; i++) {
86
+ const m = lines[i].match(browser ? BROWSER_NAMES : SERVER_NAMES);
87
+ if (!m) continue;
88
+ if (!POINTS_AT_SHARED.test(lines[i].slice(m.index + m[0].length))) hits.push(i + 1);
89
+ }
90
+ return hits;
91
+ }
92
+
93
+ // hallPageOrderProblem(html) → null when the page is fine, else a sentence saying
94
+ // why. A page loading no /builders/ script at all has nothing to order.
95
+ function hallPageOrderProblem(html) {
96
+ const tags = [...String(html).matchAll(/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/g)]
97
+ .map((m) => ({ tag: m[0], src: m[1].split('?')[0] }))
98
+ .filter((t) => t.src.startsWith('/builders/'));
99
+ if (!tags.length) return null;
100
+ const at = tags.findIndex((t) => t.src === '/builders/dom-utils.js');
101
+ if (at < 0) return 'loads hall scripts but never /builders/dom-utils.js (window.OTB.escapeHtml would be undefined)';
102
+ if (at > 0) return `loads ${tags[0].src} before /builders/dom-utils.js — dom-utils must be the first hall script`;
103
+ if (/\b(defer|async)\b/.test(tags[at].tag)) return 'loads /builders/dom-utils.js with defer/async — scripts that run while the page parses would find no window.OTB';
104
+ if (tags.filter((t) => t.src === '/builders/dom-utils.js').length > 1) return 'loads /builders/dom-utils.js twice';
105
+ return null;
106
+ }
107
+
108
+ function checkOneHtmlEscaper({ root = REPO_ROOT } = {}) {
109
+ const violations = [];
110
+ let scanned = 0;
111
+ for (const r of SCAN_ROOTS) {
112
+ for (const abs of walk(path.join(root, r), [])) {
113
+ scanned++;
114
+ const relPath = path.relative(root, abs).split(path.sep).join('/');
115
+ if (ALLOWED.has(relPath)) continue;
116
+ const text = fs.readFileSync(abs, 'utf8');
117
+ const hits = findEscapeTables(text);
118
+ if (hits.length) {
119
+ violations.push(`${relPath}:${hits[0]} — a private HTML escaper (a quoted &lt; literal: an escape table). Use the shared one: `
120
+ + 'window.OTB.escapeHtml in the browser, escapeHtml from src/html-escape.js (or api.escapeHtml in a module) on the server.');
121
+ }
122
+ const fakes = findFakeDelegates(text, { browser: /^modules\/[^/]+\/public\//.test(relPath) });
123
+ if (fakes.length) {
124
+ violations.push(`${relPath}:${fakes[0]} — defines a local HTML escaper name that does not point at a shared escaper `
125
+ + '(a fallback like `(s) => String(s)` escapes nothing). Delegate to window.OTB.escapeHtml / api.escapeHtml / src/html-escape.js.');
126
+ }
127
+ }
128
+ }
129
+ const hallDir = path.join(root, 'modules', 'hall-ui', 'public');
130
+ let pages = 0;
131
+ for (const abs of walk(hallDir, []).filter((f) => f.endsWith('.html'))) {
132
+ pages++;
133
+ const problem = hallPageOrderProblem(fs.readFileSync(abs, 'utf8'));
134
+ if (problem) violations.push(`${path.relative(root, abs).split(path.sep).join('/')} — ${problem}`);
135
+ }
136
+ return {
137
+ name: 'one HTML escaper per side (task 1004876)',
138
+ ok: violations.length === 0,
139
+ hardFail: violations.length > 0,
140
+ violations,
141
+ warnings: [],
142
+ note: `${scanned} code files hold no private escape table (allowed: ${[...ALLOWED.keys()].join(', ')}); `
143
+ + `${pages} hall pages load dom-utils.js first.`,
144
+ };
145
+ }
146
+
147
+ module.exports = { checkOneHtmlEscaper, findEscapeTables, findFakeDelegates, hallPageOrderProblem, ALLOWED };
@@ -523,7 +523,7 @@ const KERNEL_FILES = [
523
523
  'src/module-api.js', // the published doorway
524
524
  'src/modules.js', // the registry/resolver facade
525
525
  'src/module-seams.js', // the seam registry (ports/events/contributions)
526
- 'src/role-responsibilities.js', // the owner's written role statements (task 1003732) — modules.js reads it; imports nothing
526
+ 'src/role-responsibilities.js', 'src/html-escape.js', // owner's role statements (task 1003732) + THE server HTML escaper (task 1004876) — both import nothing
527
527
  'src/module-loader/loader.js', // the disk loader (discover → validate → mount)
528
528
  'src/module-loader/manifest-schema.js', // the manifest validator
529
529
  'src/module-loader/semver.js', // coreVersion compatibility
@@ -1371,6 +1371,7 @@ const CHECKS = [
1371
1371
  // Task 1003206 carved this file from 2,004 lines to ~1,350, so that toll is paid
1372
1372
  // and the check gets its own line back.
1373
1373
  require('./version-literal-guard.js').checkNoVersionLiterals,
1374
+ require('./escape-helper-guard.js').checkOneHtmlEscaper, // task 1004876: ONE HTML escaper per side (dom-utils.js / src/html-escape.js), and hall pages load dom-utils first
1374
1375
  require('./dev-box-guard.js').checkNoDevBox, // task 1003899 / ADR 0346: the retired dev box stays out (its exemption list lives in that file)
1375
1376
  require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
1376
1377
  require('./routine-schedule.js').checkRoutineCadencesScheduled, // Check 25 — task 1003209: a routine that declares a cadence must have a scheduler firing it AT that period (ADR 0188)
@@ -1436,7 +1437,7 @@ module.exports = {
1436
1437
  scanForRawNul,
1437
1438
  checkVocabularyWall, VOCAB_ALLOW, VOCAB_CITATION, VOCAB_EXTS, vocabFilesToScan,
1438
1439
  checkGoalMembershipVocabulary, GOAL_VOCAB_SURFACE, checkGovernmentRenameIdentity, RENAME_IDENTITY_RE, checkPrelaunchVocabulary, PRELAUNCH_SURFACE, // 19c task 1003120, 19d task 1003152
1439
- runAll,
1440
+ runAll, CHECKS, // CHECKS: so a test can prove a check is registered, not just written (task 1004876)
1440
1441
  resolvesToHostGame,
1441
1442
  checkCoreHostBoundary,
1442
1443
  checkClaudeMdBudget,
@@ -112,10 +112,7 @@ function renderMonospaceKeyCard(model) {
112
112
  }
113
113
 
114
114
  // ---- HTML renderer (the in-feed branded card) ---------------------------
115
- function esc(s) {
116
- return String(s == null ? '' : s).replace(/[&<>"']/g, (c) =>
117
- ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
118
- }
115
+ const { escapeHtml: esc } = require('../../src/html-escape'); // the one server escaper (task 1004876)
119
116
  function renderKeyCardHtml(model) {
120
117
  const m = model;
121
118
  const bg = 'var(--color-background-warning)';